Trace logging optimizations and bug fixes
This commit is contained in:
parent
780a445468
commit
56980510b7
|
@ -31,6 +31,7 @@
|
|||
#include "../../ines.h"
|
||||
#include "../../asm.h"
|
||||
#include "../../x6502.h"
|
||||
#include "utils/StringBuilder.h"
|
||||
|
||||
#include "Qt/fceuWrapper.h"
|
||||
#include "Qt/SymbolicDebug.h"
|
||||
|
@ -42,6 +43,7 @@
|
|||
debugSymbol_t *replaceSymbols( int flags, int addr, char *str )
|
||||
{
|
||||
debugSymbol_t *sym;
|
||||
StringBuilder sb(str);
|
||||
|
||||
if ( addr >= 0x8000 )
|
||||
{
|
||||
|
@ -59,36 +61,16 @@ debugSymbol_t *replaceSymbols( int flags, int addr, char *str )
|
|||
}
|
||||
}
|
||||
|
||||
if ( !sym || !( flags & ASM_DEBUG_REPLACE ) )
|
||||
{
|
||||
sb << sb_addr( addr, flags & ASM_DEBUG_ADDR_02X ? 2 : 4 );
|
||||
|
||||
if ( sym )
|
||||
sb << ' ';
|
||||
}
|
||||
|
||||
if ( sym )
|
||||
{
|
||||
if ( flags & ASM_DEBUG_REPLACE )
|
||||
{
|
||||
strcpy( str, sym->name().c_str() );
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( flags & ASM_DEBUG_ADDR_02X )
|
||||
{
|
||||
sprintf( str, "$%02X ", addr );
|
||||
}
|
||||
else
|
||||
{
|
||||
sprintf( str, "$%04X ", addr );
|
||||
}
|
||||
strcat( str, sym->name().c_str() );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( flags & ASM_DEBUG_ADDR_02X )
|
||||
{
|
||||
sprintf( str, "$%02X", addr );
|
||||
}
|
||||
else
|
||||
{
|
||||
sprintf( str, "$%04X", addr );
|
||||
}
|
||||
}
|
||||
sb << sym->name().c_str();
|
||||
|
||||
return sym;
|
||||
}
|
||||
|
@ -97,10 +79,12 @@ int DisassembleWithDebug(int addr, uint8_t *opcode, int flags, char *str, debugS
|
|||
{
|
||||
debugSymbol_t *sym = NULL;
|
||||
debugSymbol_t *sym2 = NULL;
|
||||
static char chr[8]={0};
|
||||
const char *chr;
|
||||
char indReg;
|
||||
uint16_t tmp,tmp2;
|
||||
char stmp[128], stmp2[128];
|
||||
bool symDebugEnable, showTrace;
|
||||
StringBuilder sb(str);
|
||||
|
||||
symDebugEnable = (flags & ASM_DEBUG_SYMS ) ? true : false;
|
||||
showTrace = (flags & ASM_DEBUG_TRACES) ? true : false;
|
||||
|
@ -170,321 +154,309 @@ int DisassembleWithDebug(int addr, uint8_t *opcode, int flags, char *str, debugS
|
|||
case 0xF8: strcpy(str,"SED"); break;
|
||||
|
||||
//(Indirect,X)
|
||||
case 0x01: strcpy(chr,"ORA"); goto _indirectx;
|
||||
case 0x21: strcpy(chr,"AND"); goto _indirectx;
|
||||
case 0x41: strcpy(chr,"EOR"); goto _indirectx;
|
||||
case 0x61: strcpy(chr,"ADC"); goto _indirectx;
|
||||
case 0x81: strcpy(chr,"STA"); goto _indirectx;
|
||||
case 0xA1: strcpy(chr,"LDA"); goto _indirectx;
|
||||
case 0xC1: strcpy(chr,"CMP"); goto _indirectx;
|
||||
case 0xE1: strcpy(chr,"SBC"); goto _indirectx;
|
||||
case 0x01: chr = "ORA"; goto _indirectx;
|
||||
case 0x21: chr = "AND"; goto _indirectx;
|
||||
case 0x41: chr = "EOR"; goto _indirectx;
|
||||
case 0x61: chr = "ADC"; goto _indirectx;
|
||||
case 0x81: chr = "STA"; goto _indirectx;
|
||||
case 0xA1: chr = "LDA"; goto _indirectx;
|
||||
case 0xC1: chr = "CMP"; goto _indirectx;
|
||||
case 0xE1: chr = "SBC"; goto _indirectx;
|
||||
_indirectx:
|
||||
indirectX(tmp);
|
||||
indReg = 'X';
|
||||
|
||||
_indirect:
|
||||
if ( symDebugEnable )
|
||||
{
|
||||
sym = replaceSymbols( flags, tmp, stmp );
|
||||
showTrace
|
||||
? sprintf(str,"%s ($%02X,X) @ %s = #$%02X", chr,opcode[1],stmp,GetMem(tmp))
|
||||
: sprintf(str,"%s ($%02X,X)", chr,opcode[1]);
|
||||
}
|
||||
else
|
||||
|
||||
sb << chr << " (" << sb_addr(opcode[1], 2) << ',' << indReg << ')';
|
||||
|
||||
if (showTrace)
|
||||
{
|
||||
showTrace
|
||||
? sprintf(str,"%s ($%02X,X) @ $%04X = #$%02X", chr,opcode[1],tmp,GetMem(tmp))
|
||||
: sprintf(str,"%s ($%02X,X)", chr,opcode[1]);
|
||||
sb << " @ ";
|
||||
if (symDebugEnable)
|
||||
sb << stmp;
|
||||
else
|
||||
sb << sb_addr(tmp);
|
||||
|
||||
sb << " = " << sb_lit(GetMem(tmp));
|
||||
}
|
||||
break;
|
||||
|
||||
//Zero Page
|
||||
case 0x05: strcpy(chr,"ORA"); goto _zeropage;
|
||||
case 0x06: strcpy(chr,"ASL"); goto _zeropage;
|
||||
case 0x24: strcpy(chr,"BIT"); goto _zeropage;
|
||||
case 0x25: strcpy(chr,"AND"); goto _zeropage;
|
||||
case 0x26: strcpy(chr,"ROL"); goto _zeropage;
|
||||
case 0x45: strcpy(chr,"EOR"); goto _zeropage;
|
||||
case 0x46: strcpy(chr,"LSR"); goto _zeropage;
|
||||
case 0x65: strcpy(chr,"ADC"); goto _zeropage;
|
||||
case 0x66: strcpy(chr,"ROR"); goto _zeropage;
|
||||
case 0x84: strcpy(chr,"STY"); goto _zeropage;
|
||||
case 0x85: strcpy(chr,"STA"); goto _zeropage;
|
||||
case 0x86: strcpy(chr,"STX"); goto _zeropage;
|
||||
case 0xA4: strcpy(chr,"LDY"); goto _zeropage;
|
||||
case 0xA5: strcpy(chr,"LDA"); goto _zeropage;
|
||||
case 0xA6: strcpy(chr,"LDX"); goto _zeropage;
|
||||
case 0xC4: strcpy(chr,"CPY"); goto _zeropage;
|
||||
case 0xC5: strcpy(chr,"CMP"); goto _zeropage;
|
||||
case 0xC6: strcpy(chr,"DEC"); goto _zeropage;
|
||||
case 0xE4: strcpy(chr,"CPX"); goto _zeropage;
|
||||
case 0xE5: strcpy(chr,"SBC"); goto _zeropage;
|
||||
case 0xE6: strcpy(chr,"INC"); goto _zeropage;
|
||||
case 0x05: chr = "ORA"; goto _zeropage;
|
||||
case 0x06: chr = "ASL"; goto _zeropage;
|
||||
case 0x24: chr = "BIT"; goto _zeropage;
|
||||
case 0x25: chr = "AND"; goto _zeropage;
|
||||
case 0x26: chr = "ROL"; goto _zeropage;
|
||||
case 0x45: chr = "EOR"; goto _zeropage;
|
||||
case 0x46: chr = "LSR"; goto _zeropage;
|
||||
case 0x65: chr = "ADC"; goto _zeropage;
|
||||
case 0x66: chr = "ROR"; goto _zeropage;
|
||||
case 0x84: chr = "STY"; goto _zeropage;
|
||||
case 0x85: chr = "STA"; goto _zeropage;
|
||||
case 0x86: chr = "STX"; goto _zeropage;
|
||||
case 0xA4: chr = "LDY"; goto _zeropage;
|
||||
case 0xA5: chr = "LDA"; goto _zeropage;
|
||||
case 0xA6: chr = "LDX"; goto _zeropage;
|
||||
case 0xC4: chr = "CPY"; goto _zeropage;
|
||||
case 0xC5: chr = "CMP"; goto _zeropage;
|
||||
case 0xC6: chr = "DEC"; goto _zeropage;
|
||||
case 0xE4: chr = "CPX"; goto _zeropage;
|
||||
case 0xE5: chr = "SBC"; goto _zeropage;
|
||||
case 0xE6: chr = "INC"; goto _zeropage;
|
||||
_zeropage:
|
||||
// ################################## Start of SP CODE ###########################
|
||||
// Change width to %04X // don't!
|
||||
|
||||
sb << chr << ' ';
|
||||
if ( symDebugEnable )
|
||||
{
|
||||
sym = replaceSymbols( flags | ASM_DEBUG_ADDR_02X, opcode[1], stmp );
|
||||
showTrace
|
||||
? sprintf(str,"%s %s = #$%02X", chr,stmp,GetMem(opcode[1]))
|
||||
: sprintf(str,"%s %s", chr,stmp);
|
||||
sb << stmp;
|
||||
}
|
||||
else
|
||||
{
|
||||
showTrace
|
||||
? sprintf(str,"%s $%02X = #$%02X", chr,opcode[1],GetMem(opcode[1]))
|
||||
: sprintf(str,"%s $%02X", chr,opcode[1]);
|
||||
}
|
||||
sb << sb_addr(opcode[1], 2);
|
||||
|
||||
if (showTrace)
|
||||
sb << " = " << sb_lit(GetMem(opcode[1]));
|
||||
|
||||
// ################################## End of SP CODE ###########################
|
||||
break;
|
||||
|
||||
//#Immediate
|
||||
case 0x09: strcpy(chr,"ORA"); goto _immediate;
|
||||
case 0x29: strcpy(chr,"AND"); goto _immediate;
|
||||
case 0x49: strcpy(chr,"EOR"); goto _immediate;
|
||||
case 0x69: strcpy(chr,"ADC"); goto _immediate;
|
||||
//case 0x89: strcpy(chr,"STA"); goto _immediate; //baka, no STA #imm!!
|
||||
case 0xA0: strcpy(chr,"LDY"); goto _immediate;
|
||||
case 0xA2: strcpy(chr,"LDX"); goto _immediate;
|
||||
case 0xA9: strcpy(chr,"LDA"); goto _immediate;
|
||||
case 0xC0: strcpy(chr,"CPY"); goto _immediate;
|
||||
case 0xC9: strcpy(chr,"CMP"); goto _immediate;
|
||||
case 0xE0: strcpy(chr,"CPX"); goto _immediate;
|
||||
case 0xE9: strcpy(chr,"SBC"); goto _immediate;
|
||||
case 0x09: chr = "ORA"; goto _immediate;
|
||||
case 0x29: chr = "AND"; goto _immediate;
|
||||
case 0x49: chr = "EOR"; goto _immediate;
|
||||
case 0x69: chr = "ADC"; goto _immediate;
|
||||
//case 0x89: chr = "STA"; goto _immediate; //baka, no STA #imm!!
|
||||
case 0xA0: chr = "LDY"; goto _immediate;
|
||||
case 0xA2: chr = "LDX"; goto _immediate;
|
||||
case 0xA9: chr = "LDA"; goto _immediate;
|
||||
case 0xC0: chr = "CPY"; goto _immediate;
|
||||
case 0xC9: chr = "CMP"; goto _immediate;
|
||||
case 0xE0: chr = "CPX"; goto _immediate;
|
||||
case 0xE9: chr = "SBC"; goto _immediate;
|
||||
_immediate:
|
||||
sprintf(str,"%s #$%02X", chr,opcode[1]);
|
||||
sb << chr << ' ' << sb_lit(opcode[1]);
|
||||
break;
|
||||
|
||||
//Absolute
|
||||
case 0x0D: strcpy(chr,"ORA"); goto _absolute;
|
||||
case 0x0E: strcpy(chr,"ASL"); goto _absolute;
|
||||
case 0x2C: strcpy(chr,"BIT"); goto _absolute;
|
||||
case 0x2D: strcpy(chr,"AND"); goto _absolute;
|
||||
case 0x2E: strcpy(chr,"ROL"); goto _absolute;
|
||||
case 0x4D: strcpy(chr,"EOR"); goto _absolute;
|
||||
case 0x4E: strcpy(chr,"LSR"); goto _absolute;
|
||||
case 0x6D: strcpy(chr,"ADC"); goto _absolute;
|
||||
case 0x6E: strcpy(chr,"ROR"); goto _absolute;
|
||||
case 0x8C: strcpy(chr,"STY"); goto _absolute;
|
||||
case 0x8D: strcpy(chr,"STA"); goto _absolute;
|
||||
case 0x8E: strcpy(chr,"STX"); goto _absolute;
|
||||
case 0xAC: strcpy(chr,"LDY"); goto _absolute;
|
||||
case 0xAD: strcpy(chr,"LDA"); goto _absolute;
|
||||
case 0xAE: strcpy(chr,"LDX"); goto _absolute;
|
||||
case 0xCC: strcpy(chr,"CPY"); goto _absolute;
|
||||
case 0xCD: strcpy(chr,"CMP"); goto _absolute;
|
||||
case 0xCE: strcpy(chr,"DEC"); goto _absolute;
|
||||
case 0xEC: strcpy(chr,"CPX"); goto _absolute;
|
||||
case 0xED: strcpy(chr,"SBC"); goto _absolute;
|
||||
case 0xEE: strcpy(chr,"INC"); goto _absolute;
|
||||
case 0x0D: chr = "ORA"; goto _absolute;
|
||||
case 0x0E: chr = "ASL"; goto _absolute;
|
||||
case 0x2C: chr = "BIT"; goto _absolute;
|
||||
case 0x2D: chr = "AND"; goto _absolute;
|
||||
case 0x2E: chr = "ROL"; goto _absolute;
|
||||
case 0x4D: chr = "EOR"; goto _absolute;
|
||||
case 0x4E: chr = "LSR"; goto _absolute;
|
||||
case 0x6D: chr = "ADC"; goto _absolute;
|
||||
case 0x6E: chr = "ROR"; goto _absolute;
|
||||
case 0x8C: chr = "STY"; goto _absolute;
|
||||
case 0x8D: chr = "STA"; goto _absolute;
|
||||
case 0x8E: chr = "STX"; goto _absolute;
|
||||
case 0xAC: chr = "LDY"; goto _absolute;
|
||||
case 0xAD: chr = "LDA"; goto _absolute;
|
||||
case 0xAE: chr = "LDX"; goto _absolute;
|
||||
case 0xCC: chr = "CPY"; goto _absolute;
|
||||
case 0xCD: chr = "CMP"; goto _absolute;
|
||||
case 0xCE: chr = "DEC"; goto _absolute;
|
||||
case 0xEC: chr = "CPX"; goto _absolute;
|
||||
case 0xED: chr = "SBC"; goto _absolute;
|
||||
case 0xEE: chr = "INC"; goto _absolute;
|
||||
_absolute:
|
||||
absolute(tmp);
|
||||
|
||||
sb << chr << ' ';
|
||||
if ( symDebugEnable )
|
||||
{
|
||||
sym = replaceSymbols( flags, tmp, stmp );
|
||||
showTrace
|
||||
? sprintf(str,"%s %s = #$%02X", chr,stmp,GetMem(tmp))
|
||||
: sprintf(str,"%s %s", chr,stmp);
|
||||
sb << stmp;
|
||||
}
|
||||
else
|
||||
{
|
||||
showTrace
|
||||
? sprintf(str,"%s $%04X = #$%02X", chr,tmp,GetMem(tmp))
|
||||
: sprintf(str,"%s $%04X", chr,tmp);
|
||||
}
|
||||
sb << sb_addr(tmp);
|
||||
|
||||
if (showTrace)
|
||||
sb << " = " << sb_lit(GetMem(tmp));
|
||||
|
||||
break;
|
||||
|
||||
//branches
|
||||
case 0x10: strcpy(chr,"BPL"); goto _branch;
|
||||
case 0x30: strcpy(chr,"BMI"); goto _branch;
|
||||
case 0x50: strcpy(chr,"BVC"); goto _branch;
|
||||
case 0x70: strcpy(chr,"BVS"); goto _branch;
|
||||
case 0x90: strcpy(chr,"BCC"); goto _branch;
|
||||
case 0xB0: strcpy(chr,"BCS"); goto _branch;
|
||||
case 0xD0: strcpy(chr,"BNE"); goto _branch;
|
||||
case 0xF0: strcpy(chr,"BEQ"); goto _branch;
|
||||
case 0x10: chr = "BPL"; goto _branch;
|
||||
case 0x30: chr = "BMI"; goto _branch;
|
||||
case 0x50: chr = "BVC"; goto _branch;
|
||||
case 0x70: chr = "BVS"; goto _branch;
|
||||
case 0x90: chr = "BCC"; goto _branch;
|
||||
case 0xB0: chr = "BCS"; goto _branch;
|
||||
case 0xD0: chr = "BNE"; goto _branch;
|
||||
case 0xF0: chr = "BEQ"; goto _branch;
|
||||
_branch:
|
||||
relative(tmp);
|
||||
|
||||
sb << chr << ' ';
|
||||
if ( symDebugEnable )
|
||||
{
|
||||
sym = replaceSymbols( flags, tmp, stmp );
|
||||
sprintf(str,"%s %s", chr,stmp);
|
||||
sb << stmp;
|
||||
}
|
||||
else
|
||||
{
|
||||
sprintf(str,"%s $%04X", chr,tmp);
|
||||
}
|
||||
sb << sb_addr(tmp);
|
||||
|
||||
break;
|
||||
|
||||
//(Indirect),Y
|
||||
case 0x11: strcpy(chr,"ORA"); goto _indirecty;
|
||||
case 0x31: strcpy(chr,"AND"); goto _indirecty;
|
||||
case 0x51: strcpy(chr,"EOR"); goto _indirecty;
|
||||
case 0x71: strcpy(chr,"ADC"); goto _indirecty;
|
||||
case 0x91: strcpy(chr,"STA"); goto _indirecty;
|
||||
case 0xB1: strcpy(chr,"LDA"); goto _indirecty;
|
||||
case 0xD1: strcpy(chr,"CMP"); goto _indirecty;
|
||||
case 0xF1: strcpy(chr,"SBC"); goto _indirecty;
|
||||
case 0x11: chr = "ORA"; goto _indirecty;
|
||||
case 0x31: chr = "AND"; goto _indirecty;
|
||||
case 0x51: chr = "EOR"; goto _indirecty;
|
||||
case 0x71: chr = "ADC"; goto _indirecty;
|
||||
case 0x91: chr = "STA"; goto _indirecty;
|
||||
case 0xB1: chr = "LDA"; goto _indirecty;
|
||||
case 0xD1: chr = "CMP"; goto _indirecty;
|
||||
case 0xF1: chr = "SBC"; goto _indirecty;
|
||||
_indirecty:
|
||||
indirectY(tmp);
|
||||
indReg = 'Y';
|
||||
|
||||
if ( symDebugEnable )
|
||||
{
|
||||
sym = replaceSymbols( flags, tmp, stmp );
|
||||
showTrace
|
||||
? sprintf(str,"%s ($%02X),Y @ %s = #$%02X", chr,opcode[1],stmp,GetMem(tmp))
|
||||
: sprintf(str,"%s ($%02X),Y", chr,opcode[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
showTrace
|
||||
? sprintf(str,"%s ($%02X),Y @ $%04X = #$%02X", chr,opcode[1],tmp,GetMem(tmp))
|
||||
: sprintf(str,"%s ($%02X),Y", chr,opcode[1]);
|
||||
}
|
||||
break;
|
||||
goto _indirect;
|
||||
|
||||
//Zero Page,X
|
||||
case 0x15: strcpy(chr,"ORA"); goto _zeropagex;
|
||||
case 0x16: strcpy(chr,"ASL"); goto _zeropagex;
|
||||
case 0x35: strcpy(chr,"AND"); goto _zeropagex;
|
||||
case 0x36: strcpy(chr,"ROL"); goto _zeropagex;
|
||||
case 0x55: strcpy(chr,"EOR"); goto _zeropagex;
|
||||
case 0x56: strcpy(chr,"LSR"); goto _zeropagex;
|
||||
case 0x75: strcpy(chr,"ADC"); goto _zeropagex;
|
||||
case 0x76: strcpy(chr,"ROR"); goto _zeropagex;
|
||||
case 0x94: strcpy(chr,"STY"); goto _zeropagex;
|
||||
case 0x95: strcpy(chr,"STA"); goto _zeropagex;
|
||||
case 0xB4: strcpy(chr,"LDY"); goto _zeropagex;
|
||||
case 0xB5: strcpy(chr,"LDA"); goto _zeropagex;
|
||||
case 0xD5: strcpy(chr,"CMP"); goto _zeropagex;
|
||||
case 0xD6: strcpy(chr,"DEC"); goto _zeropagex;
|
||||
case 0xF5: strcpy(chr,"SBC"); goto _zeropagex;
|
||||
case 0xF6: strcpy(chr,"INC"); goto _zeropagex;
|
||||
case 0x15: chr = "ORA"; goto _zeropagex;
|
||||
case 0x16: chr = "ASL"; goto _zeropagex;
|
||||
case 0x35: chr = "AND"; goto _zeropagex;
|
||||
case 0x36: chr = "ROL"; goto _zeropagex;
|
||||
case 0x55: chr = "EOR"; goto _zeropagex;
|
||||
case 0x56: chr = "LSR"; goto _zeropagex;
|
||||
case 0x75: chr = "ADC"; goto _zeropagex;
|
||||
case 0x76: chr = "ROR"; goto _zeropagex;
|
||||
case 0x94: chr = "STY"; goto _zeropagex;
|
||||
case 0x95: chr = "STA"; goto _zeropagex;
|
||||
case 0xB4: chr = "LDY"; goto _zeropagex;
|
||||
case 0xB5: chr = "LDA"; goto _zeropagex;
|
||||
case 0xD5: chr = "CMP"; goto _zeropagex;
|
||||
case 0xD6: chr = "DEC"; goto _zeropagex;
|
||||
case 0xF5: chr = "SBC"; goto _zeropagex;
|
||||
case 0xF6: chr = "INC"; goto _zeropagex;
|
||||
_zeropagex:
|
||||
zpIndex(tmp,RX);
|
||||
indReg = 'X';
|
||||
|
||||
_indexed:
|
||||
// ################################## Start of SP CODE ###########################
|
||||
// Change width to %04X // don't!
|
||||
if ( symDebugEnable )
|
||||
{
|
||||
sym = replaceSymbols( flags, tmp, stmp );
|
||||
showTrace
|
||||
? sprintf(str,"%s $%02X,X @ %s = #$%02X", chr,opcode[1],stmp,GetMem(tmp))
|
||||
: sprintf(str,"%s $%02X,X", chr,opcode[1]);
|
||||
}
|
||||
else
|
||||
|
||||
sb << chr << ' ' << sb_addr(opcode[1], 2) << ',' << indReg;
|
||||
if (showTrace)
|
||||
{
|
||||
showTrace
|
||||
? sprintf(str,"%s $%02X,X @ $%04X = #$%02X", chr,opcode[1],tmp,GetMem(tmp))
|
||||
: sprintf(str,"%s $%02X,X", chr,opcode[1]);
|
||||
sb << " @ ";
|
||||
if (symDebugEnable)
|
||||
sb << stmp;
|
||||
else
|
||||
sb << sb_addr(tmp);
|
||||
|
||||
sb << " = " << sb_lit(GetMem(tmp));
|
||||
}
|
||||
// ################################## End of SP CODE ###########################
|
||||
break;
|
||||
|
||||
//Absolute,Y
|
||||
case 0x19: strcpy(chr,"ORA"); goto _absolutey;
|
||||
case 0x39: strcpy(chr,"AND"); goto _absolutey;
|
||||
case 0x59: strcpy(chr,"EOR"); goto _absolutey;
|
||||
case 0x79: strcpy(chr,"ADC"); goto _absolutey;
|
||||
case 0x99: strcpy(chr,"STA"); goto _absolutey;
|
||||
case 0xB9: strcpy(chr,"LDA"); goto _absolutey;
|
||||
case 0xBE: strcpy(chr,"LDX"); goto _absolutey;
|
||||
case 0xD9: strcpy(chr,"CMP"); goto _absolutey;
|
||||
case 0xF9: strcpy(chr,"SBC"); goto _absolutey;
|
||||
case 0x19: chr = "ORA"; goto _absolutey;
|
||||
case 0x39: chr = "AND"; goto _absolutey;
|
||||
case 0x59: chr = "EOR"; goto _absolutey;
|
||||
case 0x79: chr = "ADC"; goto _absolutey;
|
||||
case 0x99: chr = "STA"; goto _absolutey;
|
||||
case 0xB9: chr = "LDA"; goto _absolutey;
|
||||
case 0xBE: chr = "LDX"; goto _absolutey;
|
||||
case 0xD9: chr = "CMP"; goto _absolutey;
|
||||
case 0xF9: chr = "SBC"; goto _absolutey;
|
||||
_absolutey:
|
||||
absolute(tmp);
|
||||
tmp2=(tmp+RY);
|
||||
indReg = 'Y';
|
||||
|
||||
_absindexed:
|
||||
sb << chr << ' ';
|
||||
if ( symDebugEnable )
|
||||
{
|
||||
sym = replaceSymbols( flags, tmp , stmp );
|
||||
sym2 = replaceSymbols( flags, tmp2, stmp2 );
|
||||
showTrace
|
||||
? sprintf(str,"%s %s,Y @ %s = #$%02X", chr,stmp,stmp2,GetMem(tmp2))
|
||||
: sprintf(str,"%s %s,Y", chr,stmp);
|
||||
sb << stmp;
|
||||
}
|
||||
else
|
||||
sb << sb_addr(tmp);
|
||||
|
||||
sb << ',' << indReg;
|
||||
|
||||
if (showTrace)
|
||||
{
|
||||
showTrace
|
||||
? sprintf(str,"%s $%04X,Y @ $%04X = #$%02X", chr,tmp,tmp2,GetMem(tmp2))
|
||||
: sprintf(str,"%s $%04X,Y", chr,tmp);
|
||||
sb << " @ ";
|
||||
if (symDebugEnable)
|
||||
sb << stmp2;
|
||||
else
|
||||
sb << sb_addr(tmp2);
|
||||
|
||||
sb << " = " << sb_lit(GetMem(tmp2));
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
//Absolute,X
|
||||
case 0x1D: strcpy(chr,"ORA"); goto _absolutex;
|
||||
case 0x1E: strcpy(chr,"ASL"); goto _absolutex;
|
||||
case 0x3D: strcpy(chr,"AND"); goto _absolutex;
|
||||
case 0x3E: strcpy(chr,"ROL"); goto _absolutex;
|
||||
case 0x5D: strcpy(chr,"EOR"); goto _absolutex;
|
||||
case 0x5E: strcpy(chr,"LSR"); goto _absolutex;
|
||||
case 0x7D: strcpy(chr,"ADC"); goto _absolutex;
|
||||
case 0x7E: strcpy(chr,"ROR"); goto _absolutex;
|
||||
case 0x9D: strcpy(chr,"STA"); goto _absolutex;
|
||||
case 0xBC: strcpy(chr,"LDY"); goto _absolutex;
|
||||
case 0xBD: strcpy(chr,"LDA"); goto _absolutex;
|
||||
case 0xDD: strcpy(chr,"CMP"); goto _absolutex;
|
||||
case 0xDE: strcpy(chr,"DEC"); goto _absolutex;
|
||||
case 0xFD: strcpy(chr,"SBC"); goto _absolutex;
|
||||
case 0xFE: strcpy(chr,"INC"); goto _absolutex;
|
||||
case 0x1D: chr = "ORA"; goto _absolutex;
|
||||
case 0x1E: chr = "ASL"; goto _absolutex;
|
||||
case 0x3D: chr = "AND"; goto _absolutex;
|
||||
case 0x3E: chr = "ROL"; goto _absolutex;
|
||||
case 0x5D: chr = "EOR"; goto _absolutex;
|
||||
case 0x5E: chr = "LSR"; goto _absolutex;
|
||||
case 0x7D: chr = "ADC"; goto _absolutex;
|
||||
case 0x7E: chr = "ROR"; goto _absolutex;
|
||||
case 0x9D: chr = "STA"; goto _absolutex;
|
||||
case 0xBC: chr = "LDY"; goto _absolutex;
|
||||
case 0xBD: chr = "LDA"; goto _absolutex;
|
||||
case 0xDD: chr = "CMP"; goto _absolutex;
|
||||
case 0xDE: chr = "DEC"; goto _absolutex;
|
||||
case 0xFD: chr = "SBC"; goto _absolutex;
|
||||
case 0xFE: chr = "INC"; goto _absolutex;
|
||||
_absolutex:
|
||||
absolute(tmp);
|
||||
tmp2=(tmp+RX);
|
||||
if ( symDebugEnable )
|
||||
{
|
||||
sym = replaceSymbols( flags, tmp , stmp );
|
||||
sym2 = replaceSymbols( flags, tmp2, stmp2 );
|
||||
showTrace
|
||||
? sprintf(str,"%s %s,X @ %s = #$%02X", chr,stmp,stmp2,GetMem(tmp2))
|
||||
: sprintf(str,"%s %s,X", chr,stmp);
|
||||
}
|
||||
else
|
||||
{
|
||||
showTrace
|
||||
? sprintf(str,"%s $%04X,X @ $%04X = #$%02X", chr,tmp,tmp2,GetMem(tmp2))
|
||||
: sprintf(str,"%s $%04X,X", chr,tmp);
|
||||
}
|
||||
break;
|
||||
indReg = 'X';
|
||||
|
||||
goto _absindexed;
|
||||
|
||||
//jumps
|
||||
case 0x20: strcpy(chr,"JSR"); goto _jump;
|
||||
case 0x4C: strcpy(chr,"JMP"); goto _jump;
|
||||
case 0x6C: absolute(tmp); sprintf(str,"JMP ($%04X) = $%04X", tmp,GetMem(tmp)|GetMem(tmp+1)<<8); break;
|
||||
case 0x20: chr = "JSR"; goto _jump;
|
||||
case 0x4C: chr = "JMP"; goto _jump;
|
||||
_jump:
|
||||
absolute(tmp);
|
||||
|
||||
if ( symDebugEnable )
|
||||
sb << chr << ' ';
|
||||
if (symDebugEnable)
|
||||
{
|
||||
sym = replaceSymbols( flags, tmp, stmp );
|
||||
sprintf(str,"%s %s", chr,stmp);
|
||||
sym = replaceSymbols(flags, tmp, stmp);
|
||||
sb << stmp;
|
||||
}
|
||||
else
|
||||
{
|
||||
sprintf(str,"%s $%04X", chr,tmp);
|
||||
}
|
||||
sb << sb_addr(tmp);
|
||||
|
||||
break;
|
||||
|
||||
case 0x6C:
|
||||
absolute(tmp);
|
||||
|
||||
sb << "JMP (" << sb_addr(tmp);
|
||||
sb << ") = " << sb_addr(GetMem(tmp) | GetMem(tmp + 1) << 8);
|
||||
|
||||
break;
|
||||
|
||||
//Zero Page,Y
|
||||
case 0x96: strcpy(chr,"STX"); goto _zeropagey;
|
||||
case 0xB6: strcpy(chr,"LDX"); goto _zeropagey;
|
||||
case 0x96: chr = "STX"; goto _zeropagey;
|
||||
case 0xB6: chr = "LDX"; goto _zeropagey;
|
||||
_zeropagey:
|
||||
zpIndex(tmp,RY);
|
||||
// ################################## Start of SP CODE ###########################
|
||||
// Change width to %04X // don't!
|
||||
if ( symDebugEnable )
|
||||
{
|
||||
sym = replaceSymbols( flags, tmp, stmp );
|
||||
showTrace
|
||||
? sprintf(str,"%s $%02X,Y @ %s = #$%02X", chr,opcode[1],stmp,GetMem(tmp))
|
||||
: sprintf(str,"%s $%02X,Y", chr,opcode[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
showTrace
|
||||
? sprintf(str,"%s $%02X,Y @ $%04X = #$%02X", chr,opcode[1],tmp,GetMem(tmp))
|
||||
: sprintf(str,"%s $%02X,Y", chr,opcode[1]);
|
||||
}
|
||||
// ################################## End of SP CODE ###########################
|
||||
break;
|
||||
indReg = 'Y';
|
||||
|
||||
goto _indexed;
|
||||
|
||||
//UNDEFINED
|
||||
default: strcpy(str,"ERROR"); break;
|
||||
|
|
|
@ -0,0 +1,470 @@
|
|||
#pragma once
|
||||
|
||||
#include <Windows.h>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
|
||||
// High-performance class for writing a series of text lines to a file, using overlapped, unbuffered I/O
|
||||
// Works on Windows builds both SDL/Qt and non-SQL/Qt
|
||||
// Apart from getLastError, the entire API can be adapted to other OS with no client changes
|
||||
class TraceFileWriter
|
||||
{
|
||||
public:
|
||||
static const size_t BlockSize = 4 << 10;
|
||||
static const size_t BuffSize = BlockSize * 257;
|
||||
static const size_t FlushSize = BlockSize * 256;
|
||||
|
||||
inline TraceFileWriter()
|
||||
{
|
||||
// Windows Vista API that allows setting the end of file without closing and reopening it
|
||||
HMODULE kernel32 = GetModuleHandleA("kernel32");
|
||||
SetFileInformationByHandle = kernel32 ? (SetFileInformationByHandlePtr)GetProcAddress(kernel32, "SetFileInformationByHandle") : nullptr;
|
||||
|
||||
initialize();
|
||||
}
|
||||
|
||||
inline ~TraceFileWriter()
|
||||
{
|
||||
if (isOpen)
|
||||
close();
|
||||
}
|
||||
|
||||
inline bool getOpen() const
|
||||
{
|
||||
return isOpen;
|
||||
}
|
||||
|
||||
// SUPPOSED to always be valid (ERROR_SUCCESS if no error), but bugs may make it only valid after a failure
|
||||
inline DWORD getLastError() const
|
||||
{
|
||||
return lastErr;
|
||||
}
|
||||
|
||||
// Open the file and allocate all necessary resources
|
||||
bool open(const char *fileName, bool isPaused = false)
|
||||
{
|
||||
HANDLE file = CreateFileA(fileName, GENERIC_WRITE, FILE_SHARE_READ, nullptr, CREATE_ALWAYS, FILE_FLAG_WRITE_THROUGH | FILE_FLAG_NO_BUFFERING | FILE_FLAG_OVERLAPPED, nullptr);
|
||||
if (file == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
lastErr = GetLastError();
|
||||
return false;
|
||||
}
|
||||
|
||||
initialize(false, isPaused, fileName, file);
|
||||
|
||||
// Allocate resources
|
||||
do
|
||||
{
|
||||
for (unsigned i = 0; i < 2; i++)
|
||||
{
|
||||
buffers[i] = (char *)VirtualAlloc(NULL, BuffSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
|
||||
if (buffers[i] == nullptr)
|
||||
{
|
||||
lastErr = GetLastError();
|
||||
break;
|
||||
}
|
||||
|
||||
ovls[i].hEvent = CreateEvent(NULL, TRUE, TRUE, NULL);
|
||||
if (ovls[i].hEvent == nullptr)
|
||||
{
|
||||
lastErr = GetLastError();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!lastErr)
|
||||
isOpen = true;
|
||||
} while (false);
|
||||
|
||||
if (!isOpen)
|
||||
// Free resources on failure
|
||||
cleanup();
|
||||
|
||||
return isOpen;
|
||||
}
|
||||
|
||||
// Close the file and free resources
|
||||
void close()
|
||||
{
|
||||
if (!isOpen)
|
||||
{
|
||||
lastErr = ERROR_FILE_NOT_FOUND;
|
||||
return;
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < 2; i++)
|
||||
waitForBuffer(i);
|
||||
|
||||
writeTail();
|
||||
|
||||
cleanup();
|
||||
}
|
||||
|
||||
// When going from unpaused to paused, flush file and set end of file so it can be accessed externally
|
||||
bool setPause(bool isPaused)
|
||||
{
|
||||
if (isPaused && !this->isPaused)
|
||||
{
|
||||
// Wait for any outstanding writes to complete
|
||||
for (unsigned i = 0; i < 2; i++)
|
||||
{
|
||||
if (!waitForBuffer(i))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write out anything still in the buffer
|
||||
if (!writeTail())
|
||||
return false;
|
||||
}
|
||||
|
||||
this->isPaused = isPaused;
|
||||
lastErr = ERROR_SUCCESS;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Add a line to the buffer and write it out when the buffer is filled
|
||||
// Under most failure cirumstances the line is added to the buffer
|
||||
bool writeLine(const char *line)
|
||||
{
|
||||
if (!isOpen)
|
||||
{
|
||||
lastErr = ERROR_FILE_NOT_FOUND;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add to buffer
|
||||
static const char eol[] = "\r\n";
|
||||
size_t eolSize = strlen(eol);
|
||||
char *buff = buffers[buffIdx];
|
||||
size_t lineLen = strlen(line);
|
||||
if (buffOffs + lineLen + eolSize > BuffSize)
|
||||
{
|
||||
// Buffer is full. This shouldn't ever happen.
|
||||
lastErr = ERROR_INTERNAL_ERROR;
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(buff + buffOffs, line, lineLen);
|
||||
memcpy(buff + buffOffs + lineLen, eol, eolSize);
|
||||
buffOffs += lineLen + eolSize;
|
||||
|
||||
// Check if the previous write is done, to detect it as early as possible
|
||||
unsigned prevBuff = (buffIdx + 1) % 2;
|
||||
if (!waitForBuffer(prevBuff, 0) && lastErr != ERROR_TIMEOUT)
|
||||
return false;
|
||||
|
||||
lastErr = ERROR_SUCCESS;
|
||||
|
||||
if (buffOffs < FlushSize)
|
||||
return true;
|
||||
|
||||
return writeBlocks();
|
||||
}
|
||||
|
||||
// Flush buffer contents. Writes partial blocks, but does NOT set end of file
|
||||
// Do NOT call frequently, as writes may be significantly undersized and cause poor performance
|
||||
bool flush()
|
||||
{
|
||||
if (!isOpen)
|
||||
{
|
||||
lastErr = ERROR_FILE_NOT_FOUND;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write full blocks, if any
|
||||
if (!writeBlocks())
|
||||
return false;
|
||||
|
||||
char *buff = buffers[buffIdx];
|
||||
if (buffOffs != 0)
|
||||
{
|
||||
// Write out partial block at the tail
|
||||
size_t writeSize = (buffOffs + BlockSize - 1) & ~(BlockSize - 1);
|
||||
memset(buff + buffOffs, ' ', writeSize - buffOffs);
|
||||
|
||||
if (!beginWrite(writeSize)
|
||||
|| !waitForBuffer(buffIdx))
|
||||
return false;
|
||||
|
||||
// Do NOT update buffIdx, buffOffs, or fileOffs, as the partial block must be overwritten later
|
||||
}
|
||||
|
||||
// Wait for all writes to complete
|
||||
for (unsigned i = 0; i < 2; i++)
|
||||
{
|
||||
if (!waitForBuffer(i))
|
||||
return false;
|
||||
}
|
||||
|
||||
lastErr = ERROR_SUCCESS;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
typedef BOOL (*SetFileInformationByHandlePtr)(
|
||||
HANDLE hFile,
|
||||
FILE_INFO_BY_HANDLE_CLASS FileInformationClass,
|
||||
LPVOID lpFileInformation,
|
||||
DWORD dwBufferSize
|
||||
);
|
||||
|
||||
SetFileInformationByHandlePtr SetFileInformationByHandle;
|
||||
|
||||
const size_t NO_WRITE = size_t(-1);
|
||||
|
||||
DWORD lastErr;
|
||||
|
||||
bool isOpen;
|
||||
bool isPaused;
|
||||
|
||||
std::string fileName;
|
||||
HANDLE file;
|
||||
|
||||
// Double-buffers
|
||||
char *buffers[2];
|
||||
OVERLAPPED ovls[2];
|
||||
size_t bytesToWrite[2]; // Write in progress size or size_t(-1) if none
|
||||
|
||||
unsigned buffIdx;
|
||||
size_t buffOffs;
|
||||
uint64_t fileOffs;
|
||||
|
||||
// Put the class into a defined state, but does NOT allocate resources
|
||||
void initialize(bool isOpen = false, bool isPaused = false, const char *fileName = "", HANDLE file = INVALID_HANDLE_VALUE)
|
||||
{
|
||||
lastErr = ERROR_SUCCESS;
|
||||
|
||||
this->isOpen = isOpen;
|
||||
this->isPaused = isPaused;
|
||||
|
||||
this->fileName = fileName;
|
||||
this->file = file;
|
||||
|
||||
for (unsigned i = 0; i < 2; i++)
|
||||
{
|
||||
buffers[i] = nullptr;
|
||||
bytesToWrite[i] = NO_WRITE;
|
||||
}
|
||||
|
||||
memset(ovls, 0, sizeof(ovls));
|
||||
|
||||
buffIdx = 0;
|
||||
buffOffs = 0;
|
||||
fileOffs = 0;
|
||||
}
|
||||
|
||||
// Close file and release resources. Does NOT wait for writes to complete.
|
||||
void cleanup()
|
||||
{
|
||||
isOpen = false;
|
||||
|
||||
if (file != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
CloseHandle(file);
|
||||
file = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < 2; i++)
|
||||
{
|
||||
if (buffers[i] != nullptr)
|
||||
VirtualFree(buffers[i], 0, MEM_RELEASE);
|
||||
if (ovls[i].hEvent != nullptr)
|
||||
CloseHandle(ovls[i].hEvent);
|
||||
|
||||
buffers[i] = nullptr;
|
||||
ovls[i].hEvent = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Write out as many blocks as present in the buffer
|
||||
bool writeBlocks()
|
||||
{
|
||||
if (buffOffs < BlockSize)
|
||||
return true;
|
||||
|
||||
char *buff = buffers[buffIdx];
|
||||
unsigned prevBuff = (buffIdx + 1) % 2;
|
||||
OVERLAPPED *ovl = &ovls[buffIdx];
|
||||
|
||||
DWORD writeSize = buffOffs - (buffOffs % BlockSize);
|
||||
if (!beginWrite(writeSize))
|
||||
return false;
|
||||
|
||||
bool isAsync = bytesToWrite[buffIdx] != NO_WRITE;
|
||||
if (isAsync)
|
||||
{
|
||||
if (!waitForBuffer(prevBuff))
|
||||
return false; // Catastrophic failure case
|
||||
|
||||
// Switch to next buffer
|
||||
|
||||
memcpy(buffers[prevBuff], buff + writeSize, buffOffs - writeSize);
|
||||
buffOffs -= writeSize;
|
||||
|
||||
fileOffs += writeSize;
|
||||
|
||||
buffIdx = prevBuff;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Stick with same buffer
|
||||
memmove(buff, buff + writeSize, buffOffs - writeSize);
|
||||
|
||||
buffOffs -= writeSize;
|
||||
}
|
||||
|
||||
bytesToWrite[buffIdx] = NO_WRITE;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Begin a write and handle errors without updating class state
|
||||
bool beginWrite(size_t writeSize)
|
||||
{
|
||||
bytesToWrite[buffIdx] = NO_WRITE;
|
||||
|
||||
if (writeSize % BlockSize != 0)
|
||||
{
|
||||
lastErr = ERROR_INTERNAL_ERROR;
|
||||
return false;
|
||||
}
|
||||
|
||||
OVERLAPPED *ovl = &ovls[buffIdx];
|
||||
ovl->Offset = (DWORD)fileOffs;
|
||||
ovl->OffsetHigh = DWORD(fileOffs >> 32);
|
||||
|
||||
bool success = false;
|
||||
DWORD bytesWritten;
|
||||
if (WriteFile(file, buffers[buffIdx], writeSize, &bytesWritten, ovl))
|
||||
{
|
||||
// Completed synchronously
|
||||
lastErr = (bytesWritten == writeSize)
|
||||
? ERROR_SUCCESS
|
||||
: ERROR_WRITE_FAULT; // "Close enough"
|
||||
}
|
||||
else
|
||||
{
|
||||
DWORD lastErr = GetLastError();
|
||||
if (lastErr == ERROR_IO_PENDING)
|
||||
{
|
||||
bytesToWrite[buffIdx] = writeSize;
|
||||
lastErr = ERROR_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
return !lastErr;
|
||||
}
|
||||
|
||||
// Set the end of file so it can be accessed
|
||||
bool setEndOfFile()
|
||||
{
|
||||
if (SetFileInformationByHandle != nullptr)
|
||||
{
|
||||
// Easy case: Vista or better
|
||||
FILE_END_OF_FILE_INFO eofInfo;
|
||||
eofInfo.EndOfFile.QuadPart = int64_t(fileOffs + buffOffs);
|
||||
|
||||
if (!SetFileInformationByHandle(file, FileEndOfFileInfo, &eofInfo, sizeof(eofInfo)))
|
||||
{
|
||||
lastErr = GetLastError();
|
||||
return false;
|
||||
}
|
||||
|
||||
lastErr = ERROR_SUCCESS;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Hard case: XP
|
||||
// If set EOF fails, make a desperate attempt to reopen the file and keep running
|
||||
lastErr = ERROR_SUCCESS;
|
||||
do
|
||||
{
|
||||
// Set EOF to fileOffs rounded up to the next block
|
||||
LARGE_INTEGER tgtOffs;
|
||||
tgtOffs.QuadPart = (fileOffs + buffOffs + BlockSize - 1) & ~(BlockSize - 1);
|
||||
LARGE_INTEGER newOffs;
|
||||
|
||||
if (!SetFilePointerEx(file, tgtOffs, &newOffs, FILE_BEGIN)
|
||||
|| !SetEndOfFile(file))
|
||||
break;
|
||||
|
||||
CloseHandle(file);
|
||||
|
||||
// Open file with buffering so the exact byte size can be set
|
||||
tgtOffs.QuadPart = fileOffs + buffOffs;
|
||||
|
||||
file = CreateFile(fileName.c_str(), GENERIC_WRITE, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr);
|
||||
if (file != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
if (!SetFilePointerEx(file, tgtOffs, &newOffs, FILE_BEGIN)
|
||||
|| !SetEndOfFile(file))
|
||||
lastErr = GetLastError();
|
||||
|
||||
CloseHandle(file);
|
||||
}
|
||||
else
|
||||
lastErr = GetLastError();
|
||||
|
||||
// Finally, reopen the file in original mode
|
||||
file = CreateFile(fileName.c_str(), GENERIC_WRITE, FILE_SHARE_READ, nullptr, OPEN_EXISTING,
|
||||
FILE_FLAG_WRITE_THROUGH | FILE_FLAG_NO_BUFFERING | FILE_FLAG_OVERLAPPED, nullptr);
|
||||
if (file == INVALID_HANDLE_VALUE || lastErr)
|
||||
break;
|
||||
|
||||
lastErr = ERROR_SUCCESS;
|
||||
return true;
|
||||
} while (false);
|
||||
|
||||
// Failed
|
||||
if (!lastErr)
|
||||
lastErr = GetLastError();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Write out everything in the buffer and set the file end for pausing
|
||||
inline bool writeTail()
|
||||
{
|
||||
return flush() && setEndOfFile();
|
||||
}
|
||||
|
||||
// Wait for an a buffer to become available, waiting for write completion if necessary
|
||||
bool waitForBuffer(unsigned buffIdx, DWORD timeout = INFINITE)
|
||||
{
|
||||
if (buffIdx >= 2)
|
||||
lastErr = ERROR_INTERNAL_ERROR;
|
||||
else if (bytesToWrite[buffIdx] == NO_WRITE)
|
||||
// No write in progress
|
||||
lastErr = ERROR_SUCCESS;
|
||||
else
|
||||
{
|
||||
// Wait for the operation to complete
|
||||
DWORD waitRes = WaitForSingleObject(ovls[buffIdx].hEvent, timeout);
|
||||
if (waitRes == WAIT_TIMEOUT)
|
||||
lastErr = ERROR_TIMEOUT;
|
||||
else if (waitRes != WAIT_OBJECT_0)
|
||||
{
|
||||
lastErr = GetLastError();
|
||||
bytesToWrite[buffIdx] = NO_WRITE;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Verify it succeeded
|
||||
DWORD prevBytesWritten;
|
||||
if (!GetOverlappedResult(file, &ovls[buffIdx], &prevBytesWritten, FALSE))
|
||||
lastErr = GetLastError();
|
||||
else if (prevBytesWritten != bytesToWrite[buffIdx])
|
||||
lastErr = ERROR_WRITE_FAULT;
|
||||
else
|
||||
lastErr = ERROR_SUCCESS;
|
||||
|
||||
bytesToWrite[buffIdx] = NO_WRITE;
|
||||
}
|
||||
}
|
||||
|
||||
return !lastErr;
|
||||
}
|
||||
};
|
|
@ -25,6 +25,7 @@
|
|||
|
||||
#ifdef WIN32
|
||||
#include <windows.h>
|
||||
#include <Qt/TraceFileWriter.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
|
@ -56,6 +57,7 @@
|
|||
#include "../../movie.h"
|
||||
|
||||
#include "common/os_utils.h"
|
||||
#include "utils/StringBuilder.h"
|
||||
|
||||
#include "Qt/ConsoleDebugger.h"
|
||||
#include "Qt/ConsoleWindow.h"
|
||||
|
@ -914,15 +916,10 @@ traceRecord_t::traceRecord_t(void)
|
|||
//----------------------------------------------------
|
||||
int traceRecord_t::appendAsmText(const char *txt)
|
||||
{
|
||||
int i = 0;
|
||||
size_t len = strlen(txt);
|
||||
memcpy(asmTxt + asmTxtSize, txt, len + 1);
|
||||
|
||||
while (txt[i] != 0)
|
||||
{
|
||||
asmTxt[asmTxtSize] = txt[i];
|
||||
i++;
|
||||
asmTxtSize++;
|
||||
}
|
||||
asmTxt[asmTxtSize] = 0;
|
||||
asmTxtSize += len;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -945,7 +942,6 @@ static int convToXchar(int i)
|
|||
int traceRecord_t::convToText(char *txt, int *len)
|
||||
{
|
||||
int i = 0, j = 0;
|
||||
char stmp[128];
|
||||
char str_axystate[32], str_procstatus[32];
|
||||
|
||||
str_axystate[0] = 0;
|
||||
|
@ -954,112 +950,56 @@ int traceRecord_t::convToText(char *txt, int *len)
|
|||
txt[0] = 0;
|
||||
if (opSize == 0)
|
||||
{
|
||||
j = 0;
|
||||
while (asmTxt[j] != 0)
|
||||
{
|
||||
txt[i] = asmTxt[j];
|
||||
i++;
|
||||
j++;
|
||||
}
|
||||
txt[i] = 0;
|
||||
strcpy(txt, asmTxt);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
StringBuilder sb(txt + i);
|
||||
if (skippedLines > 0)
|
||||
{
|
||||
sprintf(stmp, "(%d lines skipped) ", skippedLines);
|
||||
|
||||
j = 0;
|
||||
while (stmp[j] != 0)
|
||||
{
|
||||
txt[i] = stmp[j];
|
||||
i++;
|
||||
j++;
|
||||
}
|
||||
}
|
||||
sb << '(' << sb_dec(skippedLines) << " lines skipped) ";
|
||||
|
||||
// Start filling the str_temp line: Frame count, Cycles count, Instructions count, AXYS state, Processor status, Tabs, Address, Data, Disassembly
|
||||
if (logging_options & LOG_FRAMES_COUNT)
|
||||
{
|
||||
sprintf(stmp, "f%-6llu ", (long long unsigned int)frameCount);
|
||||
|
||||
j = 0;
|
||||
while (stmp[j] != 0)
|
||||
{
|
||||
txt[i] = stmp[j];
|
||||
i++;
|
||||
j++;
|
||||
}
|
||||
}
|
||||
sb << 'f' << sb_dec(frameCount, -6);
|
||||
|
||||
if (logging_options & LOG_CYCLES_COUNT)
|
||||
{
|
||||
sprintf(stmp, "c%-11llu ", (long long unsigned int)cycleCount);
|
||||
|
||||
j = 0;
|
||||
while (stmp[j] != 0)
|
||||
{
|
||||
txt[i] = stmp[j];
|
||||
i++;
|
||||
j++;
|
||||
}
|
||||
}
|
||||
sb << 'c' << sb_dec(cycleCount, -11);
|
||||
|
||||
if (logging_options & LOG_INSTRUCTIONS_COUNT)
|
||||
{
|
||||
sprintf(stmp, "i%-11llu ", (long long unsigned int)instrCount);
|
||||
|
||||
j = 0;
|
||||
while (stmp[j] != 0)
|
||||
{
|
||||
txt[i] = stmp[j];
|
||||
i++;
|
||||
j++;
|
||||
}
|
||||
}
|
||||
sb << 'i' << sb_dec(instrCount, -11);
|
||||
|
||||
if (logging_options & LOG_REGISTERS)
|
||||
{
|
||||
sprintf(str_axystate, "A:%02X X:%02X Y:%02X S:%02X ", (cpu.A), (cpu.X), (cpu.Y), (cpu.S));
|
||||
StringBuilder sb(str_axystate);
|
||||
sb << "A:" << sb_hex(cpu.A, 2)
|
||||
<< " X:" << sb_hex(cpu.X, 2)
|
||||
<< " Y:" << sb_hex(cpu.Y, 2)
|
||||
<< " S:" << sb_hex(cpu.S, 2)
|
||||
<< ' ';
|
||||
}
|
||||
|
||||
if (logging_options & LOG_PROCESSOR_STATUS)
|
||||
{
|
||||
int tmp = cpu.P ^ 0xFF;
|
||||
sprintf(str_procstatus, "P:%c%c%c%c%c%c%c%c ",
|
||||
'N' | (tmp & 0x80) >> 2,
|
||||
'V' | (tmp & 0x40) >> 1,
|
||||
'U' | (tmp & 0x20),
|
||||
'B' | (tmp & 0x10) << 1,
|
||||
'D' | (tmp & 0x08) << 2,
|
||||
'I' | (tmp & 0x04) << 3,
|
||||
'Z' | (tmp & 0x02) << 4,
|
||||
'C' | (tmp & 0x01) << 5);
|
||||
char *s = str_procstatus;
|
||||
*(s++) = cpu.P & 0x80 ? 'N' : 'n';
|
||||
*(s++) = cpu.P & 0x40 ? 'V' : 'v';
|
||||
*(s++) = cpu.P & 0x20 ? 'U' : 'u';
|
||||
*(s++) = cpu.P & 0x10 ? 'B' : 'b';
|
||||
*(s++) = cpu.P & 0x08 ? 'D' : 'd';
|
||||
*(s++) = cpu.P & 0x04 ? 'I' : 'i';
|
||||
*(s++) = cpu.P & 0x02 ? 'Z' : 'z';
|
||||
*(s++) = cpu.P & 0x01 ? 'C' : 'c';
|
||||
*(s++) = ' ';
|
||||
*(s++) = '\0';
|
||||
}
|
||||
|
||||
if (logging_options & LOG_TO_THE_LEFT)
|
||||
{
|
||||
if (logging_options & LOG_REGISTERS)
|
||||
{
|
||||
j = 0;
|
||||
while (str_axystate[j] != 0)
|
||||
{
|
||||
txt[i] = str_axystate[j];
|
||||
i++;
|
||||
j++;
|
||||
}
|
||||
}
|
||||
sb << str_axystate;
|
||||
if (logging_options & LOG_PROCESSOR_STATUS)
|
||||
{
|
||||
j = 0;
|
||||
while (str_procstatus[j] != 0)
|
||||
{
|
||||
txt[i] = str_procstatus[j];
|
||||
i++;
|
||||
j++;
|
||||
}
|
||||
}
|
||||
sb << str_procstatus;
|
||||
}
|
||||
|
||||
if (logging_options & LOG_CODE_TABBING)
|
||||
|
@ -1067,106 +1007,44 @@ int traceRecord_t::convToText(char *txt, int *len)
|
|||
// add spaces at the beginning of the line according to stack pointer
|
||||
int spaces = (0xFF - cpu.S) & LOG_TABS_MASK;
|
||||
|
||||
while (spaces > 0)
|
||||
{
|
||||
txt[i] = ' ';
|
||||
i++;
|
||||
spaces--;
|
||||
}
|
||||
for (; spaces > 0; spaces--)
|
||||
sb << ' ';
|
||||
}
|
||||
else if (logging_options & LOG_TO_THE_LEFT)
|
||||
{
|
||||
txt[i] = ' ';
|
||||
i++;
|
||||
}
|
||||
sb << ' ';
|
||||
|
||||
if (logging_options & LOG_BANK_NUMBER)
|
||||
{
|
||||
if (cpu.PC >= 0x8000)
|
||||
{
|
||||
sprintf(stmp, "$%02X:%04X: ", bank, cpu.PC);
|
||||
}
|
||||
sb << sb_addr((uint8_t)bank, 2) << ':';
|
||||
else
|
||||
{
|
||||
sprintf(stmp, " $%04X: ", cpu.PC);
|
||||
}
|
||||
sb << " $";
|
||||
}
|
||||
else
|
||||
{
|
||||
sprintf(stmp, "$%04X: ", cpu.PC);
|
||||
}
|
||||
j = 0;
|
||||
while (stmp[j] != 0)
|
||||
{
|
||||
txt[i] = stmp[j];
|
||||
i++;
|
||||
j++;
|
||||
}
|
||||
sb << '$';
|
||||
|
||||
sb << sb_hex(cpu.PC, 4) << ": ";
|
||||
|
||||
for (j = 0; j < opSize; j++)
|
||||
{
|
||||
txt[i] = convToXchar((opCode[j] >> 4) & 0x0F);
|
||||
i++;
|
||||
txt[i] = convToXchar(opCode[j] & 0x0F);
|
||||
i++;
|
||||
txt[i] = ' ';
|
||||
i++;
|
||||
}
|
||||
while (j < 3)
|
||||
{
|
||||
txt[i] = ' ';
|
||||
i++;
|
||||
txt[i] = ' ';
|
||||
i++;
|
||||
txt[i] = ' ';
|
||||
i++;
|
||||
j++;
|
||||
}
|
||||
j = 0;
|
||||
while (asmTxt[j] != 0)
|
||||
{
|
||||
txt[i] = asmTxt[j];
|
||||
i++;
|
||||
j++;
|
||||
}
|
||||
if (callAddr >= 0)
|
||||
{
|
||||
sprintf(stmp, " (from $%04X)", callAddr);
|
||||
sb << sb_hex(opCode[j], 2) << ' ';
|
||||
for (; j < 3; j++)
|
||||
sb << " ";
|
||||
|
||||
j = 0;
|
||||
while (stmp[j] != 0)
|
||||
{
|
||||
txt[i] = stmp[j];
|
||||
i++;
|
||||
j++;
|
||||
}
|
||||
}
|
||||
sb << asmTxt;
|
||||
|
||||
if (callAddr >= 0)
|
||||
sb << " (from " << sb_addr((uint16_t)callAddr) << ')';
|
||||
|
||||
if (!(logging_options & LOG_TO_THE_LEFT))
|
||||
{
|
||||
if (logging_options & LOG_REGISTERS)
|
||||
{
|
||||
j = 0;
|
||||
while (str_axystate[j] != 0)
|
||||
{
|
||||
txt[i] = str_axystate[j];
|
||||
i++;
|
||||
j++;
|
||||
}
|
||||
}
|
||||
sb << str_axystate;
|
||||
if (logging_options & LOG_PROCESSOR_STATUS)
|
||||
{
|
||||
j = 0;
|
||||
while (str_procstatus[j] != 0)
|
||||
{
|
||||
txt[i] = str_procstatus[j];
|
||||
i++;
|
||||
j++;
|
||||
}
|
||||
}
|
||||
sb << str_procstatus;
|
||||
}
|
||||
|
||||
txt[i] = 0;
|
||||
i = int(sb.str() + sb.size() - txt);
|
||||
txt[i] = '\0';
|
||||
|
||||
if (len)
|
||||
{
|
||||
|
@ -1249,12 +1127,15 @@ static void pushToLogBuffer(traceRecord_t &rec)
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool overrun = nextHead == logBufTail;
|
||||
|
||||
logBufHead = nextHead;
|
||||
|
||||
if ( overrunWarningArmed )
|
||||
{ // Don't spam with buffer overrun warning messages,
|
||||
// we will print once if this happens.
|
||||
if (logBufHead == logBufTail)
|
||||
if (overrun)
|
||||
{
|
||||
if ( traceLogWindow )
|
||||
{
|
||||
|
@ -2536,9 +2417,7 @@ TraceLogDiskThread_t::~TraceLogDiskThread_t(void)
|
|||
void TraceLogDiskThread_t::run(void)
|
||||
{
|
||||
char line[256];
|
||||
char buf[8192];
|
||||
int i,idx=0;
|
||||
int blockSize = 4 * 1024;
|
||||
const unsigned blockSize = 4 * 1024;
|
||||
bool dataNeedsFlush = true;
|
||||
bool isPaused = false;
|
||||
|
||||
|
@ -2547,10 +2426,8 @@ void TraceLogDiskThread_t::run(void)
|
|||
setPriority( QThread::HighestPriority );
|
||||
|
||||
#ifdef WIN32
|
||||
logFile = CreateFileA( logFilePath.c_str(), GENERIC_WRITE,
|
||||
0, NULL, OPEN_ALWAYS, FILE_FLAG_WRITE_THROUGH | FILE_FLAG_NO_BUFFERING, NULL );
|
||||
|
||||
if ( logFile == INVALID_HANDLE_VALUE )
|
||||
TraceFileWriter tracer;
|
||||
if (!tracer.open(logFilePath.c_str(), (bool)FCEUI_EmulationPaused()))
|
||||
{
|
||||
char stmp[1024];
|
||||
sprintf( stmp, "Error: Failed to open log file for writing: %s", logFilePath.c_str() );
|
||||
|
@ -2558,6 +2435,11 @@ void TraceLogDiskThread_t::run(void)
|
|||
return;
|
||||
}
|
||||
#else
|
||||
const unsigned bufSize = blockSize * 2;
|
||||
const unsigned flushSize = blockSize;
|
||||
char buf[bufSize];
|
||||
int i, idx=0;
|
||||
|
||||
logFile = open( logFilePath.c_str(), O_CREAT | O_WRONLY | O_TRUNC,
|
||||
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH );
|
||||
|
||||
|
@ -2579,7 +2461,6 @@ void TraceLogDiskThread_t::run(void)
|
|||
|
||||
logBuf = (traceRecord_t *)malloc(size);
|
||||
}
|
||||
idx = 0;
|
||||
|
||||
while ( !isInterruptionRequested() )
|
||||
{
|
||||
|
@ -2588,7 +2469,13 @@ void TraceLogDiskThread_t::run(void)
|
|||
while (logBufHead != logBufTail)
|
||||
{
|
||||
logBuf[logBufTail].convToText(line);
|
||||
logBufTail = (logBufTail + 1) % logBufMax;
|
||||
|
||||
#ifdef WIN32
|
||||
bool success = tracer.writeLine(line);
|
||||
|
||||
/// TODO: Do something on error
|
||||
#else
|
||||
i=0;
|
||||
while ( line[i] != 0 )
|
||||
{
|
||||
|
@ -2596,24 +2483,23 @@ void TraceLogDiskThread_t::run(void)
|
|||
}
|
||||
buf[idx] = '\n'; idx++;
|
||||
|
||||
logBufTail = (logBufTail + 1) % logBufMax;
|
||||
|
||||
if ( idx >= blockSize )
|
||||
if ( idx >= flushSize )
|
||||
{
|
||||
#ifdef WIN32
|
||||
DWORD bytesWritten;
|
||||
WriteFile( logFile, buf, idx, &bytesWritten, NULL ); idx = 0;
|
||||
#else
|
||||
if ( write( logFile, buf, idx ) < 0 )
|
||||
{
|
||||
// HANDLE ERROR TODO
|
||||
}
|
||||
idx = 0;
|
||||
#endif
|
||||
dataNeedsFlush = true;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
bool success = tracer.setPause(isPaused);
|
||||
|
||||
/// TODO: Do something on error
|
||||
#else
|
||||
if (isPaused)
|
||||
{
|
||||
// If paused, the user might be at a breakpoint or doing some
|
||||
|
@ -2621,55 +2507,40 @@ void TraceLogDiskThread_t::run(void)
|
|||
// Only flush data when paused, to keep write efficiency up.
|
||||
if ( idx > 0 )
|
||||
{
|
||||
#ifdef WIN32
|
||||
DWORD bytesWritten;
|
||||
WriteFile( logFile, buf, idx, &bytesWritten, NULL ); idx = 0;
|
||||
#else
|
||||
if ( write( logFile, buf, idx ) < 0 )
|
||||
{
|
||||
// HANDLE ERROR TODO
|
||||
}
|
||||
idx = 0;
|
||||
#endif
|
||||
dataNeedsFlush = true;
|
||||
}
|
||||
if (dataNeedsFlush)
|
||||
{
|
||||
//printf("Flushing Trace Log Disk Buffers\n");
|
||||
#ifdef WIN32
|
||||
FlushFileBuffers( logFile );
|
||||
#else
|
||||
if ( fsync( logFile ) )
|
||||
{
|
||||
printf("Trace Log fsync error\n");
|
||||
}
|
||||
#endif
|
||||
dataNeedsFlush = false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
SDL_Delay(1);
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
tracer.close();
|
||||
#else
|
||||
if ( idx > 0 )
|
||||
{
|
||||
#ifdef WIN32
|
||||
DWORD bytesWritten;
|
||||
WriteFile( logFile, buf, idx, &bytesWritten, NULL ); idx = 0;
|
||||
#else
|
||||
if ( write( logFile, buf, idx ) < 0 )
|
||||
{
|
||||
// HANDLE ERROR TODO
|
||||
}
|
||||
idx = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
if ( logFile != INVALID_HANDLE_VALUE )
|
||||
{
|
||||
CloseHandle( logFile ); logFile = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
#else
|
||||
if ( logFile != -1 )
|
||||
{
|
||||
close(logFile); logFile = -1;
|
||||
|
|
|
@ -0,0 +1,164 @@
|
|||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <type_traits>
|
||||
|
||||
// Faster replacement functions for sprintf. It's uglier, but much faster.
|
||||
class StringBuilder
|
||||
{
|
||||
public:
|
||||
// Helper struct. Do not use directly.
|
||||
template <unsigned Radix, typename T, typename Prefix = nullptr_t>
|
||||
struct IntInfo
|
||||
{
|
||||
T x;
|
||||
int minLen;
|
||||
char leadChar;
|
||||
bool upperCase;
|
||||
Prefix prefix;
|
||||
};
|
||||
|
||||
inline StringBuilder(char *str) : start(str), end(str)
|
||||
{
|
||||
*end = '\0';
|
||||
}
|
||||
|
||||
inline char *str() const
|
||||
{
|
||||
return start;
|
||||
}
|
||||
|
||||
inline size_t size() const
|
||||
{
|
||||
return size_t(end - start);
|
||||
}
|
||||
|
||||
inline StringBuilder &operator <<(char ch)
|
||||
{
|
||||
*(end++) = ch;
|
||||
*end = '\0';
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline StringBuilder &operator <<(const char *src)
|
||||
{
|
||||
size_t len = std::strlen(src);
|
||||
std::memcpy(end, src, len + 1);
|
||||
|
||||
end += len;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<unsigned Radix, typename T, typename Prefix>
|
||||
inline StringBuilder &operator <<(const IntInfo<Radix, T, Prefix> intInfo)
|
||||
{
|
||||
*this << intInfo.prefix;
|
||||
return appendInt<Radix>(intInfo.x, intInfo.minLen, intInfo.leadChar, intInfo.upperCase);
|
||||
}
|
||||
|
||||
protected:
|
||||
inline StringBuilder &operator <<(nullptr_t)
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<unsigned Radix, typename T>
|
||||
StringBuilder &appendInt(T x, int minLen = 0, char leadChar = ' ', bool upperCase = false)
|
||||
{
|
||||
static_assert(Radix >= 2 && Radix <= 36, "Radix must be between 2 and 36");
|
||||
static_assert(std::is_integral<T>::value, "T must be an integral type");
|
||||
|
||||
static const char *upperCaseDigits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
||||
*lowerCaseDigits = "0123456789abcdefghijklmnopqrstuvwxyz";
|
||||
const char *digits = upperCase ? upperCaseDigits : lowerCaseDigits;
|
||||
|
||||
const bool isSigned = std::is_signed<T>::value;
|
||||
bool isNeg = isSigned && x < 0;
|
||||
if (isSigned && isNeg)
|
||||
// This convoluted expression is to silence an unnecessary compiler warning when unsigned
|
||||
x = T(-((typename std::make_signed<T>::type)(x)));
|
||||
|
||||
unsigned i = 0;
|
||||
if (x != 0)
|
||||
{
|
||||
do
|
||||
{
|
||||
end[i++] = digits[x % Radix];
|
||||
x /= Radix;
|
||||
} while (x != 0);
|
||||
}
|
||||
else
|
||||
end[i++] = '0';
|
||||
|
||||
if (isNeg)
|
||||
end[i++] = '-';
|
||||
|
||||
if (minLen < 0)
|
||||
{
|
||||
end[i] = '\0';
|
||||
|
||||
std::reverse(end, end + i);
|
||||
|
||||
for (; i < (unsigned)-minLen; i++)
|
||||
end[i] = leadChar;
|
||||
|
||||
end[i] = '\0';
|
||||
}
|
||||
else
|
||||
{
|
||||
for (; i < (unsigned)minLen; i++)
|
||||
end[i] = leadChar;
|
||||
|
||||
end[i] = '\0';
|
||||
|
||||
std::reverse(end, end + i);
|
||||
}
|
||||
|
||||
end += i;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
char *start;
|
||||
char *end;
|
||||
};
|
||||
|
||||
// Formatters for numbers
|
||||
|
||||
// Generic integer with any radius
|
||||
template<unsigned Radix, typename T>
|
||||
inline StringBuilder::IntInfo<Radix, T> sb_int(T x, int minLen = 0, char leadChar = ' ', bool upperCase = false)
|
||||
{
|
||||
return { x, minLen, leadChar, upperCase, nullptr };
|
||||
}
|
||||
|
||||
// A decimal number
|
||||
template <typename T>
|
||||
inline StringBuilder::IntInfo<10, T> sb_dec(T x, int minLen = 0, char leadChar = ' ')
|
||||
{
|
||||
return { x, minLen, leadChar, false, nullptr };
|
||||
}
|
||||
|
||||
// A hex number
|
||||
template <typename T>
|
||||
inline StringBuilder::IntInfo<16, T> sb_hex(T x, int minLen = 0, bool upperCase = true, char leadChar = '0')
|
||||
{
|
||||
return { x, minLen, leadChar, upperCase, nullptr };
|
||||
}
|
||||
|
||||
// An address of the basic form $%x
|
||||
template <typename T>
|
||||
inline StringBuilder::IntInfo<16, T, char> sb_addr(T x, int minLen = 4, bool upperCase = true)
|
||||
{
|
||||
return { x, minLen, '0', upperCase, '$' };
|
||||
}
|
||||
|
||||
// A literal value of the form #$%x
|
||||
template <typename T>
|
||||
inline StringBuilder::IntInfo<16, T, const char *> sb_lit(T x, int minLen = 2, bool upperCase = true)
|
||||
{
|
||||
return { x, minLen, '0', upperCase, "#$" };
|
||||
}
|
Loading…
Reference in New Issue