1//============================================================================
2//
3// SSSS tt lll lll
4// SS SS tt ll ll
5// SS tttttt eeee ll ll aaaa
6// SSSS tt ee ee ll ll aa
7// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
8// SS SS tt ee ll ll aa aa
9// SSSS ttt eeeee llll llll aaaaa
10//
11// Copyright (c) 1995-2019 by Bradford W. Mott, Stephen Anthony
12// and the Stella Team
13//
14// See the file "License.txt" for information on usage and redistribution of
15// this file, and for a DISCLAIMER OF ALL WARRANTIES.
16//============================================================================
17
18#include "bspf.hxx"
19#include "System.hxx"
20#include "M6502.hxx"
21#include "FSNode.hxx"
22#include "DiStella.hxx"
23#include "Debugger.hxx"
24#include "DebuggerParser.hxx"
25#include "CpuDebug.hxx"
26#include "OSystem.hxx"
27#include "Settings.hxx"
28#include "Version.hxx"
29#include "Cart.hxx"
30#include "CartDebug.hxx"
31#include "CartDebugWidget.hxx"
32#include "CartRamWidget.hxx"
33#include "RomWidget.hxx"
34#include "Base.hxx"
35#include "exception/EmulationWarning.hxx"
36
37using Common::Base;
38using std::hex;
39using std::dec;
40using std::setfill;
41using std::setw;
42using std::left;
43using std::right;
44
45// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
46CartDebug::CartDebug(Debugger& dbg, Console& console, const OSystem& osystem)
47 : DebuggerSystem(dbg, console),
48 myOSystem(osystem),
49 myDebugWidget(nullptr),
50 myAddrToLineIsROM(true),
51 myLabelLength(8) // longest pre-defined label
52{
53 // Add case sensitive compare for user labels
54 // TODO - should user labels be case insensitive too?
55 auto usrCmp = [](const string& a, const string& b) { return a < b; };
56 myUserAddresses = LabelToAddr(usrCmp);
57
58 // Add case insensitive compare for system labels
59 auto sysCmp = [](const string& a, const string& b) {
60 return BSPF::compareIgnoreCase(a, b) < 0;
61 };
62 mySystemAddresses = LabelToAddr(sysCmp);
63
64 // Add Zero-page RAM addresses
65 for(uInt16 i = 0x80; i <= 0xFF; ++i)
66 {
67 myState.rport.push_back(i);
68 myState.wport.push_back(i);
69 myOldState.rport.push_back(i);
70 myOldState.wport.push_back(i);
71 }
72
73 // Create bank information for each potential bank, and an extra one for ZP RAM
74 // Banksizes greater than 4096 indicate multi-bank ROMs, but we handle only
75 // 4K pieces at a time
76 // Banksizes less than 4K use the actual value
77 size_t banksize = 0;
78 myConsole.cartridge().getImage(banksize);
79
80 BankInfo info;
81 info.size = std::min<size_t>(banksize, 4_KB);
82 for(uInt32 i = 0; i < myConsole.cartridge().bankCount(); ++i)
83 myBankInfo.push_back(info);
84
85 info.size = 128; // ZP RAM
86 myBankInfo.push_back(info);
87
88 // We know the address for the startup bank right now
89 myBankInfo[myConsole.cartridge().startBank()].addressList.push_front(
90 myDebugger.dpeek(0xfffc));
91 addLabel("Start", myDebugger.dpeek(0xfffc, DATA));
92
93 // Add system equates
94 for(uInt16 addr = 0x00; addr <= 0x0F; ++addr)
95 {
96 if(ourTIAMnemonicR[addr])
97 mySystemAddresses.emplace(ourTIAMnemonicR[addr], addr);
98 myReserved.TIARead[addr] = false;
99 }
100 for(uInt16 addr = 0x00; addr <= 0x3F; ++addr)
101 {
102 if(ourTIAMnemonicW[addr])
103 mySystemAddresses.emplace(ourTIAMnemonicW[addr], addr);
104 myReserved.TIAWrite[addr] = false;
105 }
106 for(uInt16 addr = 0x280; addr <= 0x297; ++addr)
107 {
108 if(ourIOMnemonic[addr-0x280])
109 mySystemAddresses.emplace(ourIOMnemonic[addr-0x280], addr);
110 myReserved.IOReadWrite[addr-0x280] = false;
111 }
112 for(uInt16 addr = 0x80; addr <= 0xFF; ++addr)
113 {
114 mySystemAddresses.emplace(ourZPMnemonic[addr-0x80], addr);
115 myReserved.ZPRAM[addr-0x80] = false;
116 }
117
118 myReserved.Label.clear();
119 myDisassembly.list.reserve(2048);
120
121 // Add settings for Distella
122 DiStella::settings.gfxFormat =
123 myOSystem.settings().getInt("dis.gfxformat") == 16 ? Base::F_16 : Base::F_2;
124 DiStella::settings.resolveCode =
125 myOSystem.settings().getBool("dis.resolve");
126 DiStella::settings.showAddresses =
127 myOSystem.settings().getBool("dis.showaddr");
128 DiStella::settings.aFlag = false; // Not currently configurable
129 DiStella::settings.fFlag = true; // Not currently configurable
130 DiStella::settings.rFlag = myOSystem.settings().getBool("dis.relocate");
131 DiStella::settings.bFlag = true; // Not currently configurable
132 DiStella::settings.bytesWidth = 8+1; // TODO - configure based on window size
133}
134
135// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
136const DebuggerState& CartDebug::getState()
137{
138 myState.ram.clear();
139 for(uInt32 i = 0; i < myState.rport.size(); ++i)
140 myState.ram.push_back(myDebugger.peek(myState.rport[i]));
141
142 if(myDebugWidget)
143 myState.bank = myDebugWidget->bankState();
144
145 return myState;
146}
147
148// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
149void CartDebug::saveOldState()
150{
151 myOldState.ram.clear();
152 for(uInt32 i = 0; i < myOldState.rport.size(); ++i)
153 myOldState.ram.push_back(myDebugger.peek(myOldState.rport[i]));
154
155 if(myDebugWidget)
156 {
157 myOldState.bank = myDebugWidget->bankState();
158 myDebugWidget->saveOldState();
159 }
160}
161
162// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
163int CartDebug::lastReadBaseAddress()
164{
165 return mySystem.m6502().lastReadBaseAddress();
166}
167
168// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
169int CartDebug::lastWriteBaseAddress()
170{
171 return mySystem.m6502().lastWriteBaseAddress();
172}
173
174// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
175string CartDebug::toString()
176{
177 ostringstream buf;
178 uInt32 bytesPerLine;
179
180 switch(Base::format())
181 {
182 case Base::F_16:
183 case Base::F_10:
184 bytesPerLine = 0x10;
185 break;
186
187 case Base::F_2:
188 bytesPerLine = 0x04;
189 break;
190
191 case Base::F_DEFAULT:
192 default:
193 return DebuggerParser::red("invalid base, this is a BUG");
194 }
195
196 const CartState& state = static_cast<const CartState&>(getState());
197 const CartState& oldstate = static_cast<const CartState&>(getOldState());
198
199 uInt32 curraddr = 0, bytesSoFar = 0;
200 for(uInt32 i = 0; i < state.ram.size(); i += bytesPerLine, bytesSoFar += bytesPerLine)
201 {
202 // We detect different 'pages' of RAM when the addresses jump by
203 // more than the number of bytes on the previous line, or when 256
204 // bytes have been previously output
205 if(state.rport[i] - curraddr > bytesPerLine || bytesSoFar >= 256)
206 {
207 char port[37];
208 std::snprintf(port, 36, "%04x: (rport = %04x, wport = %04x)\n",
209 state.rport[i], state.rport[i], state.wport[i]);
210 port[2] = port[3] = 'x';
211 buf << DebuggerParser::red(port);
212 bytesSoFar = 0;
213 }
214 curraddr = state.rport[i];
215 buf << Base::HEX2 << (curraddr & 0x00ff) << ": ";
216
217 for(uInt8 j = 0; j < bytesPerLine; ++j)
218 {
219 buf << myDebugger.invIfChanged(state.ram[i+j], oldstate.ram[i+j]) << " ";
220
221 if(j == 0x07) buf << " ";
222 }
223 buf << endl;
224 }
225
226 return buf.str();
227}
228
229// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
230bool CartDebug::disassemble(bool force)
231{
232 // Test current disassembly; don't re-disassemble if it hasn't changed
233 // Also check if the current PC is in the current list
234 bool bankChanged = myConsole.cartridge().bankChanged();
235 uInt16 PC = myDebugger.cpuDebug().pc();
236 int pcline = addressToLine(PC);
237 bool pcfound = (pcline != -1) && (uInt32(pcline) < myDisassembly.list.size()) &&
238 (myDisassembly.list[pcline].disasm[0] != '.');
239 bool pagedirty = (PC & 0x1000) ? mySystem.isPageDirty(0x1000, 0x1FFF) :
240 mySystem.isPageDirty(0x80, 0xFF);
241
242 bool changed = !mySystem.autodetectMode() &&
243 (force || bankChanged || !pcfound || pagedirty);
244 if(changed)
245 {
246 // Are we disassembling from ROM or ZP RAM?
247 BankInfo& info = (PC & 0x1000) ? myBankInfo[getBank(PC)] :
248 myBankInfo[myBankInfo.size()-1];
249
250 // If the offset has changed, all old addresses must be 'converted'
251 // For example, if the list contains any $fxxx and the address space is now
252 // $bxxx, it must be changed
253 uInt16 offset = (PC - (PC % 0x1000));
254 AddressList& addresses = info.addressList;
255 for(auto& i: addresses)
256 i = (i & 0xFFF) + offset;
257
258 // Only add addresses when absolutely necessary, to cut down on the
259 // work that Distella has to do
260 if(bankChanged || !pcfound)
261 {
262 AddressList::const_iterator i;
263 for(i = addresses.cbegin(); i != addresses.cend(); ++i)
264 {
265 if (PC == *i) // already present
266 break;
267 }
268 // Otherwise, add the item at the end
269 if (i == addresses.end())
270 addresses.push_back(PC);
271 }
272
273 // Always attempt to resolve code sections unless it's been
274 // specifically disabled
275 bool found = fillDisassemblyList(info, PC);
276 if(!found && DiStella::settings.resolveCode)
277 {
278 // Temporarily turn off code resolution
279 DiStella::settings.resolveCode = false;
280 fillDisassemblyList(info, PC);
281 DiStella::settings.resolveCode = true;
282 }
283 }
284
285 return changed;
286}
287
288// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
289bool CartDebug::fillDisassemblyList(BankInfo& info, uInt16 search)
290{
291 // An empty address list means that DiStella can't do a disassembly
292 if(info.addressList.size() == 0)
293 return false;
294
295 myDisassembly.list.clear();
296 myDisassembly.fieldwidth = 24 + myLabelLength;
297 DiStella distella(*this, myDisassembly.list, info, DiStella::settings,
298 myDisLabels, myDisDirectives, myReserved);
299
300 // Parts of the disassembly will be accessed later in different ways
301 // We place those parts in separate maps, to speed up access
302 bool found = false;
303 myAddrToLineList.clear();
304 myAddrToLineIsROM = info.offset & 0x1000;
305 for(uInt32 i = 0; i < myDisassembly.list.size(); ++i)
306 {
307 const DisassemblyTag& tag = myDisassembly.list[i];
308 const uInt16 address = tag.address & 0xFFF;
309
310 // Exclude 'ROW'; they don't have a valid address
311 if(tag.type != CartDebug::ROW)
312 {
313 // Create a mapping from addresses to line numbers
314 myAddrToLineList.emplace(address, i);
315
316 // Did we find the search value?
317 if(address == (search & 0xFFF))
318 found = true;
319 }
320 }
321 return found;
322}
323
324// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
325int CartDebug::addressToLine(uInt16 address) const
326{
327 // Switching between ZP RAM address space and Cart/ROM address space
328 // means the line isn't present
329 if(!myAddrToLineIsROM != !(address & 0x1000))
330 return -1;
331
332 const auto& iter = myAddrToLineList.find(address & 0xFFF);
333 return iter != myAddrToLineList.end() ? iter->second : -1;
334}
335
336// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
337string CartDebug::disassemble(uInt16 start, uInt16 lines) const
338{
339 // Fill the string with disassembled data
340 start &= 0xFFF;
341 ostringstream buffer;
342
343 // First find the lines in the range, and determine the longest string
344 uInt32 list_size = uInt32(myDisassembly.list.size());
345 uInt32 begin = list_size, end = 0, length = 0;
346 for(end = 0; end < list_size && lines > 0; ++end)
347 {
348 const CartDebug::DisassemblyTag& tag = myDisassembly.list[end];
349 if((tag.address & 0xfff) >= start)
350 {
351 if(begin == list_size) begin = end;
352 if(tag.type != CartDebug::ROW)
353 length = std::max(length, uInt32(tag.disasm.length()));
354
355 --lines;
356 }
357 }
358
359 // Now output the disassembly, using as little space as possible
360 for(uInt32 i = begin; i < end; ++i)
361 {
362 const CartDebug::DisassemblyTag& tag = myDisassembly.list[i];
363 if(tag.type == CartDebug::NONE)
364 continue;
365 else if(tag.address)
366 buffer << std::uppercase << std::hex << std::setw(4)
367 << std::setfill('0') << tag.address << ": ";
368 else
369 buffer << " ";
370
371 buffer << tag.disasm << std::setw(int(length - tag.disasm.length() + 2))
372 << std::setfill(' ') << " "
373 << std::setw(4) << std::left << tag.ccount << " " << tag.bytes << endl;
374 }
375
376 return buffer.str();
377}
378
379// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
380bool CartDebug::addDirective(CartDebug::DisasmType type,
381 uInt16 start, uInt16 end, int bank)
382{
383 if(end < start || start == 0 || end == 0)
384 return false;
385
386 if(bank < 0) // Do we want the current bank or ZP RAM?
387 bank = (myDebugger.cpuDebug().pc() & 0x1000) ?
388 getBank(myDebugger.cpuDebug().pc()) : int(myBankInfo.size())-1;
389
390 bank = std::min(bank, bankCount());
391 BankInfo& info = myBankInfo[bank];
392 DirectiveList& list = info.directiveList;
393
394 DirectiveTag tag;
395 tag.type = type;
396 tag.start = start;
397 tag.end = end;
398
399 DirectiveList::iterator i;
400
401 // If the same directive and range is added, consider it a removal instead
402 for(i = list.begin(); i != list.end(); ++i)
403 {
404 if(i->type == tag.type && i->start == tag.start && i->end == tag.end)
405 {
406 list.erase(i);
407 return false;
408 }
409 }
410
411 // Otherwise, scan the list and make space for a 'smart' merge
412 // Note that there are 4 possibilities:
413 // 1: a range is completely inside the new range
414 // 2: a range is completely outside the new range
415 // 3: a range overlaps at the beginning of the new range
416 // 4: a range overlaps at the end of the new range
417 for(i = list.begin(); i != list.end(); ++i)
418 {
419 // Case 1: remove range that is completely inside new range
420 if(tag.start <= i->start && tag.end >= i->end)
421 {
422 i = list.erase(i);
423 }
424 // Case 2: split the old range
425 else if(tag.start >= i->start && tag.end <= i->end)
426 {
427 // Only split when necessary
428 if(tag.type == i->type)
429 return true; // node is fine as-is
430
431 // Create new endpoint
432 DirectiveTag tag2;
433 tag2.type = i->type;
434 tag2.start = tag.end + 1;
435 tag2.end = i->end;
436
437 // Modify startpoint
438 i->end = tag.start - 1;
439
440 // Insert new endpoint
441 ++i;
442 list.insert(i, tag2);
443 break; // no need to go further; this is the insertion point
444 }
445 // Case 3: truncate end of old range
446 else if(tag.start >= i->start && tag.start <= i->end)
447 {
448 i->end = tag.start - 1;
449 }
450 // Case 4: truncate start of old range
451 else if(tag.end >= i->start && tag.end <= i->end)
452 {
453 i->start = tag.end + 1;
454 }
455 }
456
457 // We now know that the new range can be inserted without overlap
458 // Where possible, consecutive ranges should be merged rather than
459 // new nodes created
460 for(i = list.begin(); i != list.end(); ++i)
461 {
462 if(tag.end < i->start) // node should be inserted *before* this one
463 {
464 bool createNode = true;
465
466 // Is the new range ending consecutive with the old range beginning?
467 // If so, a merge will suffice
468 if(i->type == tag.type && tag.end + 1 == i->start)
469 {
470 i->start = tag.start;
471 createNode = false; // a merge was done, so a new node isn't needed
472 }
473
474 // Can we also merge with the previous range (if any)?
475 if(i != list.begin())
476 {
477 DirectiveList::iterator p = i;
478 --p;
479 if(p->type == tag.type && p->end + 1 == tag.start)
480 {
481 if(createNode) // a merge with right-hand range didn't previously occur
482 {
483 p->end = tag.end;
484 createNode = false; // a merge was done, so a new node isn't needed
485 }
486 else // merge all three ranges
487 {
488 i->start = p->start;
489 i = list.erase(p);
490 createNode = false; // a merge was done, so a new node isn't needed
491 }
492 }
493 }
494
495 // Create the node only when necessary
496 if(createNode)
497 i = list.insert(i, tag);
498
499 break;
500 }
501 }
502 // Otherwise, add the tag at the end
503 if(i == list.end())
504 list.push_back(tag);
505
506 return true;
507}
508
509// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
510int CartDebug::getBank(uInt16 address)
511{
512 return myConsole.cartridge().getBank(address);
513}
514
515// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
516int CartDebug::getPCBank()
517{
518 return myConsole.cartridge().getBank(myDebugger.cpuDebug().pc());
519}
520
521// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
522int CartDebug::bankCount() const
523{
524 return myConsole.cartridge().bankCount();
525}
526
527// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
528bool CartDebug::addLabel(const string& label, uInt16 address)
529{
530 // Only user-defined labels can be added or redefined
531 switch(addressType(address))
532 {
533 case AddrType::TIA:
534 case AddrType::IO:
535 return false;
536 default:
537 removeLabel(label);
538 myUserAddresses.emplace(label, address);
539 myUserLabels.emplace(address, label);
540 myLabelLength = std::max(myLabelLength, uInt16(label.size()));
541 mySystem.setDirtyPage(address);
542 return true;
543 }
544}
545
546// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
547bool CartDebug::removeLabel(const string& label)
548{
549 // Only user-defined labels can be removed
550 const auto& iter = myUserAddresses.find(label);
551 if(iter != myUserAddresses.end())
552 {
553 // Erase the address assigned to the label
554 const auto& iter2 = myUserLabels.find(iter->second);
555 if(iter2 != myUserLabels.end())
556 myUserLabels.erase(iter2);
557
558 // Erase the label itself
559 mySystem.setDirtyPage(iter->second);
560 myUserAddresses.erase(iter);
561
562 return true;
563 }
564 return false;
565}
566
567// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
568bool CartDebug::getLabel(ostream& buf, uInt16 addr, bool isRead, int places) const
569{
570 switch(addressType(addr))
571 {
572 case AddrType::TIA:
573 {
574 if(isRead)
575 {
576 uInt16 a = addr & 0x0F, offset = addr & 0xFFF0;
577 if(ourTIAMnemonicR[a])
578 {
579 buf << ourTIAMnemonicR[a];
580 if(offset > 0)
581 buf << "|$" << Base::HEX2 << offset;
582 }
583 else
584 buf << "$" << Base::HEX2 << addr;
585 }
586 else
587 {
588 uInt16 a = addr & 0x3F, offset = addr & 0xFFC0;
589 if(ourTIAMnemonicW[a])
590 {
591 buf << ourTIAMnemonicW[a];
592 if(offset > 0)
593 buf << "|$" << Base::HEX2 << offset;
594 }
595 else
596 buf << "$" << Base::HEX2 << addr;
597 }
598 return true;
599 }
600
601 case AddrType::IO:
602 {
603 uInt16 a = addr & 0xFF, offset = addr & 0xFD00;
604 if(a <= 0x97)
605 {
606 if(ourIOMnemonic[a - 0x80])
607 {
608 buf << ourIOMnemonic[a - 0x80];
609 if(offset > 0)
610 buf << "|$" << Base::HEX2 << offset;
611 }
612 else
613 buf << "$" << Base::HEX2 << addr;
614 }
615 else
616 buf << "$" << Base::HEX2 << addr;
617
618 return true;
619 }
620
621 case AddrType::ZPRAM:
622 {
623 // RAM can use user-defined labels; otherwise we default to
624 // standard mnemonics
625 auto iter = myUserLabels.find(addr);
626 if(iter != myUserLabels.end())
627 {
628 buf << iter->second;
629 }
630 else
631 {
632 uInt16 a = addr & 0xFF, offset = addr & 0xFF00;
633 if((iter = myUserLabels.find(a)) != myUserLabels.end())
634 buf << iter->second;
635 else
636 buf << ourZPMnemonic[a - 0x80];
637 if(offset > 0)
638 buf << "|$" << Base::HEX2 << offset;
639 }
640
641 return true;
642 }
643
644 case AddrType::ROM:
645 {
646 // These addresses can never be in the system labels list
647 const auto& iter = myUserLabels.find(addr);
648 if(iter != myUserLabels.end())
649 {
650 buf << iter->second;
651 return true;
652 }
653 break;
654 }
655 }
656
657 switch(places)
658 {
659 case 2:
660 buf << "$" << Base::HEX2 << addr;
661 return true;
662 case 4:
663 buf << "$" << Base::HEX4 << addr;
664 return true;
665 case 8:
666 buf << "$" << Base::HEX8 << addr;
667 return true;
668 }
669
670 return false;
671}
672
673// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
674string CartDebug::getLabel(uInt16 addr, bool isRead, int places) const
675{
676 ostringstream buf;
677 getLabel(buf, addr, isRead, places);
678 return buf.str();
679}
680
681// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
682int CartDebug::getAddress(const string& label) const
683{
684 LabelToAddr::const_iterator iter;
685
686 if((iter = mySystemAddresses.find(label)) != mySystemAddresses.end())
687 return iter->second;
688 else if((iter = myUserAddresses.find(label)) != myUserAddresses.end())
689 return iter->second;
690 else
691 return -1;
692}
693
694// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
695string CartDebug::loadListFile()
696{
697 // The default naming/location for list files is the ROM dir based on the
698 // actual ROM filename
699
700 if(myListFile == "")
701 {
702 FilesystemNode lst(myOSystem.romFile().getPathWithExt("") + ".lst");
703 if(lst.isFile() && lst.isReadable())
704 myListFile = lst.getPath();
705 else
706 return DebuggerParser::red("list file \'" + lst.getShortPath() + "\' not found");
707 }
708
709 FilesystemNode node(myListFile);
710 ifstream in(node.getPath());
711 if(!in.is_open())
712 return DebuggerParser::red("list file '" + node.getShortPath() + "' not readable");
713
714 while(!in.eof())
715 {
716 string line, addr_s;
717
718 getline(in, line);
719
720 if(!in.good() || line == "" || line[0] == '-')
721 continue;
722 else // Search for constants
723 {
724 stringstream buf(line);
725
726 // Swallow first value, then get actual numerical value for address
727 // We need to read the address as a string, since it may contain 'U'
728 int addr = -1;
729 buf >> addr >> addr_s;
730 if(addr_s.length() == 0)
731 continue;
732 const char* p = addr_s[0] == 'U' ? addr_s.c_str() + 1 : addr_s.c_str();
733 addr = int(strtoul(p, nullptr, 16));
734
735 // For now, completely ignore ROM addresses
736 if(!(addr & 0x1000))
737 {
738 // Search for pattern 'xx yy CONSTANT ='
739 buf.seekg(20); // skip potential '????'
740 int xx = -1, yy = -1;
741 char eq = '\0';
742 buf >> hex >> xx >> hex >> yy >> line >> eq;
743 if(xx >= 0 && yy >= 0 && eq == '=')
744 //myUserCLabels.emplace(xx*256+yy, line);
745 addLabel(line, xx * 256 + yy);
746 }
747 }
748 }
749 myDebugger.rom().invalidate();
750
751 return "list file '" + node.getShortPath() + "' loaded OK";
752}
753
754// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
755string CartDebug::loadSymbolFile()
756{
757 // The default naming/location for symbol files is the ROM dir based on the
758 // actual ROM filename
759
760 if(mySymbolFile == "")
761 {
762 FilesystemNode sym(myOSystem.romFile().getPathWithExt("") + ".sym");
763 if(sym.isFile() && sym.isReadable())
764 mySymbolFile = sym.getPath();
765 else
766 return DebuggerParser::red("symbol file \'" + sym.getShortPath() + "\' not found");
767 }
768
769 FilesystemNode node(mySymbolFile);
770 ifstream in(node.getPath());
771 if(!in.is_open())
772 return DebuggerParser::red("symbol file '" + node.getShortPath() + "' not readable");
773
774 myUserAddresses.clear();
775 myUserLabels.clear();
776
777 while(!in.eof())
778 {
779 string label;
780 int value = -1;
781
782 getline(in, label);
783 if(!in.good()) continue;
784 stringstream buf(label);
785 buf >> label >> hex >> value;
786
787 if(label.length() > 0 && label[0] != '-' && value >= 0)
788 {
789 // Make sure the value doesn't represent a constant
790 // For now, we simply ignore constants completely
791 //const auto& iter = myUserCLabels.find(value);
792 //if(iter == myUserCLabels.end() || !BSPF::equalsIgnoreCase(label, iter->second))
793 const auto& iter = myUserLabels.find(value);
794 if (iter == myUserLabels.end() || !BSPF::equalsIgnoreCase(label, iter->second))
795 {
796 // Check for period, and strip leading number
797 string::size_type pos = label.find_first_of(".", 0);
798 if(pos != string::npos)
799 addLabel(label.substr(pos), value);
800 else
801 {
802 pos = label.find_last_of("$");
803 if (pos == string::npos || pos != label.length() - 1)
804 addLabel(label, value);
805 }
806 }
807 }
808 }
809 myDebugger.rom().invalidate();
810
811 return "symbol file '" + node.getShortPath() + "' loaded OK";
812}
813
814// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
815string CartDebug::loadConfigFile()
816{
817 // The default naming/location for config files is the ROM dir based on the
818 // actual ROM filename
819
820 if(myCfgFile == "")
821 {
822 FilesystemNode cfg(myOSystem.romFile().getPathWithExt("") + ".cfg");
823 if(cfg.isFile() && cfg.isReadable())
824 myCfgFile = cfg.getPath();
825 else
826 return DebuggerParser::red("config file \'" + cfg.getShortPath() + "\' not found");
827 }
828
829 FilesystemNode node(myCfgFile);
830 ifstream in(node.getPath());
831 if(!in.is_open())
832 return "Unable to load directives from " + node.getPath();
833
834 // Erase all previous directives
835 for(auto& bi: myBankInfo)
836 bi.directiveList.clear();
837
838 int currentbank = 0;
839 while(!in.eof())
840 {
841 // Skip leading space
842 int c = in.peek();
843 while(c == ' ' || c == '\t')
844 {
845 in.get();
846 c = in.peek();
847 }
848
849 string line;
850 c = in.peek();
851 if(c == '/') // Comment, swallow line and continue
852 {
853 getline(in, line);
854 continue;
855 }
856 else if(c == '[')
857 {
858 in.get();
859 getline(in, line, ']');
860 stringstream buf(line);
861 buf >> currentbank;
862 }
863 else // Should be commands from this point on
864 {
865 getline(in, line);
866 stringstream buf;
867 buf << line;
868
869 string directive;
870 uInt16 start = 0, end = 0;
871 buf >> directive;
872 if(BSPF::startsWithIgnoreCase(directive, "ORG"))
873 {
874 // TODO - figure out what to do with this
875 buf >> hex >> start;
876 }
877 else if(BSPF::startsWithIgnoreCase(directive, "CODE"))
878 {
879 buf >> hex >> start >> hex >> end;
880 addDirective(CartDebug::CODE, start, end, currentbank);
881 }
882 else if(BSPF::startsWithIgnoreCase(directive, "GFX"))
883 {
884 buf >> hex >> start >> hex >> end;
885 addDirective(CartDebug::GFX, start, end, currentbank);
886 }
887 else if(BSPF::startsWithIgnoreCase(directive, "PGFX"))
888 {
889 buf >> hex >> start >> hex >> end;
890 addDirective(CartDebug::PGFX, start, end, currentbank);
891 }
892 else if(BSPF::startsWithIgnoreCase(directive, "DATA"))
893 {
894 buf >> hex >> start >> hex >> end;
895 addDirective(CartDebug::DATA, start, end, currentbank);
896 }
897 else if(BSPF::startsWithIgnoreCase(directive, "ROW"))
898 {
899 buf >> hex >> start;
900 buf >> hex >> end;
901 addDirective(CartDebug::ROW, start, end, currentbank);
902 }
903 }
904 }
905 myDebugger.rom().invalidate();
906
907 stringstream retVal;
908 if(myConsole.cartridge().bankCount() > 1)
909 retVal << DebuggerParser::red("config file for multi-bank ROM not fully supported\n");
910 retVal << "config file '" << node.getShortPath() << "' loaded OK";
911 return retVal.str();
912
913}
914
915// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
916string CartDebug::saveConfigFile()
917{
918 // The default naming/location for config files is the ROM dir based on the
919 // actual ROM filename
920
921 FilesystemNode cfg;
922 if(myCfgFile == "")
923 {
924 cfg = FilesystemNode(myOSystem.romFile().getPathWithExt("") + ".cfg");
925 if(cfg.isFile() && cfg.isWritable())
926 myCfgFile = cfg.getPath();
927 else
928 return DebuggerParser::red("config file \'" + cfg.getShortPath() + "\' not writable");
929 }
930
931 const string& name = myConsole.properties().get(PropType::Cart_Name);
932 const string& md5 = myConsole.properties().get(PropType::Cart_MD5);
933
934 ofstream out(cfg.getPath());
935 if(!out.is_open())
936 return "Unable to save directives to " + cfg.getShortPath();
937
938 // Store all bank information
939 out << "//Stella.pro: \"" << name << "\"" << endl
940 << "//MD5: " << md5 << endl
941 << endl;
942 for(uInt32 b = 0; b < myConsole.cartridge().bankCount(); ++b)
943 {
944 out << "[" << b << "]" << endl;
945 getBankDirectives(out, myBankInfo[b]);
946 }
947
948 stringstream retVal;
949 if(myConsole.cartridge().bankCount() > 1)
950 retVal << DebuggerParser::red("config file for multi-bank ROM not fully supported\n");
951 retVal << "config file '" << cfg.getShortPath() << "' saved OK";
952 return retVal.str();
953}
954
955// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
956string CartDebug::saveDisassembly()
957{
958 if(myDisasmFile == "")
959 {
960 const string& propsname =
961 myConsole.properties().get(PropType::Cart_Name) + ".asm";
962
963 myDisasmFile = FilesystemNode(myOSystem.defaultSaveDir() + propsname).getPath();
964 }
965
966 FilesystemNode node(myDisasmFile);
967 ofstream out(node.getPath());
968 if(!out.is_open())
969 return "Unable to save disassembly to " + node.getShortPath();
970
971#define ALIGN(x) setfill(' ') << left << setw(x)
972
973 // We can't print the header to the disassembly until it's actually
974 // been processed; therefore buffer output to a string first
975 ostringstream buf;
976 buf << "\n\n;***********************************************************\n"
977 << "; Bank " << myConsole.cartridge().getBank();
978 if (myConsole.cartridge().bankCount() > 1)
979 buf << " / 0.." << myConsole.cartridge().bankCount() - 1;
980 buf << "\n;***********************************************************\n\n";
981
982 // Use specific settings for disassembly output
983 // This will most likely differ from what you see in the debugger
984 DiStella::Settings settings;
985 settings.gfxFormat = DiStella::settings.gfxFormat;
986 settings.resolveCode = true;
987 settings.showAddresses = false;
988 settings.aFlag = false; // Otherwise DASM gets confused
989 settings.fFlag = DiStella::settings.fFlag;
990 settings.rFlag = DiStella::settings.rFlag;
991 settings.bytesWidth = 8+1; // same as Stella debugger
992 settings.bFlag = DiStella::settings.bFlag; // process break routine (TODO)
993
994 Disassembly disasm;
995 disasm.list.reserve(2048);
996 for(int bank = 0; bank < myConsole.cartridge().bankCount(); ++bank)
997 {
998 BankInfo& info = myBankInfo[bank];
999 // An empty address list means that DiStella can't do a disassembly
1000 if(info.addressList.size() == 0)
1001 continue;
1002
1003 // Disassemble bank
1004 disasm.list.clear();
1005 DiStella distella(*this, disasm.list, info, settings,
1006 myDisLabels, myDisDirectives, myReserved);
1007
1008 if (myReserved.breakFound)
1009 addLabel("Break", myDebugger.dpeek(0xfffe));
1010
1011 buf << " SEG CODE\n"
1012 << " ORG $" << Base::HEX4 << info.offset << "\n\n";
1013
1014 // Format in 'distella' style
1015 for(uInt32 i = 0; i < disasm.list.size(); ++i)
1016 {
1017 const DisassemblyTag& tag = disasm.list[i];
1018
1019 // Add label (if any)
1020 if(tag.label != "")
1021 buf << ALIGN(4) << (tag.label) << "\n";
1022 buf << " ";
1023
1024 switch(tag.type)
1025 {
1026 case CartDebug::CODE:
1027 {
1028 buf << ALIGN(32) << tag.disasm << tag.ccount.substr(0, 5) << tag.ctotal << tag.ccount.substr(5, 2);
1029 if (tag.disasm.find("WSYNC") != std::string::npos)
1030 buf << "\n;---------------------------------------";
1031 break;
1032 }
1033 case CartDebug::ROW:
1034 {
1035 buf << ".byte " << ALIGN(32) << tag.disasm.substr(6, 8*4-1) << "; $" << Base::HEX4 << tag.address << " (*)";
1036 break;
1037 }
1038 case CartDebug::GFX:
1039 {
1040 buf << ".byte " << (settings.gfxFormat == Base::F_2 ? "%" : "$")
1041 << tag.bytes << " ; |";
1042 for(int c = 12; c < 20; ++c)
1043 buf << ((tag.disasm[c] == '\x1e') ? "#" : " ");
1044 buf << ALIGN(13) << "|" << "$" << Base::HEX4 << tag.address << " (G)";
1045 break;
1046 }
1047 case CartDebug::PGFX:
1048 {
1049 buf << ".byte " << (settings.gfxFormat == Base::F_2 ? "%" : "$")
1050 << tag.bytes << " ; |";
1051 for(int c = 12; c < 20; ++c)
1052 buf << ((tag.disasm[c] == '\x1f') ? "*" : " ");
1053 buf << ALIGN(13) << "|" << "$" << Base::HEX4 << tag.address << " (P)";
1054 break;
1055 }
1056 case CartDebug::DATA:
1057 {
1058 buf << ".byte " << ALIGN(32) << tag.disasm.substr(6, 8 * 4 - 1) << "; $" << Base::HEX4 << tag.address << " (D)";
1059 break;
1060 }
1061 case CartDebug::NONE:
1062 default:
1063 {
1064 break;
1065 }
1066 } // switch
1067 buf << "\n";
1068 }
1069 }
1070
1071 // Some boilerplate, similar to what DiStella adds
1072 auto timeinfo = BSPF::localTime();
1073 out << "; Disassembly of " << myOSystem.romFile().getShortPath() << "\n"
1074 << "; Disassembled " << std::put_time(&timeinfo, "%c\n")
1075 << "; Using Stella " << STELLA_VERSION << "\n;\n"
1076 << "; ROM properties name : " << myConsole.properties().get(PropType::Cart_Name) << "\n"
1077 << "; ROM properties MD5 : " << myConsole.properties().get(PropType::Cart_MD5) << "\n"
1078 << "; Bankswitch type : " << myConsole.cartridge().about() << "\n;\n"
1079 << "; Legend: * = CODE not yet run (tentative code)\n"
1080 << "; D = DATA directive (referenced in some way)\n"
1081 << "; G = GFX directive, shown as '#' (stored in player, missile, ball)\n"
1082 << "; P = PGFX directive, shown as '*' (stored in playfield)\n"
1083 << "; i = indexed accessed only\n"
1084 << "; c = used by code executed in RAM\n"
1085 << "; s = used by stack\n"
1086 << "; ! = page crossed, 1 cycle penalty\n"
1087 << "\n processor 6502\n\n";
1088
1089 bool addrUsed = false;
1090 for(uInt16 addr = 0x00; addr <= 0x0F; ++addr)
1091 addrUsed = addrUsed || myReserved.TIARead[addr] || (mySystem.getAccessFlags(addr) & WRITE);
1092 for(uInt16 addr = 0x00; addr <= 0x3F; ++addr)
1093 addrUsed = addrUsed || myReserved.TIAWrite[addr] || (mySystem.getAccessFlags(addr) & DATA);
1094 for(uInt16 addr = 0x00; addr <= 0x17; ++addr)
1095 addrUsed = addrUsed || myReserved.IOReadWrite[addr];
1096 if(addrUsed)
1097 {
1098 out << "\n;-----------------------------------------------------------\n"
1099 << "; TIA and IO constants accessed\n"
1100 << ";-----------------------------------------------------------\n\n";
1101
1102 // TIA read access
1103 for(uInt16 addr = 0x00; addr <= 0x0F; ++addr)
1104 if(myReserved.TIARead[addr] && ourTIAMnemonicR[addr])
1105 out << ALIGN(16) << ourTIAMnemonicR[addr] << "= $"
1106 << Base::HEX2 << right << addr << " ; (R)\n";
1107 else if (mySystem.getAccessFlags(addr) & DATA)
1108 out << ";" << ALIGN(16-1) << ourTIAMnemonicR[addr] << "= $"
1109 << Base::HEX2 << right << addr << " ; (Ri)\n";
1110 out << "\n";
1111
1112 // TIA write access
1113 for(uInt16 addr = 0x00; addr <= 0x3F; ++addr)
1114 if(myReserved.TIAWrite[addr] && ourTIAMnemonicW[addr])
1115 out << ALIGN(16) << ourTIAMnemonicW[addr] << "= $"
1116 << Base::HEX2 << right << addr << " ; (W)\n";
1117 else if (mySystem.getAccessFlags(addr) & WRITE)
1118 out << ";" << ALIGN(16-1) << ourTIAMnemonicW[addr] << "= $"
1119 << Base::HEX2 << right << addr << " ; (Wi)\n";
1120 out << "\n";
1121
1122 // RIOT IO access
1123 for(uInt16 addr = 0x00; addr <= 0x17; ++addr)
1124 if(myReserved.IOReadWrite[addr] && ourIOMnemonic[addr])
1125 out << ALIGN(16) << ourIOMnemonic[addr] << "= $"
1126 << Base::HEX4 << right << (addr+0x280) << "\n";
1127 }
1128
1129 addrUsed = false;
1130 for(uInt16 addr = 0x80; addr <= 0xFF; ++addr)
1131 addrUsed = addrUsed || myReserved.ZPRAM[addr-0x80]
1132 || (mySystem.getAccessFlags(addr) & (DATA | WRITE))
1133 || (mySystem.getAccessFlags(addr|0x100) & (DATA | WRITE));
1134 if(addrUsed)
1135 {
1136 bool addLine = false;
1137 out << "\n\n;-----------------------------------------------------------\n"
1138 << "; RIOT RAM (zero-page) labels\n"
1139 << ";-----------------------------------------------------------\n\n";
1140
1141 for (uInt16 addr = 0x80; addr <= 0xFF; ++addr) {
1142 bool ramUsed = (mySystem.getAccessFlags(addr) & (DATA | WRITE));
1143 bool codeUsed = (mySystem.getAccessFlags(addr) & CODE);
1144 bool stackUsed = (mySystem.getAccessFlags(addr|0x100) & (DATA | WRITE));
1145
1146 if (myReserved.ZPRAM[addr - 0x80] &&
1147 myUserLabels.find(addr) == myUserLabels.end()) {
1148 if (addLine)
1149 out << "\n";
1150 out << ALIGN(16) << ourZPMnemonic[addr - 0x80] << "= $"
1151 << Base::HEX2 << right << (addr)
1152 << ((stackUsed|codeUsed) ? "; (" : "")
1153 << (codeUsed ? "c" : "")
1154 << (stackUsed ? "s" : "")
1155 << ((stackUsed | codeUsed) ? ")" : "")
1156 << "\n";
1157 addLine = false;
1158 } else if (ramUsed|codeUsed|stackUsed) {
1159 if (addLine)
1160 out << "\n";
1161 out << ALIGN(18) << ";" << "$"
1162 << Base::HEX2 << right << (addr)
1163 << " ("
1164 << (ramUsed ? "i" : "")
1165 << (codeUsed ? "c" : "")
1166 << (stackUsed ? "s" : "")
1167 << ")\n";
1168 addLine = false;
1169 } else
1170 addLine = true;
1171 }
1172 }
1173
1174 if(myReserved.Label.size() > 0)
1175 {
1176 out << "\n\n;-----------------------------------------------------------\n"
1177 << "; Non Locatable Labels\n"
1178 << ";-----------------------------------------------------------\n\n";
1179 for(const auto& iter: myReserved.Label)
1180 out << ALIGN(16) << iter.second << "= $" << iter.first << "\n";
1181 }
1182
1183 if(myUserLabels.size() > 0)
1184 {
1185 out << "\n\n;-----------------------------------------------------------\n"
1186 << "; User Defined Labels\n"
1187 << ";-----------------------------------------------------------\n\n";
1188 int max_len = 16;
1189 for(const auto& iter: myUserLabels)
1190 max_len = std::max(max_len, int(iter.second.size()));
1191 for(const auto& iter: myUserLabels)
1192 out << ALIGN(max_len) << iter.second << "= $" << iter.first << "\n";
1193 }
1194
1195 // And finally, output the disassembly
1196 out << buf.str();
1197
1198 stringstream retVal;
1199 if(myConsole.cartridge().bankCount() > 1)
1200 retVal << DebuggerParser::red("disassembly for multi-bank ROM not fully supported, only currently enabled banks disassembled\n");
1201 retVal << "saved " << node.getShortPath() << " OK";
1202 return retVal.str();
1203}
1204
1205// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1206string CartDebug::saveRom()
1207{
1208 const string& rom = myConsole.properties().get(PropType::Cart_Name) + ".a26";
1209
1210 FilesystemNode node(myOSystem.defaultSaveDir() + rom);
1211 ofstream out(node.getPath(), std::ios::binary);
1212 if(out && myConsole.cartridge().saveROM(out))
1213 return "saved ROM as " + node.getShortPath();
1214 else
1215 return DebuggerParser::red("failed to save ROM");
1216}
1217
1218// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1219string CartDebug::listConfig(int bank)
1220{
1221 if(myConsole.cartridge().bankCount() > 1)
1222 return DebuggerParser::red("config file for multi-bank ROM not yet supported");
1223
1224 uInt32 startbank = 0, endbank = bankCount();
1225 if(bank >= 0 && bank < bankCount())
1226 {
1227 startbank = bank;
1228 endbank = startbank + 1;
1229 }
1230
1231 ostringstream buf;
1232 buf << "(items marked '*' are user-defined)" << endl;
1233 for(uInt32 b = startbank; b < endbank; ++b)
1234 {
1235 BankInfo& info = myBankInfo[b];
1236 buf << "[" << b << "]" << endl;
1237 for(const auto& i: info.directiveList)
1238 {
1239 if(i.type != CartDebug::NONE)
1240 {
1241 buf << "(*) ";
1242 disasmTypeAsString(buf, i.type);
1243 buf << " " << Base::HEX4 << i.start << " " << Base::HEX4 << i.end << endl;
1244 }
1245 }
1246 getBankDirectives(buf, info);
1247 }
1248
1249 return buf.str();
1250}
1251
1252// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1253string CartDebug::clearConfig(int bank)
1254{
1255 uInt32 startbank = 0, endbank = bankCount();
1256 if(bank >= 0 && bank < bankCount())
1257 {
1258 startbank = bank;
1259 endbank = startbank + 1;
1260 }
1261
1262 size_t count = 0;
1263 for(uInt32 b = startbank; b < endbank; ++b)
1264 {
1265 count += myBankInfo[b].directiveList.size();
1266 myBankInfo[b].directiveList.clear();
1267 }
1268
1269 ostringstream buf;
1270 if(count > 0)
1271 buf << "removed " << dec << count << " directives from "
1272 << dec << (endbank - startbank) << " banks";
1273 else
1274 buf << "no directives present";
1275 return buf.str();
1276}
1277
1278// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1279void CartDebug::getCompletions(const char* in, StringList& completions) const
1280{
1281 // First scan system equates
1282 for(uInt16 addr = 0x00; addr <= 0x0F; ++addr)
1283 if(ourTIAMnemonicR[addr] && BSPF::matches(ourTIAMnemonicR[addr], in))
1284 completions.push_back(ourTIAMnemonicR[addr]);
1285 for(uInt16 addr = 0x00; addr <= 0x3F; ++addr)
1286 if(ourTIAMnemonicW[addr] && BSPF::matches(ourTIAMnemonicW[addr], in))
1287 completions.push_back(ourTIAMnemonicW[addr]);
1288 for(uInt16 addr = 0; addr <= 0x297-0x280; ++addr)
1289 if(ourIOMnemonic[addr] && BSPF::matches(ourIOMnemonic[addr], in))
1290 completions.push_back(ourIOMnemonic[addr]);
1291 for(uInt16 addr = 0; addr <= 0x7F; ++addr)
1292 if(ourZPMnemonic[addr] && BSPF::matches(ourZPMnemonic[addr], in))
1293 completions.push_back(ourZPMnemonic[addr]);
1294
1295 // Now scan user-defined labels
1296 for(const auto& iter: myUserAddresses)
1297 {
1298 const char* l = iter.first.c_str();
1299 if(BSPF::matches(l, in))
1300 completions.push_back(l);
1301 }
1302}
1303
1304// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1305CartDebug::AddrType CartDebug::addressType(uInt16 addr) const
1306{
1307 // Determine the type of address to access the correct list
1308 // These addresses were based on (and checked against) Kroko's 2600 memory
1309 // map, found at http://www.qotile.net/minidig/docs/2600_mem_map.txt
1310 if(addr % 0x2000 < 0x1000)
1311 {
1312 if((addr & 0x00ff) < 0x80)
1313 return AddrType::TIA;
1314 else
1315 {
1316 switch(addr & 0x0f00)
1317 {
1318 case 0x000: case 0x100: case 0x400: case 0x500:
1319 case 0x800: case 0x900: case 0xc00: case 0xd00:
1320 return AddrType::ZPRAM;
1321 case 0x200: case 0x300: case 0x600: case 0x700:
1322 case 0xa00: case 0xb00: case 0xe00: case 0xf00:
1323 return AddrType::IO;
1324 }
1325 }
1326 }
1327 return AddrType::ROM;
1328}
1329
1330// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1331void CartDebug::getBankDirectives(ostream& buf, BankInfo& info) const
1332{
1333 // Start with the offset for this bank
1334 buf << "ORG " << Base::HEX4 << info.offset << endl;
1335
1336 // Now consider each byte
1337 uInt32 prev = info.offset, addr = prev + 1;
1338 DisasmType prevType = disasmTypeAbsolute(mySystem.getAccessFlags(prev));
1339 for( ; addr < info.offset + info.size; ++addr)
1340 {
1341 DisasmType currType = disasmTypeAbsolute(mySystem.getAccessFlags(addr));
1342
1343 // Have we changed to a new type?
1344 if(currType != prevType)
1345 {
1346 disasmTypeAsString(buf, prevType);
1347 buf << " " << Base::HEX4 << prev << " " << Base::HEX4 << (addr-1) << endl;
1348
1349 prev = addr;
1350 prevType = currType;
1351 }
1352 }
1353
1354 // Grab the last directive, making sure it accounts for all remaining space
1355 if(prev != addr)
1356 {
1357 disasmTypeAsString(buf, prevType);
1358 buf << " " << Base::HEX4 << prev << " " << Base::HEX4 << (addr-1) << endl;
1359 }
1360}
1361
1362// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1363void CartDebug::addressTypeAsString(ostream& buf, uInt16 addr) const
1364{
1365 if(!(addr & 0x1000))
1366 {
1367 buf << DebuggerParser::red("type only defined for cart address space");
1368 return;
1369 }
1370
1371 uInt8 directive = myDisDirectives[addr & 0xFFF] & 0xFC,
1372 debugger = myDebugger.getAccessFlags(addr) & 0xFC,
1373 label = myDisLabels[addr & 0xFFF];
1374
1375 buf << endl << "directive: " << Base::toString(directive, Base::F_2_8) << " ";
1376 disasmTypeAsString(buf, directive);
1377 buf << endl << "emulation: " << Base::toString(debugger, Base::F_2_8) << " ";
1378 disasmTypeAsString(buf, debugger);
1379 buf << endl << "tentative: " << Base::toString(label, Base::F_2_8) << " ";
1380 disasmTypeAsString(buf, label);
1381 buf << endl;
1382}
1383
1384// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1385CartDebug::DisasmType CartDebug::disasmTypeAbsolute(uInt8 flags) const
1386{
1387 if(flags & CartDebug::CODE)
1388 return CartDebug::CODE;
1389 else if(flags & CartDebug::TCODE)
1390 return CartDebug::CODE; // TODO - should this be separate??
1391 else if(flags & CartDebug::GFX)
1392 return CartDebug::GFX;
1393 else if(flags & CartDebug::PGFX)
1394 return CartDebug::PGFX;
1395 else if(flags & CartDebug::DATA)
1396 return CartDebug::DATA;
1397 else if(flags & CartDebug::ROW)
1398 return CartDebug::ROW;
1399 else
1400 return CartDebug::NONE;
1401}
1402
1403// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1404void CartDebug::disasmTypeAsString(ostream& buf, DisasmType type) const
1405{
1406 switch(type)
1407 {
1408 case CartDebug::CODE: buf << "CODE"; break;
1409 case CartDebug::TCODE: buf << "TCODE"; break;
1410 case CartDebug::GFX: buf << "GFX"; break;
1411 case CartDebug::PGFX: buf << "PGFX"; break;
1412 case CartDebug::DATA: buf << "DATA"; break;
1413 case CartDebug::ROW: buf << "ROW"; break;
1414 case CartDebug::REFERENCED:
1415 case CartDebug::VALID_ENTRY:
1416 case CartDebug::NONE: break;
1417 }
1418}
1419
1420// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1421void CartDebug::disasmTypeAsString(ostream& buf, uInt8 flags) const
1422{
1423 if(flags)
1424 {
1425 if(flags & CartDebug::CODE)
1426 buf << "CODE ";
1427 if(flags & CartDebug::TCODE)
1428 buf << "TCODE ";
1429 if(flags & CartDebug::GFX)
1430 buf << "GFX ";
1431 if(flags & CartDebug::PGFX)
1432 buf << "PGFX ";
1433 if(flags & CartDebug::DATA)
1434 buf << "DATA ";
1435 if(flags & CartDebug::ROW)
1436 buf << "ROW ";
1437 if(flags & CartDebug::REFERENCED)
1438 buf << "*REFERENCED ";
1439 if(flags & CartDebug::VALID_ENTRY)
1440 buf << "*VALID_ENTRY ";
1441 }
1442 else
1443 buf << "no flags set";
1444}
1445
1446// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1447const char* const CartDebug::ourTIAMnemonicR[16] = {
1448 "CXM0P", "CXM1P", "CXP0FB", "CXP1FB", "CXM0FB", "CXM1FB", "CXBLPF", "CXPPMM",
1449 "INPT0", "INPT1", "INPT2", "INPT3", "INPT4", "INPT5", "$1e", "$1f"
1450};
1451
1452// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1453const char* const CartDebug::ourTIAMnemonicW[64] = {
1454 "VSYNC", "VBLANK", "WSYNC", "RSYNC", "NUSIZ0", "NUSIZ1", "COLUP0", "COLUP1",
1455 "COLUPF", "COLUBK", "CTRLPF", "REFP0", "REFP1", "PF0", "PF1", "PF2",
1456 "RESP0", "RESP1", "RESM0", "RESM1", "RESBL", "AUDC0", "AUDC1", "AUDF0",
1457 "AUDF1", "AUDV0", "AUDV1", "GRP0", "GRP1", "ENAM0", "ENAM1", "ENABL",
1458 "HMP0", "HMP1", "HMM0", "HMM1", "HMBL", "VDELP0", "VDELP1", "VDELBL",
1459 "RESMP0", "RESMP1", "HMOVE", "HMCLR", "CXCLR", "$2d", "$2e", "$2f",
1460 "$30", "$31", "$32", "$33", "$34", "$35", "$36", "$37",
1461 "$38", "$39", "$3a", "$3b", "$3c", "$3d", "$3e", "$3f"
1462};
1463
1464// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1465const char* const CartDebug::ourIOMnemonic[24] = {
1466 "SWCHA", "SWACNT", "SWCHB", "SWBCNT", "INTIM", "TIMINT",
1467 "$286", "$287", "$288", "$289", "$28a", "$28b", "$28c",
1468 "$28d", "$28e", "$28f", "$290", "$291", "$292", "$293",
1469 "TIM1T", "TIM8T", "TIM64T", "T1024T"
1470};
1471
1472// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1473const char* const CartDebug::ourZPMnemonic[128] = {
1474 "ram_80", "ram_81", "ram_82", "ram_83", "ram_84", "ram_85", "ram_86", "ram_87",
1475 "ram_88", "ram_89", "ram_8A", "ram_8B", "ram_8C", "ram_8D", "ram_8E", "ram_8F",
1476 "ram_90", "ram_91", "ram_92", "ram_93", "ram_94", "ram_95", "ram_96", "ram_97",
1477 "ram_98", "ram_99", "ram_9A", "ram_9B", "ram_9C", "ram_9D", "ram_9E", "ram_9F",
1478 "ram_A0", "ram_A1", "ram_A2", "ram_A3", "ram_A4", "ram_A5", "ram_A6", "ram_A7",
1479 "ram_A8", "ram_A9", "ram_AA", "ram_AB", "ram_AC", "ram_AD", "ram_AE", "ram_AF",
1480 "ram_B0", "ram_B1", "ram_B2", "ram_B3", "ram_B4", "ram_B5", "ram_B6", "ram_B7",
1481 "ram_B8", "ram_B9", "ram_BA", "ram_BB", "ram_BC", "ram_BD", "ram_BE", "ram_BF",
1482 "ram_C0", "ram_C1", "ram_C2", "ram_C3", "ram_C4", "ram_C5", "ram_C6", "ram_C7",
1483 "ram_C8", "ram_C9", "ram_CA", "ram_CB", "ram_CC", "ram_CD", "ram_CE", "ram_CF",
1484 "ram_D0", "ram_D1", "ram_D2", "ram_D3", "ram_D4", "ram_D5", "ram_D6", "ram_D7",
1485 "ram_D8", "ram_D9", "ram_DA", "ram_DB", "ram_DC", "ram_DD", "ram_DE", "ram_DF",
1486 "ram_E0", "ram_E1", "ram_E2", "ram_E3", "ram_E4", "ram_E5", "ram_E6", "ram_E7",
1487 "ram_E8", "ram_E9", "ram_EA", "ram_EB", "ram_EC", "ram_ED", "ram_EE", "ram_EF",
1488 "ram_F0", "ram_F1", "ram_F2", "ram_F3", "ram_F4", "ram_F5", "ram_F6", "ram_F7",
1489 "ram_F8", "ram_F9", "ram_FA", "ram_FB", "ram_FC", "ram_FD", "ram_FE", "ram_FF"
1490};
1491