ciderpress/reformat/Disasm.cpp

1448 lines
44 KiB
C++

/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* Disassemble Apple II binaries.
*/
#include "StdAfx.h"
#include "Disasm.h"
/*
* ===========================================================================
* Common stuff for 65xxx-series CPU
* ===========================================================================
*/
/*
* The color scheme from Don Lancaster's "Enhancing your Apple II, Volume 1,
* Second Edition":
* (1) Paint all subroutine returns (RTS) green (entire line to the right
* of the 4-character address field).
* (2) Paint all subroutine calls (JSR) orange (with an extended swipe for
* out-of-range calls, e.g. JSR $FDED). Paint the target address of
* the JSR orange as well. Write the name of the target in brown felt
* tip after the orange arrow, e.g. "COUT (output char)".
* (3) Paint all absolute jumps pink. Highlight across the entire line
* (everything but the address). Paint the address field of the target
* location pink.
* (4) Show branches in blue, with lines down the left-hand side.
* (5) Paint constants green ("lda #$74") and variables pink ("sta $38"),
* highlighting only the operation (i.e. not the hex values in the
* middle of the listing).
* (6) Paint the housekeeping yellow (inx, dex, dey, txa, cld, sec, tsx).
* Draw yellow lines between pha/pla pairs.
*/
/*
* Return the #of bytes required by the specified addressing mode, given a
* particular CPU and values for the 'e', 'm', and 'x' flags.
*
* Width is always > 0. This guarantees that we don't loop forever even if
* we hit stuff we don't recognize.
*/
int ReformatDisasm65xxx::GetOpWidth(OpCode opCode, AddrMode addrMode, CPU cpu,
bool emul, bool shortM, bool shortX)
{
int width = 0;
switch (addrMode) {
case kAddrAcc:
case kAddrImplied:
case kAddrStackPull:
case kAddrStackPush:
case kAddrStackRTI:
case kAddrStackRTL:
case kAddrStackRTS:
width = 1;
break;
case kAddrDP:
case kAddrDPIndexX:
case kAddrDPIndexY:
case kAddrDPIndexXInd:
case kAddrDPInd:
case kAddrDPIndLong:
case kAddrDPIndIndexY:
case kAddrDPIndIndexYLong:
case kAddrPCRel:
case kAddrStackRel:
case kAddrStackRelIndexY:
case kAddrStackDPInd:
width = 2;
break;
case kAddrAbs:
case kAddrAbsIndexX:
case kAddrAbsIndexY:
case kAddrAbsIndexXInd:
case kAddrAbsInd:
case kAddrAbsIndLong:
case kAddrBlockMove:
case kAddrPCRelLong:
case kAddrStackAbs:
case kAddrStackPCRel:
width = 3;
break;
case kAddrAbsLong:
case kAddrAbsIndexXLong:
width = 4;
break;
case kAddrStackInt: // BRK/COP
if (emul || fOneByteBrkCop)
width = 1;
else
width = 2;
break;
case kAddrImm:
width = 2;
if (!emul) {
if (opCode == kOpCPX ||
opCode == kOpCPY ||
opCode == kOpLDX ||
opCode == kOpLDY)
{
if (!shortX)
width = 3;
} else
if (opCode == kOpADC ||
opCode == kOpAND ||
opCode == kOpBIT ||
opCode == kOpCMP ||
opCode == kOpEOR ||
opCode == kOpLDA ||
opCode == kOpORA ||
opCode == kOpSBC)
{
if (!shortM)
width = 3;
} else
if (opCode == kOpREP ||
opCode == kOpSEP)
{
/* keep width = 2 */
} else {
assert(false);
}
}
break;
case kAddrWDM: // not really defined
width = 2;
break;
case kAddrModeUnknown: // unknown opcode
width = 1;
break;
default:
assert(false);
width = 1;
break;
}
assert(width > 0);
return width;
}
/*
* Returns "true" if it looks like we're pointing at a ProDOS 8 MLI call.
*
* The caller is expected to check to see if we're in bank 0.
*/
bool ReformatDisasm65xxx::IsP8Call(const uint8_t* srcBuf, long srcLen)
{
if (srcLen >= 6 &&
srcBuf[0] == 0x20 && srcBuf[1] == 0x00 && srcBuf[2] == 0xbf)
{
return true;
}
return false;
}
/*
* Returns "true" if it looks like we're pointing at a IIgs toolbox call.
*/
bool ReformatDisasm65xxx::IsToolboxCall(const uint8_t* srcBuf, long srcLen,
long backState)
{
if (srcLen >= 4 && backState >= 3 &&
srcBuf[0] == 0x22 && srcBuf[1] == 0x00 && srcBuf[2] == 0x00 &&
srcBuf[3] == 0xe1 && srcBuf[-3] == 0xa2) // LDX #xxxx
{
return true;
}
return false;
}
/*
* Returns "true" if it looks like we're pointing at an inline GS/OS call.
*/
bool ReformatDisasm65xxx::IsInlineGSOS(const uint8_t* srcBuf, long srcLen)
{
if (srcLen >= 10 &&
srcBuf[0] == 0x22 && srcBuf[1] == 0xa8 && srcBuf[2] == 0x00 &&
srcBuf[3] == 0xe1)
{
return true;
}
return false;
}
/*
* Returns "true" if it looks like we're pointing at a stack GS/OS call.
*/
bool ReformatDisasm65xxx::IsStackGSOS(const uint8_t* srcBuf, long srcLen,
long backState)
{
if (srcLen >= 4 && backState >= 3 &&
srcBuf[0] == 0x22 && srcBuf[1] == 0xb0 && srcBuf[2] == 0x00 &&
srcBuf[3] == 0xe1 && srcBuf[-3] == 0xf4) // PEA xxxx
{
return true;
}
return false;
}
/*
* Output one or more lines of text similar to what the monitor would output
* on an 8-bit Apple II.
*
* Returns the number of bytes consumed.
*/
int ReformatDisasm65xxx::OutputMonitor8(const uint8_t* srcBuf, long srcLen,
long backState, uint16_t addr)
{
const CPU kCPU = kCPU65C02; // 6502 or 65C02
OpCode opCode;
AddrMode addrMode;
int bytesUsed;
int opAndAddr;
opAndAddr = kOpMap[*srcBuf].opAndAddr[kCPU];
opCode = (OpCode) (opAndAddr & 0xff);
assert(opCode >= kOpCodeUnknown && opCode < kOpCodeMAX);
addrMode = (AddrMode) (opAndAddr >> 8);
assert(addrMode >= kAddrModeUnknown && addrMode < kAddrModeMAX);
bytesUsed = GetOpWidth(opCode, addrMode, kCPU, true, true, true);
if (IsP8Call(srcBuf, srcLen)) {
/* print and skip P8 inline call stuff */
const char* callName;
callName = NiftyList::LookupP8MLI(srcBuf[3]);
if (callName == NULL)
callName = "(Unknown P8 MLI)";
PrintMonitor8Line(opCode, addrMode, addr, srcBuf, bytesUsed, callName);
BufPrintf("%04X- %02X $%02X",
addr+3, srcBuf[3], srcBuf[3]);
RTFNewPara();
BufPrintf("%04X- %02X %02X $%02X%02X",
addr+4, srcBuf[4], srcBuf[5], srcBuf[5], srcBuf[4]);
RTFNewPara();
bytesUsed += 3;
} else
if (srcLen < bytesUsed) {
assert(bytesUsed <= kMaxByteConsumption);
uint8_t tmpBuf[kMaxByteConsumption];
memset(tmpBuf, 0, kMaxByteConsumption);
memcpy(tmpBuf, srcBuf, srcLen);
PrintMonitor8Line(opCode, addrMode, addr, tmpBuf, bytesUsed, NULL);
} else {
PrintMonitor8Line(opCode, addrMode, addr, srcBuf, bytesUsed, NULL);
}
return bytesUsed;
}
/*
* Output one line of 8-bit monitor stuff.
*/
void ReformatDisasm65xxx::PrintMonitor8Line(OpCode opCode, AddrMode addrMode,
uint16_t addr, const uint8_t* srcBuf, long srcLen, const char* comment)
{
char lineBuf[64]; // actual length is about 30 -- does not hold comment
char* cp;
const char* mnemonic = kOpCodeDetails[opCode].mnemonic;
uint8_t byte0, byte1, byte2;
cp = lineBuf;
switch (srcLen) {
case 1:
byte0 = srcBuf[0];
byte1 = byte2 = 0xcc; // make bugs more obvious
cp += sprintf(cp, "%04X- %02X %s", addr, byte0, mnemonic);
break;
case 2:
byte0 = srcBuf[0];
byte1 = srcBuf[1];
byte2 = 0xcc;
cp += sprintf(cp, "%04X- %02X %02X %s", addr, byte0, byte1,
mnemonic);
break;
case 3:
byte0 = srcBuf[0];
byte1 = srcBuf[1];
byte2 = srcBuf[2];
cp += sprintf(cp, "%04X- %02X %02X %02X %s", addr, byte0, byte1,
byte2, mnemonic);
break;
default:
assert(false);
return;
}
switch (addrMode) {
case kAddrImplied:
case kAddrStackPull:
case kAddrStackPush:
case kAddrStackRTI:
case kAddrStackRTS:
break;
case kAddrAcc:
//cp += sprintf(cp, " A");
break;
case kAddrDP:
cp += sprintf(cp, " $%02X", byte1);
break;
case kAddrDPIndexX:
cp += sprintf(cp, " $%02X,X", byte1);
break;
case kAddrDPIndexY:
cp += sprintf(cp, " $%02X,Y", byte1);
break;
case kAddrDPIndexXInd:
cp += sprintf(cp, " ($%02X,X)", byte1);
break;
case kAddrDPInd:
cp += sprintf(cp, " ($%02X)", byte1);
break;
case kAddrDPIndIndexY:
cp += sprintf(cp, " ($%02X),Y", byte1);
break;
case kAddrImm:
cp += sprintf(cp, " #$%02X", byte1);
break;
case kAddrPCRel:
cp += sprintf(cp, " $%04X", RelOffset(addr, byte1));
break;
case kAddrAbs:
cp += sprintf(cp, " $%02X%02X", byte2, byte1);
if (comment == NULL)
comment = NiftyList::Lookup00Addr(byte1 | byte2 << 8);
break;
case kAddrAbsIndexX:
cp += sprintf(cp, " $%02X%02X,X", byte2, byte1);
break;
case kAddrAbsIndexY:
cp += sprintf(cp, " $%02X%02X,Y", byte2, byte1);
break;
case kAddrAbsIndexXInd:
cp += sprintf(cp, " ($%02X%02X,X)", byte2, byte1);
break;
case kAddrAbsInd:
cp += sprintf(cp, " ($%02X%02X)", byte2, byte1);
break;
case kAddrStackInt: // BRK/COP
if (srcLen != 1)
cp += sprintf(cp, " $%02X", byte1);
break;
case kAddrAbsIndLong: // JML
case kAddrAbsLong:
case kAddrAbsIndexXLong:
case kAddrBlockMove:
case kAddrDPIndLong:
case kAddrDPIndIndexYLong:
case kAddrPCRelLong: // BRL
case kAddrStackAbs: // PEA
case kAddrStackDPInd: // PEI
case kAddrStackPCRel: // PER
case kAddrStackRTL:
case kAddrStackRel:
case kAddrStackRelIndexY:
case kAddrWDM:
// not for 8-bit mode
assert(false);
break;
case kAddrModeUnknown:
assert(srcLen == 1);
break;
default:
assert(false);
break;
}
assert(strlen(cp)+1 < sizeof(lineBuf));
if (comment == NULL)
BufPrintf("%s", lineBuf);
else
BufPrintf("%s %s", lineBuf, comment);
RTFNewPara();
}
/*
* Output one or more lines of text similar to what the monitor would output
* on an 8-bit Apple II.
*
* Returns the number of bytes consumed.
*/
int ReformatDisasm65xxx::OutputMonitor16(const uint8_t* srcBuf, long srcLen,
long backState, uint32_t addr, bool shortRegs)
{
const CPU kCPU = kCPU65816;
OpCode opCode;
AddrMode addrMode;
int bytesUsed;
int opAndAddr;
const char* callName;
opAndAddr = kOpMap[*srcBuf].opAndAddr[kCPU];
opCode = (OpCode) (opAndAddr & 0xff);
assert(opCode >= kOpCodeUnknown && opCode < kOpCodeMAX);
addrMode = (AddrMode) (opAndAddr >> 8);
assert(addrMode >= kAddrModeUnknown && addrMode < kAddrModeMAX);
bytesUsed = GetOpWidth(opCode, addrMode, kCPU, false, shortRegs, shortRegs);
if (Bank(addr) == 0 && IsP8Call(srcBuf, srcLen)) {
/* print and skip P8 inline call stuff */
callName = NiftyList::LookupP8MLI(srcBuf[3]);
if (callName == NULL)
callName = "(Unknown P8 MLI)";
PrintMonitor16Line(opCode, addrMode, addr, srcBuf, bytesUsed, callName);
BufPrintf("00/%04X: %02X %02X",
addr+3, srcBuf[3], srcBuf[3]);
RTFNewPara();
BufPrintf("00/%04X: %02X %02X %02X%02X",
addr+4, srcBuf[4], srcBuf[5], srcBuf[5], srcBuf[4]);
RTFNewPara();
bytesUsed += 3;
} else
if (IsToolboxCall(srcBuf, srcLen, backState)) {
callName = NiftyList::LookupToolbox(srcBuf[-2] | srcBuf[-1] << 8);
PrintMonitor16Line(opCode, addrMode, addr, srcBuf, bytesUsed, callName);
} else
if (IsInlineGSOS(srcBuf, srcLen)) {
callName = NiftyList::LookupGSOS(srcBuf[4] | srcBuf[5] << 8);
PrintMonitor16Line(opCode, addrMode, addr, srcBuf, bytesUsed, callName);
BufPrintf("%02X/%04X: %02X %02X %02X%02X",
Bank(addr), Offset(addr+4), srcBuf[4], srcBuf[5],
srcBuf[5], srcBuf[4]);
RTFNewPara();
BufPrintf("%02X/%04X: %02X %02X %02X %02X %02X%02X%02X%02X",
Bank(addr), Offset(addr+6),
srcBuf[6], srcBuf[7], srcBuf[8], srcBuf[9],
srcBuf[9], srcBuf[8], srcBuf[7], srcBuf[6]);
RTFNewPara();
bytesUsed += 6;
} else
if (IsStackGSOS(srcBuf, srcLen, backState)) {
callName = NiftyList::LookupGSOS(srcBuf[-2] | srcBuf[-1] << 8);
PrintMonitor16Line(opCode, addrMode, addr, srcBuf, bytesUsed, callName);
} else
if (srcLen < bytesUsed) {
assert(bytesUsed <= kMaxByteConsumption);
uint8_t tmpBuf[kMaxByteConsumption];
memset(tmpBuf, 0, kMaxByteConsumption);
memcpy(tmpBuf, srcBuf, srcLen);
PrintMonitor16Line(opCode, addrMode, addr, tmpBuf, bytesUsed, NULL);
} else {
PrintMonitor16Line(opCode, addrMode, addr, srcBuf, bytesUsed, NULL);
}
return bytesUsed;
}
/*
* Output one line of 16-bit monitor stuff.
*/
void ReformatDisasm65xxx::PrintMonitor16Line(OpCode opCode, AddrMode addrMode,
uint32_t addr, const uint8_t* srcBuf, long srcLen, const char* comment)
{
char lineBuf[64]; // actual length is about 30 -- does not hold comment
char* cp;
const char* mnemonic = kOpCodeDetails[opCode].mnemonic;
uint8_t byte0, byte1, byte2, byte3;
int16_t offset;
cp = lineBuf;
cp += sprintf(cp, "%02X/%04X: ", addr >> 16, addr & 0xffff);
switch (srcLen) {
case 1:
byte0 = srcBuf[0];
byte1 = byte2 = byte3 = 0xcc; // make bugs more obvious
cp += sprintf(cp, "%02X %s", byte0, mnemonic);
break;
case 2:
byte0 = srcBuf[0];
byte1 = srcBuf[1];
byte2 = byte3 = 0xcc;
cp += sprintf(cp, "%02X %02X %s", byte0, byte1, mnemonic);
break;
case 3:
byte0 = srcBuf[0];
byte1 = srcBuf[1];
byte2 = srcBuf[2];
byte3 = 0xcc;
cp += sprintf(cp, "%02X %02X %02X %s", byte0, byte1, byte2,
mnemonic);
break;
case 4:
byte0 = srcBuf[0];
byte1 = srcBuf[1];
byte2 = srcBuf[2];
byte3 = srcBuf[3];
cp += sprintf(cp, "%02X %02X %02X %02X %s", byte0, byte1, byte2,
byte3, mnemonic);
break;
default:
assert(false);
return;
}
switch (addrMode) {
case kAddrImplied:
case kAddrStackPull:
case kAddrStackPush:
case kAddrStackRTI:
case kAddrStackRTS:
case kAddrStackRTL:
break;
case kAddrAcc:
//cp += sprintf(cp, " A");
break;
case kAddrDP:
cp += sprintf(cp, " %02X", byte1);
break;
case kAddrDPIndexX:
cp += sprintf(cp, " %02X,X", byte1);
break;
case kAddrDPIndexY:
cp += sprintf(cp, " %02X,Y", byte1);
break;
case kAddrDPIndexXInd:
cp += sprintf(cp, " (%02X,X)", byte1);
break;
case kAddrDPInd:
cp += sprintf(cp, " (%02X)", byte1);
break;
case kAddrDPIndIndexY:
cp += sprintf(cp, " (%02X),Y", byte1);
break;
case kAddrImm:
if (srcLen == 2)
cp += sprintf(cp, " #%02X", byte1);
else
cp += sprintf(cp, " #%02X%02X", byte2, byte1);
break;
case kAddrPCRel:
offset = (char) byte1;
if (offset < 0)
cp += sprintf(cp, " %04X {-%02X}",
RelOffset((uint16_t) addr, byte1), -offset);
else
cp += sprintf(cp, " %04X {+%02X}",
RelOffset((uint16_t) addr, byte1), offset);
break;
case kAddrAbs:
cp += sprintf(cp, " %02X%02X", byte2, byte1);
if (comment == NULL && Bank(addr) == 0)
comment = NiftyList::Lookup00Addr(byte1 | byte2 << 8);
break;
case kAddrAbsIndexX:
cp += sprintf(cp, " %02X%02X,X", byte2, byte1);
break;
case kAddrAbsIndexY:
cp += sprintf(cp, " %02X%02X,Y", byte2, byte1);
break;
case kAddrAbsIndexXInd:
cp += sprintf(cp, " (%02X%02X,X)", byte2, byte1);
break;
case kAddrAbsInd:
cp += sprintf(cp, " (%02X%02X)", byte2, byte1);
break;
case kAddrWDM:
case kAddrStackInt: // BRK/COP
if (srcLen != 1)
cp += sprintf(cp, " %02X", byte1);
break;
case kAddrAbsIndLong: // JML
cp += sprintf(cp, " (%02X%02X)", byte2, byte1);
break;
case kAddrAbsLong:
cp += sprintf(cp, " %02X%02X%02X", byte3, byte2, byte1);
if (comment == NULL) {
if (byte3 == 0x00)
comment = NiftyList::Lookup00Addr(byte1 | byte2 << 8);
else if (byte3 == 0x01)
comment = NiftyList::Lookup01Vector(byte1 | byte2 << 8);
else if (byte3 == 0xe0)
comment = NiftyList::LookupE0Vector(byte1 | byte2 << 8);
else if (byte3 == 0xe1)
comment = NiftyList::LookupE1Vector(byte1 | byte2 << 8);
}
break;
case kAddrAbsIndexXLong:
cp += sprintf(cp, " %02X%02X%02X,X", byte3, byte2, byte1);
break;
case kAddrBlockMove:
cp += sprintf(cp, " %02X%02X", byte2, byte1);
break;
case kAddrDPIndLong:
cp += sprintf(cp, " [%02X]", byte1);
break;
case kAddrDPIndIndexYLong:
cp += sprintf(cp, " [%02X],Y", byte1);
break;
case kAddrStackPCRel: // PER
case kAddrPCRelLong: // BRL
offset = (short) (byte1 | byte2 << 8);
if (offset < 0)
cp += sprintf(cp, " %04X {-%02X}",
RelLongOffset((uint16_t) addr, offset), -offset);
else
cp += sprintf(cp, " %04X {+%02X}",
RelLongOffset((uint16_t) addr, offset), offset);
break;
case kAddrStackAbs: // PEA
cp += sprintf(cp, " %02X%02X", byte2, byte1);
break;
case kAddrStackDPInd: // PEI
cp += sprintf(cp, " %02X", byte1);
break;
case kAddrStackRel:
cp += sprintf(cp, " %02X,S", byte1);
break;
case kAddrStackRelIndexY:
cp += sprintf(cp, " (%02X,S),Y", byte1);
break;
case kAddrModeUnknown:
assert(srcLen == 1);
break;
default:
assert(false);
break;
}
assert(strlen(cp)+1 < sizeof(lineBuf));
if (comment == NULL)
BufPrintf("%s", lineBuf);
else {
if (srcLen < 4)
BufPrintf("%s %s", lineBuf, comment);
else
BufPrintf("%s %s", lineBuf, comment);
}
RTFNewPara();
}
/*
* ===========================================================================
* Disassemble 8-bit code
* ===========================================================================
*/
/*
* For ProDOS 8 and DOS 3.3 stuff, we can't say for sure whether a "BIN"
* file is code or, say, a hi-res graphic. We use "maybe" level to put
* disassembly below other formats.
*/
void ReformatDisasm8::Examine(ReformatHolder* pHolder)
{
ReformatHolder::ReformatApplies applies = ReformatHolder::kApplicNot;
int fileType = pHolder->GetFileType();
if (fileType >= 0xf1 && fileType <= 0xf8) {
applies = ReformatHolder::kApplicProbablyNot;
} else if (fileType == 0x00 &&
pHolder->GetSourceFormat() != ReformatHolder::kSourceFormatCPM)
{
applies = ReformatHolder::kApplicProbablyNot;
} else if (fileType == kTypeBIN) {
applies = ReformatHolder::kApplicMaybe;
} else if (fileType == kTypeSYS || fileType == kTypeCMD ||
fileType == kType8OB || fileType == kTypeP8C)
{
applies = ReformatHolder::kApplicYes;
}
pHolder->SetApplic(ReformatHolder::kReformatMonitor8, applies,
ReformatHolder::kApplicNot, ReformatHolder::kApplicNot);
// not doing this yet
pHolder->SetApplic(ReformatHolder::kReformatDisasmMerlin8,
ReformatHolder::kApplicNot,
ReformatHolder::kApplicNot, ReformatHolder::kApplicNot);
}
/*
* Create a monitor listing or disassembly of a file.
*/
int ReformatDisasm8::Process(const ReformatHolder* pHolder,
ReformatHolder::ReformatID id, ReformatHolder::ReformatPart part,
ReformatOutput* pOutput)
{
const uint8_t* srcBuf = pHolder->GetSourceBuf(part);
long srcLen = pHolder->GetSourceLen(part);
long fileType = pHolder->GetFileType();
long backState = 0;
uint16_t addr;
fUseRTF = false;
if (!srcLen)
return -1;
RTFBegin();
/* pick a nice 16-bit start address */
if (fileType == kTypeSYS || fileType == kTypeP8C || fileType == kType8OB)
addr = 0x2000;
else if (fileType == kTypeBIN || fileType == kTypeCMD)
addr = (uint16_t) pHolder->GetAuxType();
else
addr = 0x0000;
while (srcLen > 0) {
int consumed;
consumed = OutputMonitor8(srcBuf, srcLen, backState, addr);
srcBuf += consumed;
srcLen -= consumed;
backState += consumed;
addr += consumed;
}
RTFEnd();
SetResultBuffer(pOutput);
return 0;
}
/*
* ===========================================================================
* Disassemble 16-bit code
* ===========================================================================
*/
/*
* For 16-bit stuff, the file types are always explicit. However, there are
* instances of 65802/65816 code being written for 8-bit platforms, so we
* want to keep 16-bit disassembly available but prioritized below "raw" for
* BIN files.
*/
void ReformatDisasm16::Examine(ReformatHolder* pHolder)
{
ReformatHolder::ReformatApplies applies = ReformatHolder::kApplicNot;
long fileType = pHolder->GetFileType();
if (fileType == kTypeBIN || fileType == kTypeSYS || fileType == kTypeCMD ||
fileType == kType8OB || fileType == kTypeP8C)
{
applies = ReformatHolder::kApplicProbablyNot;
} else if (fileType == 0x00 &&
pHolder->GetSourceFormat() != ReformatHolder::kSourceFormatCPM)
{
applies = ReformatHolder::kApplicProbablyNot;
} else {
/*
* Interesting file types (GS/OS ref table F-1):
* $B1 OBJ Object
* $B2 LIB Library
* $B3 S16 GS/OS or ProDOS 16 app
* $B4 RTL Run-time library
* $B5 EXE Shell application
* $B6 PIF Permanent initialization
* $B7 TIF Temporary initialization
* $B8 NDA New desk accessory
* $B9 CDA Classic desk accessory
* $BA TOL Tool set file
* $BB DVR Apple IIgs device driver
* $BC LDF Generic load file (application-specific)
* $BD FST GS/OS file system translator
*
* We also handle non-OMF files:
* $F9 OS GS/OS System file
*/
if (fileType >= kTypeOBJ && fileType <= kTypeFST)
applies = ReformatHolder::kApplicYes;
else if (fileType == kTypeOS)
applies = ReformatHolder::kApplicYes;
}
pHolder->SetApplic(ReformatHolder::kReformatMonitor16Long, applies,
ReformatHolder::kApplicNot, ReformatHolder::kApplicNot);
pHolder->SetApplic(ReformatHolder::kReformatMonitor16Short, applies,
ReformatHolder::kApplicNot, ReformatHolder::kApplicNot);
pHolder->SetApplicPreferred(ReformatHolder::kReformatMonitor16Long);
// not doing this yet
pHolder->SetApplic(ReformatHolder::kReformatDisasmOrcam16,
ReformatHolder::kApplicNot,
ReformatHolder::kApplicNot, ReformatHolder::kApplicNot);
}
/*
* Disassemble or show monitor listing for 16-bit code.
*/
int ReformatDisasm16::Process(const ReformatHolder* pHolder,
ReformatHolder::ReformatID id, ReformatHolder::ReformatPart part,
ReformatOutput* pOutput)
{
const uint8_t* srcBuf = pHolder->GetSourceBuf(part);
long srcLen = pHolder->GetSourceLen(part);
long fileType = pHolder->GetFileType();
uint32_t addr = 0;
bool shortRegs;
fUseRTF = false;
if (!srcLen)
return -1;
RTFBegin();
if (id == ReformatHolder::kReformatMonitor16Long)
shortRegs = false;
else
shortRegs = true;
fOneByteBrkCop = pHolder->GetOption(ReformatHolder::kOptOneByteBrkCop) != 0;
if (fileType >= kTypeOBJ && fileType <= kTypeFST) {
if (!OutputOMF(srcBuf, srcLen, fileType, shortRegs)) {
/* must not be OMF; do a generic list */
fExpBuf.Reset();
BufPrintf("[ Valid OMF expected but not found ]\r\n");
RTFNewPara();
OutputSection(srcBuf, srcLen, addr, shortRegs);
}
} else {
/* pick a start address in bank 0 */
if (fileType == kTypeSYS)
addr = 0x2000;
else if (fileType == kTypeBIN || fileType == kTypeCMD)
addr = (uint16_t) pHolder->GetAuxType();
OutputSection(srcBuf, srcLen, addr, shortRegs);
}
RTFEnd();
SetResultBuffer(pOutput);
return 0;
}
/*
* Output one section of a file.
*/
void ReformatDisasm16::OutputSection(const uint8_t* srcBuf, long srcLen,
uint32_t addr, bool shortRegs)
{
long backState = 0;
while (srcLen > 0) {
int consumed;
consumed = OutputMonitor16(srcBuf, srcLen, backState, addr, shortRegs);
srcBuf += consumed;
srcLen -= consumed;
backState += consumed;
addr += consumed;
}
}
/*
* Break an OMF file into sections, and output each individually.
*/
bool ReformatDisasm16::OutputOMF(const uint8_t* srcBuf, long srcLen,
long fileType, bool shortRegs)
{
const uint8_t* origBuf = srcBuf;
long origLen = srcLen;
OMFSegmentHeader segHdr;
int segmentNumber = 1;
BufPrintf(";\r\n");
BufPrintf("; OMF segment summary:\r\n");
BufPrintf(";\r\n");
/* pass #1: print a preview */
while (srcLen > 0) {
if (!segHdr.Unpack(srcBuf, srcLen, fileType)) {
if (segmentNumber == 1)
return false;
else {
BufPrintf("; (bad header found, ignoring last %ld bytes)\r\n",
srcLen);
break; // out of while; display what we have
}
}
PrintHeader(&segHdr, segmentNumber, false);
srcBuf += segHdr.GetSegmentLen();
srcLen -= segHdr.GetSegmentLen();
segmentNumber++;
}
BufPrintf(";\r\n");
RTFNewPara();
segmentNumber = 1;
srcBuf = origBuf;
srcLen = origLen;
while (srcLen > 0) {
if (!segHdr.Unpack(srcBuf, srcLen, fileType)) {
BufPrintf("!!!\r\n");
BufPrintf("!!! Found bad OMF header at offset 0x%04x (remaining len=%ld)\r\n",
srcBuf - origBuf, srcLen);
BufPrintf("!!!\r\n");
RTFNewPara();
if (segmentNumber == 1)
return false;
else
return true;
}
PrintHeader(&segHdr, segmentNumber, true);
PrintSegment(&segHdr, srcBuf, srcLen, shortRegs);
RTFNewPara();
srcBuf += segHdr.GetSegmentLen();
srcLen -= segHdr.GetSegmentLen();
segmentNumber++;
}
return true;
}
/*
* Print the interesting bits of the header.
*/
void ReformatDisasm16::PrintHeader(const OMFSegmentHeader* pSegHdr,
int segmentNumber, bool longFmt)
{
//pSegHdr->Dump();
const char* versStr;
switch (pSegHdr->GetVersion()) {
case 0: versStr = "0.0"; break;
case 1: versStr = "1.0"; break;
case 2: versStr = "2.0"; break;
case 0x82: versStr = "2.1"; break;
default: versStr = "(unknown)"; break;
}
const char* typeStr;
switch (pSegHdr->GetSegmentType()) {
case OMFSegmentHeader::kTypeCode: typeStr = "CODE"; break;
case OMFSegmentHeader::kTypeData: typeStr = "DATA"; break;
case OMFSegmentHeader::kTypeJumpTable: typeStr = "JumpTab"; break;
case OMFSegmentHeader::kTypePathName: typeStr = "PathName"; break;
case OMFSegmentHeader::kTypeLibraryDict: typeStr = "LibDict"; break;
case OMFSegmentHeader::kTypeInit: typeStr = "Init"; break;
case OMFSegmentHeader::kTypeAbsoluteBank: typeStr = "AbsBank"; break;
case OMFSegmentHeader::kTypeDPStack: typeStr = "DP/Stack"; break;
default: typeStr = "(unknown)"; break;
}
if (longFmt) {
BufPrintf(";\r\n");
BufPrintf("; Segment #%d (%d): loadName='%s' segName='%s': \r\n",
segmentNumber, pSegHdr->GetSegNum(),
pSegHdr->GetLoadName(), pSegHdr->GetSegName());
BufPrintf("; type=%s length=%ld OMF v%s\r\n",
typeStr, pSegHdr->GetSegmentLen(), versStr);
BufPrintf("; flags:%s%s%s%s%s%s%s%s\r\n",
pSegHdr->GetSegmentFlag(OMFSegmentHeader::kFlagBankRelative) ? " bankRel" : "",
pSegHdr->GetSegmentFlag(OMFSegmentHeader::kFlagSkip) ? " skip" : "",
pSegHdr->GetSegmentFlag(OMFSegmentHeader::kFlagReload) ? " reload" : "",
pSegHdr->GetSegmentFlag(OMFSegmentHeader::kFlagAbsoluteBank) ? " absBank" : "",
pSegHdr->GetSegmentFlag(OMFSegmentHeader::kFlagNoSpecialMem) ? " noSpecial" : "",
pSegHdr->GetSegmentFlag(OMFSegmentHeader::kFlagPositionIndep) ? " posnIndep" : "",
pSegHdr->GetSegmentFlag(OMFSegmentHeader::kFlagPrivate) ? " private" : "",
pSegHdr->GetSegmentFlag(OMFSegmentHeader::kFlagDynamic) ? " dynamic" : "");
BufPrintf(";");
RTFNewPara();
} else {
BufPrintf("; #%02d: %-8s len=0x%06x loadName='%s' segName='%s'\r\n",
segmentNumber, typeStr, pSegHdr->GetSegmentLen(),
pSegHdr->GetLoadName(), pSegHdr->GetSegName());
}
}
/*
* Print the contents of the segment.
*/
void ReformatDisasm16::PrintSegment(const OMFSegmentHeader* pSegHdr,
const uint8_t* srcBuf, long srcLen, bool shortRegs)
{
uint32_t subLen;
int offset = 0;
assert(pSegHdr != NULL);
assert(srcBuf != NULL);
assert(srcLen > 0);
srcBuf += pSegHdr->GetDispData();
srcLen -= pSegHdr->GetDispData();
if (srcLen < 0) {
BufPrintf("GLITCH: ran out of data\r\n");
return;
}
if (*srcBuf == 0xf2 && srcLen >= 5) {
/* handle kSegOpLCONST just for fun */
subLen = Get32LE(srcBuf+1);
BufPrintf("OMF: LCONST record (0x%04x bytes follow)\r\n", subLen);
offset = 5;
}
OutputSection(srcBuf + offset,
pSegHdr->GetSegmentLen() - pSegHdr->GetDispData() - offset,
0x000000, shortRegs);
#if 0
OMFSegment seg;
seg.Setup(pSegHdr, srcBuf, srcLen);
const uint8_t* ptr;
do {
ptr = seg.ProcessNextChunk();
if (ptr == NULL) {
BufPrintf("!!! bogus OMF values encountered\r\n");
return;
}
switch (*ptr) {
case OMFSegment::kSegOpEND:
BufPrintf("OMF: END (bytesLeft=%d)\r\n", (ptr+1) - srcBuf);
break;
}
} while (*ptr != OMFSegment::kSegOpEND);
#endif
}
/*
* ===========================================================================
* OMFSegmentHeader
* ===========================================================================
*/
/*
* Unpack an OMF segment header from the current offset.
*
* Returns "true" on success, "false" on failure.
*/
bool OMFSegmentHeader::Unpack(const uint8_t* srcBuf, long srcLen, int fileType)
{
if (srcLen < kHdrMinSize) {
LOGI("OMF: Too short to be segment (%ld)", srcLen);
return false;
}
fVersion = *(srcBuf + 0x0f);
if (fVersion > kMaxVersion) {
LOGI("OMF: Wrong version number to be OMF (%d)", fVersion);
return false;
}
fRevision = 0;
if (fVersion == 0) {
/* unpack OMF v0.0 (not actually used for 16-bit code?) */
if (srcLen < kV0HdrMinSize)
return false;
fBlockCnt = Get32LE(srcBuf + 0x00);
fResSpc = Get32LE(srcBuf + 0x04);
fLength = Get32LE(srcBuf + 0x08);
fType = *(srcBuf + 0x0c);
fLabLen = *(srcBuf + 0x0d);
fNumLen = *(srcBuf + 0x0e);
fBankSize = Get32LE(srcBuf + 0x10);
fOrg = Get32LE(srcBuf + 0x14);
fAlign = Get32LE(srcBuf + 0x18);
fNumSex = *(srcBuf + 0x1c);
fByteCnt = fBlockCnt * 512;
fKind = 0;
fLCBank = 0;
fSegNum = 0;
fEntry = 0;
fTempOrg = 0;
fDispName = 0x24;
if (fLabLen == 0)
fDispData = fDispName + *(srcBuf + fDispName);
else
fDispData = fDispName + fLabLen;
} else if (fVersion == 1) {
/* unpack OMF v1.0 */
if (srcLen < kV1HdrMinSize)
return false;
fBlockCnt = Get32LE(srcBuf + 0x00);
fResSpc = Get32LE(srcBuf + 0x04);
fLength = Get32LE(srcBuf + 0x08);
fType = *(srcBuf + 0x0c);
fLabLen = *(srcBuf + 0x0d);
fNumLen = *(srcBuf + 0x0e);
fBankSize = Get32LE(srcBuf + 0x10);
// unused32 at 0x14
fOrg = Get32LE(srcBuf + 0x18);
fAlign = Get32LE(srcBuf + 0x1c);
fNumSex = *(srcBuf + 0x20);
fLCBank = *(srcBuf + 0x21);
fSegNum = Get16LE(srcBuf + 0x22);
fEntry = Get32LE(srcBuf + 0x24);
fDispName = Get16LE(srcBuf + 0x28);
fDispData = Get16LE(srcBuf + 0x2a);
/*
* Orca/APW libs seem to use version 1 with a byte count in the
* first field, but the "LLRE" app has version 1 with a block count.
* The spec is pretty clear, so it looks like somebody's library
* builder screwed up.
*
* Special case type=LIB ($B2).
*/
if (fileType == 0xb2) {
LOGI("NOTE: switching blockCount=%ld to byte count", fBlockCnt);
fByteCnt = fBlockCnt;
fBlockCnt = 0;
} else {
fByteCnt = fBlockCnt * 512;
}
fKind = 0;
fTempOrg = 0;
} else {
/* unpack OMF v2.x */
if (srcLen < kV2HdrMinSize)
return false;
fByteCnt = Get32LE(srcBuf + 0x00);
fResSpc = Get32LE(srcBuf + 0x04);
fLength = Get32LE(srcBuf + 0x08);
// unused at +0x0c
fLabLen = *(srcBuf + 0x0d);
fNumLen = *(srcBuf + 0x0e);
fBankSize = Get32LE(srcBuf + 0x10);
fKind = Get16LE(srcBuf + 0x14);
// unused at +0x16
fOrg = Get32LE(srcBuf + 0x18);
fAlign = Get32LE(srcBuf + 0x1c);
fNumSex = *(srcBuf + 0x20);
fSegNum = Get16LE(srcBuf + 0x22);
// unused at +0x23
fEntry = Get32LE(srcBuf + 0x24);
fDispName = Get16LE(srcBuf + 0x28);
fDispData = Get16LE(srcBuf + 0x2a);
if (fDispName > 0x2c) {
// v2.1 adds a 4-byte "tempOrg" field
fTempOrg = Get32LE(srcBuf + 0x2c);
fRevision = 1;
} else {
fTempOrg = 0;
}
fBlockCnt = 0;
fType = 0;
fLCBank = 0;
}
/* validate fields */
if (fByteCnt < kHdrMinSize || fByteCnt > (uint32_t) srcLen) {
LOGI("OMF: Bad value for byteCnt (%ld, srcLen=%ld min=%d)",
fByteCnt, srcLen, kHdrMinSize);
return false;
}
if (fDispName < 0x24 || fDispName > (srcLen - kLoadNameLen)) {
LOGI("OMF: Bad value for dispName (%d, srcLen=%ld)",
fDispName, srcLen);
return false;
}
if (fDispData < 0x24 || fDispData > srcLen) {
LOGI("OMF: Bad value for dispData (%d, srcLen=%ld)",
fDispData, srcLen);
return false;
}
if (fDispData < fDispName + kLoadNameLen) {
LOGI("OMF: dispData is inside label region (%d / %d)",
fDispData, fDispName);
return false;
}
if (fBankSize != kExpectedBankSize && fBankSize != 0) {
LOGI("OMF: NOTE: bankSize=%ld", fBankSize);
/* allowed, just weird */
}
if (fNumLen != kExpectedNumLen || fNumSex != 0) {
LOGI("OMF: WARNING: numLen=%d numSex=%d", fNumLen, fNumSex);
/* big endian odd-sized numbers?? keep going, I guess */
}
const uint8_t* segName;
int segLabelLen;
/* copy the label entries over */
segName = srcBuf + fDispName;
if (fVersion > 0) {
memcpy(fLoadName, srcBuf + fDispName, kLoadNameLen);
fLoadName[kLoadNameLen] = '\0';
segName += kLoadNameLen;
} else {
fLoadName[0] = '\0';
}
if (fLabLen == 0) {
/* pascal-style string */
segLabelLen = *segName++;
memcpy(fSegName, segName, segLabelLen);
fSegName[segLabelLen] = '\0';
LOGI(" OMF: Pascal segment label '%hs'", fSegName);
} else {
/* C-style or non-terminated string */
segLabelLen = fLabLen;
memcpy(fSegName, segName, segLabelLen);
fSegName[segLabelLen] = '\0';
LOGI(" OMF: Std segment label '%hs'", fSegName);
}
fReady = true;
return true;
}
/*
* Pry the segment type out of the "type" or "kind" field.
*/
OMFSegmentHeader::SegmentType OMFSegmentHeader::GetSegmentType(void) const
{
assert(fReady);
if (fVersion < 2)
return (SegmentType) (fType & 0x1f);
else
return (SegmentType) (fKind & 0x1f);
}
/*
* Return the value of one of the segment header flags.
*/
bool OMFSegmentHeader::GetSegmentFlag(SegmentFlag flag) const
{
if (fVersion < 2) {
switch (flag) {
case kFlagPositionIndep:
return (fType & 0x20) != 0;
case kFlagPrivate:
return (fType & 0x40) != 0;
case kFlagDynamic:
return (fType & 0x80) != 0;
default:
return false;
}
} else {
switch (flag) {
case kFlagBankRelative:
return (fKind & 0x0100) != 0;
case kFlagSkip:
return (fKind & 0x0200) != 0;
case kFlagReload:
return (fKind & 0x0400) != 0;
case kFlagAbsoluteBank:
return (fKind & 0x0800) != 0;
case kFlagNoSpecialMem:
return (fKind & 0x1000) != 0;
case kFlagPositionIndep:
return (fKind & 0x2000) != 0;
case kFlagPrivate:
return (fKind & 0x4000) != 0;
case kFlagDynamic:
return (fKind & 0x8000) != 0;
default:
assert(false);
return false;
}
}
}
/*
* Dump the contents of the segment header struct.
*/
void OMFSegmentHeader::Dump(void) const
{
LOGI("OMF segment header:");
LOGI(" segNum=%d loadName='%hs' segName='%hs'",
fSegNum, fLoadName, fSegName);
LOGI(" blockCnt=%ld byteCnt=%ld resSpc=%ld length=%ld",
fBlockCnt, fByteCnt, fResSpc, fLength);
LOGI(" version=%d type=0x%02x kind=0x%04x",
fVersion, fType, fKind);
LOGI(" labLen=%d numLen=%d bankSize=%ld lcBank=%d",
fLabLen, fNumLen, fBankSize, fLCBank);
LOGI(" align=%ld numSex=%d org=%ld tempOrg=%ld",
fAlign, fNumSex, fOrg, fTempOrg);
LOGI(" entry=%ld dispName=%d dispData=%d",
fEntry, fDispName, fDispData);
}
#if 0
/*
* ===========================================================================
* OMFSegment
* ===========================================================================
*/
/*
* Prepare to roll through a segment.
*/
void
OMFSegment::Setup(const OMFSegmentHeader* pSegHdr, const uint8_t* srcBuf,
long srcLen)
{
fSegBuf = fCurPtr = srcBuf + pSegHdr->GetDispData();
fSegLen = srcLen - pSegHdr->GetDispData();
fLabLen = pSegHdr->GetLabLen();
fNumLen = pSegHdr->GetNumLen();
}
/*
* Process the next chunk from the segment.
*
* Returns a pointer to the start of the chunk, or "NULL" if we've encountered
* some bogus condition (e.g. running off the end).
*/
const uint8_t*
OMFSegment::ProcessNextChunk(void)
{
const uint8_t* prevPtr = fCurPtr;
long remLen = fSegLen - (fCurPtr - fSegBuf);
uint32_t subLen;
int len = 1; // one byte at least (for the opcode)
assert(fLabLen >= 0);
assert(fNumLen > 0);
switch (*fCurPtr) {
case kSegOpEND:
LOGI(" OMF END reached, remaining len = %d",
fSegLen - (fCurPtr - fSegBuf));
assert(len == 1);
break;
case kSegOpALIGN:
len += fNumLen;
break;
case kSegOpORG:
len += fNumLen;
break;
case kSegOpRELOC:
len += 10;
break;
case kSegOpINTERSEG:
len += 14;
break;
case kSegOpUSING:
case kSegOpSTRONG:
len += LabelLength(fCurPtr + 1);
break;
case kSegOpGLOBAL:
len += LabelLength(fCurPtr + 1) + 4;
break;
case kSegOpGEQU:
len += LabelLength(fCurPtr + 1) + 4;
len += ExpressionLength(fCurPtr + len);
break;
case kSegOpMEM: // not used on IIgs?
len += fNumLen*2;
break;
case kSegOpEXPR:
case kSegOpZEXPR:
case kSegOpBEXPR:
len += 1;
len += ExpressionLength(fCurPtr + len);
break;
case kSegOpRELEXPR:
len += 1 + fNumLen;
len += ExpressionLength(fCurPtr + len);
break;
case kSegOpLOCAL:
len += LabelLength(fCurPtr+1) + 4;
break;
case kSegOpEQU:
len += LabelLength(fCurPtr+1) + 4;
len += ExpressionLength(fCurPtr + len);
break;
case kSegOpDS:
len += fNumLen;
break;
case kSegOpLCONST:
subLen = Get32LE(fCurPtr+1);
len += fNumLen + subLen;
break;
case kSegOpLEXPR:
len += 1;
len += ExpressionLength(fCurPtr + len);
break;
case kSegOpENTRY:
len += 6;
len += LabelLength(fCurPtr + len);
break;
case kSegOpcRELOC:
len += 6;
break;
case kSegOpcINTERSEG:
len += 7;
break;
case kSegOpSUPER:
subLen = Get32LE(fCurPtr+1);
len += 4 + subLen;
break;
case kSegOpGeneral:
case kSegOpExperimental1:
case kSegOpExperimental2:
case kSegOpExperimental3:
case kSegOpExperimental4:
subLen = Get32LE(fCurPtr+1); // assumes fNumLen==4
LOGI(" OMF found 'reserved' len=%lu (remLen=%ld)", subLen, remLen);
if (subLen > (uint32_t) remLen)
return NULL;
len += subLen + fNumLen;
break;
default:
assert(len == 1);
if (*fCurPtr >= kSegOpCONSTStart && *fCurPtr <= kSegOpCONSTEnd)
len += *fCurPtr;
break;
}
fCurPtr += len;
return prevPtr;
}
/*
* Determine the length of an OMF expression.
*
* Pass a pointer to the start of the expression.
*/
int
OMFSegment::ExpressionLength(const uint8_t* ptr)
{
// do this someday
return 1;
}
#endif