From 833554751ac92946bac8c4f46fd5b7c09cc28df2 Mon Sep 17 00:00:00 2001 From: NaOH Date: Sun, 2 Feb 2020 10:34:13 -0800 Subject: [PATCH 1/7] directives for seeking (adjusting position) in output file --- asm6f.c | 388 +++++++++++++++++++++++++++++++++++++++++++---------- readme.txt | 48 +++++++ 2 files changed, 366 insertions(+), 70 deletions(-) diff --git a/asm6f.c b/asm6f.c index 0507082..8991cd5 100644 --- a/asm6f.c +++ b/asm6f.c @@ -1,6 +1,11 @@ /* asm6f - asm6 with modifications for NES/Famicom development */ /* asm6f History: +1.7 + * [NaOH] read iNES/NES2 header from existing .nes file + * [NaOH] seek/skip to specific locations without padding, + allowing overwrites of existing data. + 1.6 + f002 * [nicklausw] Added new directives for INES header generation. * [nicklausw] Put unstable/highly unstable opcode use behind directives, @@ -41,8 +46,9 @@ #include #include #include +#include -#define VERSION "1.6" +#define VERSION "1.7" #define addr firstlabel.value // '$' value #define NOORIGIN -0x40000000 // nice even number so aligning works before origin is defined @@ -71,7 +77,9 @@ typedef struct { ptrdiff_t value; //PC (label), value (equate), param count (macro), funcptr (reserved) // [freem addition (from asm6_sonder.c)] - int pos; // location in file; used to determine bank when exporting labels + // location in output file; used to determine bank when exporting labels + // (only meaningful for labels corresponding to an address.) + int pos; char *line; //for macro or equate, also used to mark unknown label //*next:text->*next:text->.. @@ -85,6 +93,9 @@ typedef struct { void *link; //labels that share the same name (local labels) are chained together } label; +// this is the current program address (in memory). +// it is incremented every time a byte is output. +// accessed in this file via the macro `addr`. label firstlabel={ //'$' label "$",//*name 0,//value @@ -127,10 +138,14 @@ int nes2wram_num = 0; int nes2bram_num = 0; int nes2chrbram_num = 0; +// icfn definitions -- these all use the same signature, though +// not all of these functions make use of the label* argument. + void inesprg(label*, char**); void ineschr(label*, char**); void inesmir(label*, char**); void inesmap(label*, char**); +void incines(label*,char**); void nes2chrram(label*, char**); void nes2prgram(label*, char**); @@ -140,27 +155,19 @@ void nes2vs(label*, char**); void nes2bram(label*, char**); void nes2chrbram(label*, char**); -label *findlabel(char*); -void initlabels(); -label *newlabel(); -void getword(char*,char**,int); -int getvalue(char**); -int getoperator(char**); -int eval(char**,int); -label *getreserved(char**); -int getlabel(char*,char**); -void processline(char*,char*,int); -void listline(char*,char*); -void endlist(); void opcode(label*,char**); void org(label*,char**); void base(label*,char**); void pad(label*,char**); +void seekabs(label*, char**); +void seekrel(label*, char**); +void skiprel(label*, char**); void equ(label*,char**); void equal(label*,char**); void nothing(label*,char**); void include(label*,char**); void incbin(label*,char**); +void incnes(label*,char**); void dw(label*,char**); void db(label*,char**); void dl(label*,char**); @@ -190,9 +197,27 @@ void make_error(label*,char**); void unstable(label*,char**); void hunstable(label*,char**); +// forward declarations +label *findlabel(char*); +void initlabels(); +label *newlabel(); +void getword(char*,char**,int); +int getvalue(char**); +int getoperator(char**); +int eval(char**,int); +label *getreserved(char**); +int getlabel(char*,char**); +void processline(char*,char*,int); +void listline(char*,char*); +void endlist(); +void flush_output(int); + // [freem addition (from asm6_sonder.c)] int filepos=0; +// [NaOH] keeps track of last byte edited in file. +int filesize=0; + enum optypes {ACC,IMM,IND,INDX,INDY,ZPX,ZPY,ABSX,ABSY,ZP,ABS,REL,IMP}; int opsize[]={0,1,2,1,1,1,1,2,2,1,2,1,0}; char ophead[]={0,'#','(','(','(',0,0,0,0,0,0,0,0}; @@ -390,8 +415,12 @@ struct { {"ORG",org}, {"BASE",base}, {"PAD",pad}, + {"SEEKABS",seekabs}, + {"SEEKREL",seekrel}, + {"SKIPREL",skiprel}, {"INCLUDE",include},{"INCSRC",include}, {"INCBIN",incbin},{"BIN",incbin}, + {"INCNES",incnes}, {"HEX",hex}, {"WORD",dw},{"DW",dw},{"DCW",dw},{"DC.W",dw}, {"BYTE",db},{"DB",db},{"DCB",db},{"DC.B",db}, @@ -414,6 +443,7 @@ struct { {"INESCHR",ineschr}, {"INESMIR",inesmir}, {"INESMAP",inesmap}, + {"INCINES",incines}, {"NES2CHRRAM",nes2chrram}, {"NES2PRGRAM",nes2prgram}, {"NES2SUB",nes2sub}, @@ -439,6 +469,9 @@ char DivZero[]="Divide by zero."; char BadAddr[]="Can't determine address."; char NeedName[]="Need a name."; char CantOpen[]="Can't open file."; +char CantSeek[]="Can't seek in file."; +char CantSeekEnum[]="Can't seek in enum mode."; +char InvalidHeader[]="iNES header invalid."; char ExtraENDM[]="ENDM without MACRO."; char ExtraENDR[]="ENDR without REPT."; char ExtraENDE[]="ENDE without ENUM."; @@ -584,6 +617,7 @@ char *my_strupr(char *string) return string; } +// takes a hex character ('0'-'F'), returns value. int hexify(int i) { if(i>='0' && i<='9') { return i-'0'; @@ -1023,6 +1057,7 @@ char *expandline(char *dst,char *src) { src++; dst++; c=*src; + // note that we skip numbers of form '0x...' and '32H' etc. (see getvalue()) } while((c>='0' && c<='9') || (c>='A' && c<='H') || (c>='a' && c<='h')); c=1;//don't terminate yet } else if(c=='"' || c=='\'') {//read past quotes @@ -1174,6 +1209,7 @@ void export_labelfiles() { (*l).type==LABEL || (((*l).type==EQUATE || (*l).type==VALUE) && strlen((*l).name) > 1) ) + // values greater than 0xffff cannot be addresses. && (*l).value < 0x10000 ){ sprintf(str,"$%04X#%s#\n",(unsigned int)(*l).value,(*l).name); @@ -1185,7 +1221,7 @@ void export_labelfiles() { } else{ // ROM - bank=(((*l).pos - 16)/16384); + bank=(((*l).pos - 16 * ines_include)/16384); if (!bankfiles[bank]){ sprintf(strptr,".nes.%X.nl",bank); bankfiles[bank]=fopen(filename,"w"); @@ -1766,6 +1802,7 @@ void processline(char *src,char *errsrc,int errline) { if((*p).type==MACRO) expandmacro(p,&s,errline,errsrc); else + // call assembler function for the given directive. ((icfn)(*p).value)(p,&s); } if(!errmsg) {//check extra garbage @@ -1925,6 +1962,7 @@ int main(int argc,char **argv) { p=0; do { filepos=0; + filesize=0; pass++; if(pass==MAXPASSES || (p==lastlabel)) lastchance=1;//give up on too many tries or no progress made @@ -1951,10 +1989,7 @@ int main(int argc,char **argv) { if(outputfile) { // Be sure last of output file is written properly int result; - if ( fwrite(outputbuff,1,outcount,outputfile) < (size_t)outcount || fflush( outputfile ) ) - fatal_error( "Write error." ); - - i=ftell(outputfile); + flush_output(1); result = fclose(outputfile); outputfile = NULL; // prevent fatal_error() from trying to close file again @@ -1962,7 +1997,7 @@ int main(int argc,char **argv) { fatal_error( "Write error." ); if(!error) { - message("%s written (%i bytes).\n",outputfilename,i); + message("%s written (%i bytes).\n",outputfilename,filesize); } else remove(outputfilename); } else { @@ -1987,61 +2022,55 @@ int main(int argc,char **argv) { #define LISTMAX 8//number of output bytes to show in listing byte listbuff[LISTMAX]; int listcount; -void output(byte *p,int size, int cdlflag) { + +void flush_output(int force) +{ + if(outcount>=BUFFSIZE || force) { + if(fwrite(outputbuff,1,outcount,outputfile)= 16) { - int repeat = size; - while(repeat--) { - if(addr < 0x10000) { - //PRG, mark as either code or data - byte flag = (byte)cdlflag; - fwrite((void*)&flag, 1, 1, cdlfile); - } else { - //CHR data - fwrite("\x0", 1, 1, cdlfile); - } - } - } } - - addr+=size; - - if(nooutput) - return; + + if (nooutput) return; + + // when starting a new pass, reopen file and possibly insert iNES header. if(oldpass!=pass) { oldpass=pass; if(outputfile) fclose(outputfile); - outputfile=fopen(outputfilename,"wb"); - filepos=0; + outputfile=fopen(outputfilename,"wb+"); + assert(filepos == 0); + assert(filesize == 0); outcount=0; if(!outputfile) { errmsg="Can't create output file."; return; } - // (insert iNES if needed) + // (insert iNES header if needed) if (ines_include) { byte ineshdr[16] = {'N','E','S',0x1A, (byte)inesprg_num, (byte)ineschr_num, (byte)(inesmap_num << 4) | inesmir_num, - (byte)(inesmap_num & 0xF0) | (use_nes2 << 3) | (nes2tv_num << 7), + (byte)(inesmap_num & 0xF0) | (use_nes2 << 3) | (nes2tv_num), (byte)(inesmap_num >> 8) | (nes2sub_num << 4), (byte)(inesprg_num >> 8) | ((ineschr_num >> 8) << 4), (byte)(nes2bram_num << 4) | nes2prg_num, @@ -2050,22 +2079,62 @@ void output(byte *p,int size, int cdlflag) { 0,0,0}; if ( fwrite(ineshdr,1,16,outputfile) < (size_t)16 || fflush( outputfile ) ) errmsg="Write error."; - filepos += 16; + filepos = sizeof(ineshdr); + filesize = sizeof(ineshdr); } } +} + +// writes `size` bytes from `p` to output file. +// cdlflag is used when generating cdlfiles. +// It should be one of the cdl types (NONE, DATA, or CODE). +void output(byte *p,int size, int cdlflag) { + + // ensure we have a file that we're outputting to. + output_file(); + + // update cdl file + if(gencdl) { + if(cdlfile && (!ines_include || filepos >= 16)) { + int repeat = size; + while(repeat--) { + if(addr < 0x10000) { + //PRG, mark as either code or data + byte flag = (byte)cdlflag; + fwrite((void*)&flag, 1, 1, cdlfile); + } else { + //CHR data + fwrite("\x0", 1, 1, cdlfile); + } + } + } + } + + // advance the memory address. + addr+=size; + + if (nooutput) + return; + if(!outputfile) return; + + // write data. while(size--) { if(listfile && listcount filesize) filesize = filepos; + + // write to outputbuffer outputbuff[outcount++]=*p; + + // advance source p++; - if(outcount>=BUFFSIZE) { - if(fwrite(outputbuff,1,BUFFSIZE,outputfile) filesize) + { + // past end of file -- pad with zeros + filepos = filesize; + fseek(outputfile, filepos, SEEK_SET); + while (filepos < pos) + { + output(&padbyte, 1, NONE); + } + } + else if (pos < filesize) + { + // within file bounds -- seek there. + filepos = pos; + fseek(outputfile, filepos, SEEK_SET); + } + + if (filepos != ftell(outputfile)) + { + errmsg = CantSeek; + } + + // ensures the above calls to output (for padding) won't modify addr. + addr = prevaddr; +} + //end listing when src=0 char srcbuff[LINEMAX]; void listline(char *src,char *comment) { @@ -2178,6 +2293,42 @@ void base(label *id, char **next) { addr=NOORIGIN;//undefine origin } +void seekabs(label *id, char **next) { + int val; + dependant=0; + val=eval(next,WHOLEEXP); + if(!dependant && !errmsg) + { + output_seek(val); + } +} + +void seekrel(label *id, char **next) { + int val; + dependant=0; + val=eval(next,WHOLEEXP); + if(!dependant && !errmsg) + { + output_seek(filepos + val); + } +} + +void skiprel(label *id, char **next) { + int val; + dependant=0; + val=eval(next,WHOLEEXP); + if(!dependant && !errmsg) + { + output_seek(filepos + val); + addr += val; + } + else + { + // undefine origin -- we don't know where we are anymore. + addr=NOORIGIN; + } +} + //nothing to do (empty line) void nothing(label *id, char **next) { } @@ -2243,6 +2394,25 @@ void incbin(label *id,char **next) { if(f) fclose(f); } +void incnes(label *id, char **next) { + char buf[WORDMAX + 10]; + + // get string-wrapped filename. + buf[0] = '"'; + getfilename(buf + 1, next); + strcpy(buf + strlen(buf), "\""); + + char* s = buf; + incines(id, &s); + + // append `, $10` to the directive. + strcpy(s, ", $10"); + + // do incbin + s = buf; + incbin(id, &s); +} + void hex(label *id,char **next) { char buff[LINEMAX]; char *src; @@ -2790,7 +2960,7 @@ void inesprg(label *id, char **next) { if(inesprg_num < 0 || inesprg_num > 0xFF) errmsg=OutOfRange; - ines_include++; + ines_include = 1; } void ineschr(label *id, char **next) { @@ -2799,7 +2969,7 @@ void ineschr(label *id, char **next) { if(ineschr_num < 0 || ineschr_num > 0xFF) errmsg=OutOfRange; - ines_include++; + ines_include = 1; } void inesmir(label *id, char **next) { @@ -2809,7 +2979,7 @@ void inesmir(label *id, char **next) { if(inesmir_num > 16 || inesmir_num < 0) errmsg=OutOfRange; - ines_include++; + ines_include = 1; } void inesmap(label *id, char **next) { @@ -2819,7 +2989,85 @@ void inesmap(label *id, char **next) { if(inesmap_num > 4095 || inesmap_num < 0) errmsg=OutOfRange; - ines_include++; + ines_include = 1; +} + +void incines(label *id,char **next) { + int filesize; + FILE *f=0; + + char header[ 0x10 ]; + int parse = 0; + + do { + //file open: + getfilename(tmpstr,next); + if(!(f=fopen(tmpstr,"rb"))) { + errmsg=CantOpen; + break; + } + fseek(f,0,SEEK_END); + filesize=ftell(f); + if (filesize < sizeof(header)) { + errmsg = InvalidHeader; + break; + } + //file seek: + fseek(f, 0,SEEK_SET); + // file read: + fread(header, sizeof(header), 1, f); + parse = 1; + } while(0); + if (f) fclose(f); + + // parse the header that was just read. + if (parse) { + ines_include = 1; + + // validate header + if (header[0] != 'N' || header[1] != 'E' || header[2] != 'S' || header[3] != 0x1A) { + errmsg = InvalidHeader; + return; + } + + // ines data + inesprg_num = header[4]; + inesprg_num |= (header[9] & 0x0f) << 8; + + ineschr_num = header[5]; + ineschr_num |= (header[9] & 0xf0) << 4; + + inesmir_num = (header[6] & 0x0f); + + inesmap_num = 0; + inesmap_num |= (header[6] & 0xf0) >> 4; + inesmap_num |= (header[7] & 0xf0); + inesmap_num |= (header[8] & 0x0f); + + // nes2 + if (header[7] & 0x04) + { + // nes2 flag is binary 10b exactly. + // not sure what x1b corresponds to. + errmsg = InvalidHeader; + return; + } + use_nes2 = (header[7] & 0x0c) == 0x08; + nes2tv_num = (header[7] & 0x03) >> 3; + nes2tv_num |= header[12] & ~0x03; + nes2sub_num = (header[8] & 0xf0) >> 4; + nes2bram_num = (header[10] & 0xf0) >> 4; + nes2prg_num = (header[10] & 0x0f); + nes2chrbram_num = (header[11] & 0xf0) >> 4; + nes2chr_num = (header[11] & 0x0f); + + // extra garbage data? + if (header[13] || header[14] || header[15]) + { + errmsg = InvalidHeader; + return; + } + } } void nes2chrram(label *id, char **next) { @@ -2828,7 +3076,7 @@ void nes2chrram(label *id, char **next) { if (nes2chr_num < 0 || nes2chr_num > 16) errmsg=OutOfRange; - ines_include++; use_nes2 = 1; + ines_include = 1; use_nes2 = 1; } void nes2prgram(label *id, char **next) { @@ -2837,7 +3085,7 @@ void nes2prgram(label *id, char **next) { if (nes2prg_num < 0 || nes2prg_num > 16) errmsg=OutOfRange; - ines_include++; use_nes2 = 1; + ines_include = 1; use_nes2 = 1; } void nes2sub(label *id, char **next) { @@ -2846,7 +3094,7 @@ void nes2sub(label *id, char **next) { if (nes2sub_num < 0 || nes2sub_num > 16) errmsg=OutOfRange; - ines_include++; use_nes2 = 1; + ines_include = 1; use_nes2 = 1; } void nes2tv(label *id, char **next) { @@ -2862,12 +3110,12 @@ void nes2tv(label *id, char **next) { if(nes2tv_num > 2 || nes2tv_num < 0) errmsg=OutOfRange; - ines_include++; use_nes2 = 1; + ines_include = 1; use_nes2 = 1; } void nes2vs(label *id, char **next) { nes2vs_num = 1; - ines_include++; use_nes2 = 1; + ines_include = 1; use_nes2 = 1; } void nes2bram(label *id, char **next) { @@ -2876,7 +3124,7 @@ void nes2bram(label *id, char **next) { if (nes2bram_num < 0 || nes2bram_num > 16) errmsg=OutOfRange; - ines_include++; use_nes2 = 1; + ines_include = 1; use_nes2 = 1; } void nes2chrbram(label *id, char **next) { @@ -2885,5 +3133,5 @@ void nes2chrbram(label *id, char **next) { if (nes2chrbram_num < 0 || nes2chrbram_num > 16) errmsg=OutOfRange; - ines_include++; use_nes2 = 1; + ines_include = 1; use_nes2 = 1; } \ No newline at end of file diff --git a/readme.txt b/readme.txt index ff053d7..f17adeb 100644 --- a/readme.txt +++ b/readme.txt @@ -6,6 +6,8 @@ With modifications by freem, nicklausw, and Sour ASM6f is a fork of ASM6, primarily targeted at NES/Famicom development. +See readme-original.txt for the features of ASM6. + -------------------------------------------------------------- Features compared to stock ASM6 -------------------------------------------------------------- @@ -211,6 +213,47 @@ HUNSTABLE HUNSTABLE xaa #7 +INCNES + + Includes the given NES file in its entirety, reading its header. + Fatal error if the header is invalid. + + Identical to: + + INCINES "file.nes" + INCBIN "file.nes", $10 + +SEEKABS x + + Sets the output position in the file. This permits + overwriting data or code that was previously written. + + When seeking past the end of the file, the file will be padded + up to the seek point. + + The program address ($) is not modified by this directive. + +SEEKREL x + + As above, but relative to the current output location. + + The program address ($) is not modified by this directive. + +SKIPREL x + + Skips x bytes without writing -- though padding will be used if skipping + past the end of the file. + + The file position and program address are both modified by this directive. + + There is no SKIPABS directive because the program address ($) and file output + location are not co-absolute; they may be offset from each other and need not agree. + + Identical to: + + SEEKREL x + BASE $+x + -------------------------------------------------------------- iNES directives -------------------------------------------------------------- @@ -250,6 +293,11 @@ NES2BRAM x NES2CHRBRAM x Amount of battery-packed CHR RAM in NES ROM. + +INCINES file.nes + Reads the nes header from the given binary file. + Reads both iNES and NES2 headers. + Fatal error if the header is invalid. -------------------------------------------------------------- loopy's original To-Do List From d7da01d969fda19303a07b1b7b693a07cb94cda2 Mon Sep 17 00:00:00 2001 From: NaOH Date: Mon, 3 Feb 2020 10:25:46 -0800 Subject: [PATCH 2/7] fixed CDL gen when using SEEK and INCNES --- asm6f.c | 251 +++++++++++++++++++++++++++++++++++++++-------------- readme.txt | 9 +- 2 files changed, 193 insertions(+), 67 deletions(-) diff --git a/asm6f.c b/asm6f.c index 8991cd5..70bbdc4 100644 --- a/asm6f.c +++ b/asm6f.c @@ -5,6 +5,7 @@ * [NaOH] read iNES/NES2 header from existing .nes file * [NaOH] seek/skip to specific locations without padding, allowing overwrites of existing data. + * [NaOH] fixed issue when generating .nl file when ORG is unset. 1.6 + f002 * [nicklausw] Added new directives for INES header generation. @@ -54,6 +55,7 @@ #define NOORIGIN -0x40000000 // nice even number so aligning works before origin is defined #define INITLISTSIZE 128 // initial label list size #define BUFFSIZE 8192 // file buffer (inputbuff, outputbuff) size +#define STACKBUFFSIZE 512 // stack-allocated buffer size. #define WORDMAX 128 // used with getword() #define LINEMAX 2048 // plenty of room for nested equates #define MAXPASSES 7 // # of tries before giving up @@ -211,6 +213,8 @@ void processline(char*,char*,int); void listline(char*,char*); void endlist(); void flush_output(int); +char* find_ext(char*); +char* replace_ext(char*, char*); // [freem addition (from asm6_sonder.c)] int filepos=0; @@ -469,6 +473,7 @@ char DivZero[]="Divide by zero."; char BadAddr[]="Can't determine address."; char NeedName[]="Need a name."; char CantOpen[]="Can't open file."; +char CantWrite[]="Write error."; char CantSeek[]="Can't seek in file."; char CantSeekEnum[]="Can't seek in enum mode."; char InvalidHeader[]="iNES header invalid."; @@ -1170,9 +1175,9 @@ void export_labelfiles() { label *l; char str[512]; char filename[512]; + char* strptr; FILE* bankfiles[64]; FILE* ramfile; - char *strptr; for(i=0;i<64;i++){ bankfiles[i]=0; } @@ -1180,13 +1185,9 @@ void export_labelfiles() { // bank files: .bank#hex.nl strcpy(filename, outputfilename); - - strptr=strrchr(filename,'.'); // strptr ='.'ptr - if(strptr) if(strchr( strptr,'\\' )) strptr = 0; // watch out for "dirname.ext\listfile" - if(!strptr) strptr = filename + strlen(str); // strptr -> inputfile extension - strcpy(strptr, ".nes.ram.nl"); - - ramfile=fopen(filename, "w"); + strptr = find_ext(filename); + sprintf(strptr, ".nes.ram.nl"); + ramfile= fopen(filename, "w"); // the bank files are created ad-hoc before being written to. @@ -1209,8 +1210,8 @@ void export_labelfiles() { (*l).type==LABEL || (((*l).type==EQUATE || (*l).type==VALUE) && strlen((*l).name) > 1) ) - // values greater than 0xffff cannot be addresses. - && (*l).value < 0x10000 + // only positive values at most 0xffff can be addresses + && (*l).value < 0x10000 && (*l).value >= 0 ){ sprintf(str,"$%04X#%s#\n",(unsigned int)(*l).value,(*l).name); // puts(str); @@ -1245,17 +1246,12 @@ void export_lua() { int i; label *l; char str[512]; - char filename[512]; + char* filename; FILE* mainfile; - char *strptr; - strcpy(filename, outputfilename); - - strptr=strrchr(filename,'.'); // strptr ='.'ptr - if(strptr) if(strchr( strptr,'\\' )) strptr = 0; // watch out for "dirname.ext\listfile" - if(!strptr) strptr = filename + strlen(str); // strptr -> inputfile extension - strcpy(strptr, ".lua"); - + + filename = replace_ext(outputfilename, ".lua"); mainfile=fopen(filename, "w"); + free(filename); for(i=labelstart;i<=labelend;i++){ l=labellist[i]; @@ -1306,18 +1302,12 @@ void export_mesenlabels() { char* commenttext; label *l; char str[512]; - char filename[512]; - char *strptr; + char* filename; FILE* outfile; - strcpy(filename, outputfilename); - - strptr = strrchr(filename, '.'); // strptr ='.'ptr - if(strptr) if(strchr(strptr, '\\')) strptr = 0; // watch out for "dirname.ext\listfile" - if(!strptr) strptr = filename + strlen(filename); // strptr -> inputfile extension - strcpy(strptr, ".mlb"); - + filename = replace_ext(outputfilename, ".mlb"); outfile = fopen(filename, "w"); + free(filename); int currentcomment = 0; @@ -1837,9 +1827,8 @@ void showhelp(void) { //-------------------------------------------------------------------------------------------- int main(int argc,char **argv) { - char str[512]; int i,notoption; - char *nameptr; + char* tryname; label *p; FILE *f; @@ -1926,36 +1915,29 @@ int main(int argc,char **argv) { if(!inputfilename) fatal_error("No source file specified."); - strcpy(str,inputfilename); - nameptr=strrchr(str,'.');//nameptr='.' ptr - if(nameptr) if(strchr(nameptr,'\\')) nameptr=0;//watch out for "dirname.ext\listfile" - if(!nameptr) nameptr=str+strlen(str);//nameptr=inputfile extension if(!outputfilename) { - strcpy(nameptr,".bin"); - outputfilename=my_strdup(str); + outputfilename = replace_ext(inputfilename, ".bin"); } if(listfilename==true_ptr) { //if listfile was wanted but no name was specified, use srcfile.LST - strcpy(nameptr,".lst"); - listfilename=my_strdup(str); + listfilename = replace_ext(inputfilename, ".lst"); } f=fopen(inputfilename,"rb"); //if srcfile won't open, try some default extensions if(!f) { - strcpy(nameptr,".asm"); - f=fopen(str,"rb"); + tryname = replace_ext(inputfilename, ".asm"); + f=fopen(tryname,"rb"); if(!f) { - strcpy(nameptr,".s"); - f=fopen(str,"rb"); + free(tryname); + tryname = replace_ext(inputfilename, ".s"); + f=fopen(tryname,"rb"); } - if(f) inputfilename=my_strdup(str); + if(f) inputfilename=tryname; } if(f) fclose(f); if(gencdl) { - strcpy(nameptr, ".cdl"); - cdlfilename = my_malloc(strlen(nameptr) + 1); - strcpy(cdlfilename, str); + cdlfilename = replace_ext(inputfilename, ".cdl"); } //main assembly loop: @@ -1977,8 +1959,8 @@ int main(int argc,char **argv) { defaultfiller=DEFAULTFILLER; //reset filler value addr=NOORIGIN;//undefine origin p=lastlabel; - nameptr=inputfilename; - include(0,&nameptr); //start assembling srcfile + tryname=inputfilename; + include(0,&tryname); //start assembling srcfile if(errmsg) { //todo - shouldn't this set error? @@ -1994,7 +1976,7 @@ int main(int argc,char **argv) { result = fclose(outputfile); outputfile = NULL; // prevent fatal_error() from trying to close file again if ( result ) - fatal_error( "Write error." ); + fatal_error( CantWrite); if(!error) { message("%s written (%i bytes).\n",outputfilename,filesize); @@ -2019,6 +2001,42 @@ int main(int argc,char **argv) { return error ? EXIT_FAILURE : 0; } +// returns position of extension in the path, or +// end of string if no extension. +char* find_ext(char* in) +{ + char* strptr = strrchr(in, '.'); + + // watch out for "dirname.ext\listfile" + if (strptr) { + if (strchr( strptr,'\\' ) || strchr(strptr, '/')) { + strptr = 0; + } + } + + // no input extension; skip to end. + if (!strptr) strptr = in + strlen(in); + + return strptr; +} + +// duplicates the given input filename, +// replacing the extension with the provided ext. +// ext need not start with a '.' +char* replace_ext(char* in, char* ext) +{ + char* strptr; + char* out = my_malloc(strlen(in) + strlen(ext) + 1); + strcpy(out, in); + + // search for last '.' + strptr = find_ext(out); + + strcpy(strptr, ext); + + return out; +} + #define LISTMAX 8//number of output bytes to show in listing byte listbuff[LISTMAX]; int listcount; @@ -2026,8 +2044,8 @@ int listcount; void flush_output(int force) { if(outcount>=BUFFSIZE || force) { - if(fwrite(outputbuff,1,outcount,outputfile)= 16)) { int repeat = size; while(repeat--) { @@ -2164,9 +2182,36 @@ void output_seek(int pos) flush_output(1); + // seek in cdlfile. + if (gencdl) + { + if (cdlfile) + { + int cdlpos = (pos < filesize) + ? pos + : filesize; + // cdl file is headerless + if (ines_include) cdlpos -= 0x10; + // clamp + if (cdlpos < 0) cdlpos = 0; + if (fflush(cdlfile)) + { + errmsg = CantWrite; + return; + } + fseek(cdlfile, cdlpos, SEEK_SET); + if (ftell(cdlfile) != cdlpos) + { + errmsg = CantSeek; + return; + } + } + } + // FIXME: we don't actually need a file in order to set the next output position. // Additionally, there is no need to pad until a write actually occurs. + // seek in outputfile. if (pos > filesize) { // past end of file -- pad with zeros @@ -2234,7 +2279,7 @@ void listline(char *src,char *comment) { strcpy(srcbuff,src);//make a copy of the original source line if(comment) { strcat(srcbuff, comment); - if(genmesenlabels && filepos > 0 && addr < 0x10000) { + if(genmesenlabels && filepos > 0 && addr < 0x10000 && addr >= 0) { //save this comment - needed for export addcomment(comment); } @@ -2395,22 +2440,98 @@ void incbin(label *id,char **next) { } void incnes(label *id, char **next) { - char buf[WORDMAX + 10]; + char filename[WORDMAX]; + char buf[WORDMAX + 2]; + int filesize, seekpos, bytesleft, i; + int cdlbytesleft, cdli, cdlstart, cdlflag; + char cdlbuf[STACKBUFFSIZE]; + FILE *f=0; + FILE *cdl=0; // get string-wrapped filename. buf[0] = '"'; - getfilename(buf + 1, next); + getfilename(filename, next); + strcpy(buf + 1, filename); strcpy(buf + strlen(buf), "\""); char* s = buf; incines(id, &s); - - // append `, $10` to the directive. - strcpy(s, ", $10"); - // do incbin - s = buf; - incbin(id, &s); + // include binary + do { + //file open: + if(!(f=fopen(filename,"rb"))) { + errmsg=CantOpen; + break; + } + fseek(f,0,SEEK_END); + filesize=ftell(f); + if (filesize < 0x10) + { + errmsg = SeekOutOfRange; + break; + } + //check for .cdl file (optional) + if (gencdl) + { + s = replace_ext(filename, ".cdl"); + if ((cdl = fopen(s, "rb"))) + { + fseek(cdl, 0, SEEK_END); + cdlbytesleft = ftell(cdl); + fseek(cdl, 0, SEEK_SET); + if (cdlbytesleft == 0) + { + fclose(cdl); + cdl = 0; + } + } + free(s); + } + + //file seek: + seekpos=0x10; + fseek(f,0x10,SEEK_SET); + //get size: + bytesleft=filesize-seekpos; + //read file: + while(bytesleft) { + // clamp to buffer + i = bytesleft; + if (i>BUFFSIZE) i=BUFFSIZE; + if (cdl && i > cdlbytesleft) i = cdlbytesleft; + if (cdl && i > STACKBUFFSIZE) i = STACKBUFFSIZE; + fread(inputbuff,1,i,f); + if (cdl) + { + fread(cdlbuf, 1, i, cdl); + cdlbytesleft -= i; + if (!cdlbytesleft) + { + fclose(cdl); + cdl = 0; + } + + // output in sections of identical cdl. + + cdli = 0; + while(cdli < i) + { + cdlstart = cdli; + cdlflag = cdlbuf[cdli++]; + for (; cdli < i && cdlbuf[cdli] == cdlflag; ++cdli); + output(inputbuff + cdlstart, cdli - cdlstart, cdlflag); + } + } + else + { + output(inputbuff,i,NONE); + } + bytesleft-=i; + } + } while(0); + if(f) fclose(f); + if (cdl) fclose(cdl); } void hex(label *id,char **next) { diff --git a/readme.txt b/readme.txt index f17adeb..62eb8a7 100644 --- a/readme.txt +++ b/readme.txt @@ -218,10 +218,15 @@ INCNES Includes the given NES file in its entirety, reading its header. Fatal error if the header is invalid. - Identical to: + Similar to using both of the following commands: INCINES "file.nes" - INCBIN "file.nes", $10 + INCBIN "file.nes", $10 + + However, if a CDL file (i.e. "file.cdl") exists with the same + basename as as the included .nes file, then that CDL data will + be used. Otherwise, the CDL data is set to NONE. See the .c flag + for details. SEEKABS x From 0982d1a8e665e8a9f7a20668038ad6d6e423972d Mon Sep 17 00:00:00 2001 From: NaOH Date: Wed, 5 Feb 2020 09:45:50 -0800 Subject: [PATCH 3/7] included headers will no longer be modifiedif they don't fit the format. --- asm6f.c | 100 +++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 67 insertions(+), 33 deletions(-) diff --git a/asm6f.c b/asm6f.c index 70bbdc4..0ff9200 100644 --- a/asm6f.c +++ b/asm6f.c @@ -56,6 +56,7 @@ #define INITLISTSIZE 128 // initial label list size #define BUFFSIZE 8192 // file buffer (inputbuff, outputbuff) size #define STACKBUFFSIZE 512 // stack-allocated buffer size. +#define HEADERSIZE 0x10 // size of an ines/nes2 header #define WORDMAX 128 // used with getword() #define LINEMAX 2048 // plenty of room for nested equates #define MAXPASSES 7 // # of tries before giving up @@ -524,6 +525,8 @@ FILE *outputfile=0; FILE *cdlfile=0; byte outputbuff[BUFFSIZE]; byte inputbuff[BUFFSIZE]; +byte ines_extension[HEADERSIZE]; +byte ines_extension_mask[HEADERSIZE] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; int outcount;//bytes waiting in outputbuff label **labellist; //array of label pointers (list starts from center and grows outward) int labels;//# of labels in labellist @@ -1222,7 +1225,7 @@ void export_labelfiles() { } else{ // ROM - bank=(((*l).pos - 16 * ines_include)/16384); + bank=(((*l).pos - HEADERSIZE)/16384); if (!bankfiles[bank]){ sprintf(strptr,".nes.%X.nl",bank); bankfiles[bank]=fopen(filename,"w"); @@ -1324,7 +1327,7 @@ void export_mesenlabels() { if(l->type == LABEL) { //Labels in the actual code - if(l->pos < 16) { + if(l->pos < HEADERSIZE) { //Ignore file header continue; } @@ -1336,8 +1339,8 @@ void export_mesenlabels() { if(c->pos < l->pos) { //This comment is for a line before the current code label, write it to the file right away - if(c->pos >= 16) { - sprintf(str, "P:%04X::", (unsigned int)c->pos - 16); + if(c->pos >= HEADERSIZE) { + sprintf(str, "P:%04X::", (unsigned int)c->pos - HEADERSIZE); fwrite((const void *)str, 1, strlen(str), outfile); fwrite((const void *)c->text, 1, strlen(c->text), outfile); fwrite("\n", 1, 1, outfile); @@ -1354,7 +1357,7 @@ void export_mesenlabels() { } //Dump the label - sprintf(str, "P:%04X:%s", (unsigned int)(l->pos - 16), l->name); + sprintf(str, "P:%04X:%s", (unsigned int)(l->pos - HEADERSIZE), l->name); fwrite((const void *)str, 1, strlen(str), outfile); if(commenttext) { @@ -2084,18 +2087,26 @@ void output_file() // (insert iNES header if needed) if (ines_include) { - byte ineshdr[16] = {'N','E','S',0x1A, + byte ineshdr[HEADERSIZE] = {'N','E','S',0x1A, (byte)inesprg_num, (byte)ineschr_num, (byte)(inesmap_num << 4) | inesmir_num, - (byte)(inesmap_num & 0xF0) | (use_nes2 << 3) | (nes2tv_num), + (byte)(inesmap_num & 0xF0) | (use_nes2 << 3) | (nes2vs_num), (byte)(inesmap_num >> 8) | (nes2sub_num << 4), (byte)(inesprg_num >> 8) | ((ineschr_num >> 8) << 4), (byte)(nes2bram_num << 4) | nes2prg_num, (byte)(nes2chrbram_num << 4) | nes2chr_num, (byte)nes2tv_num, 0,0,0}; - if ( fwrite(ineshdr,1,16,outputfile) < (size_t)16 || fflush( outputfile ) ) + // extensions + for (unsigned i = 0; i < HEADERSIZE; ++i) + { + ineshdr[i] &= ~ines_extension_mask[i]; + ineshdr[i] |= ines_extension_mask[i] & ines_extension[i]; + } + + // write header + if ( fwrite(ineshdr,1,HEADERSIZE,outputfile) < (size_t)HEADERSIZE || fflush( outputfile ) ) errmsg=CantWrite; filepos = sizeof(ineshdr); filesize = sizeof(ineshdr); @@ -2113,7 +2124,7 @@ void output(byte *p,int size, int cdlflag) { // update cdl file if(gencdl && !nooutput) { - if(cdlfile && (!ines_include || filepos >= 16)) { + if(cdlfile && (!ines_include || filepos >= HEADERSIZE)) { int repeat = size; while(repeat--) { if(addr < 0x10000) { @@ -2191,7 +2202,7 @@ void output_seek(int pos) ? pos : filesize; // cdl file is headerless - if (ines_include) cdlpos -= 0x10; + if (ines_include) cdlpos -= HEADERSIZE; // clamp if (cdlpos < 0) cdlpos = 0; if (fflush(cdlfile)) @@ -2466,7 +2477,7 @@ void incnes(label *id, char **next) { } fseek(f,0,SEEK_END); filesize=ftell(f); - if (filesize < 0x10) + if (filesize < HEADERSIZE) { errmsg = SeekOutOfRange; break; @@ -2490,8 +2501,8 @@ void incnes(label *id, char **next) { } //file seek: - seekpos=0x10; - fseek(f,0x10,SEEK_SET); + seekpos=HEADERSIZE; + fseek(f,HEADERSIZE,SEEK_SET); //get size: bytesleft=filesize-seekpos; //read file: @@ -3107,17 +3118,21 @@ void inesmap(label *id, char **next) { inesmap_num=eval(next, WHOLEEXP); //ines 2.0 allows for some big numbers... - if(inesmap_num > 4095 || inesmap_num < 0) + if(inesmap_num > 0xfff || inesmap_num < 0) errmsg=OutOfRange; ines_include = 1; + if (inesmap_num > 0xff) + { + ines_extension_mask[8] &= ~0x0f; + } } void incines(label *id,char **next) { int filesize; FILE *f=0; - char header[ 0x10 ]; + char header[ HEADERSIZE ]; int parse = 0; do { @@ -3163,31 +3178,37 @@ void incines(label *id,char **next) { inesmap_num = 0; inesmap_num |= (header[6] & 0xf0) >> 4; inesmap_num |= (header[7] & 0xf0); - inesmap_num |= (header[8] & 0x0f); + inesmap_num |= (header[8] & 0x0f) << 4; // nes2 - if (header[7] & 0x04) + use_nes2 = (header[7] & 0x0c) == 0x08; + if (use_nes2) { - // nes2 flag is binary 10b exactly. - // not sure what x1b corresponds to. - errmsg = InvalidHeader; - return; + nes2vs_num = header[7] & 0x01; + nes2sub_num = (header[8] & 0xf0) >> 4; + nes2bram_num = (header[10] & 0xf0) >> 4; + nes2prg_num = (header[10] & 0x0f); + nes2chrbram_num = (header[11] & 0xf0) >> 4; + nes2chr_num = (header[11] & 0x0f); + nes2tv_num = header[12] & 0x03; } - use_nes2 = (header[7] & 0x0c) == 0x08; - nes2tv_num = (header[7] & 0x03) >> 3; - nes2tv_num |= header[12] & ~0x03; - nes2sub_num = (header[8] & 0xf0) >> 4; - nes2bram_num = (header[10] & 0xf0) >> 4; - nes2prg_num = (header[10] & 0x0f); - nes2chrbram_num = (header[11] & 0xf0) >> 4; - nes2chr_num = (header[11] & 0x0f); - // extra garbage data? - if (header[13] || header[14] || header[15]) + // there are some bits in the header that we don't + // understand, so to ensure these parts of the header are preserved, + // we store them as unrecognized extension data. + byte mask[] = { + 0 , 0 , 0 , 0 , + 0 , 0 , 0 , 0x06, + 0 , 0 , 0 , 0 , + 0xff, 0xff, 0xff, 0xff + }; + if (!use_nes2) { - errmsg = InvalidHeader; - return; + // iNES format only strictly defines up to byte 7. + memset(mask + 0x8, 0xff, HEADERSIZE - 0x8); } + memcpy(ines_extension, header, HEADERSIZE); + memcpy(ines_extension_mask, mask, HEADERSIZE); } } @@ -3198,6 +3219,8 @@ void nes2chrram(label *id, char **next) { errmsg=OutOfRange; ines_include = 1; use_nes2 = 1; + ines_extension_mask[7] &= ~0x0c; + ines_extension_mask[11] &= ~0x0f; } void nes2prgram(label *id, char **next) { @@ -3207,6 +3230,8 @@ void nes2prgram(label *id, char **next) { errmsg=OutOfRange; ines_include = 1; use_nes2 = 1; + ines_extension_mask[7] &= ~0x0c; + ines_extension_mask[10] &= ~0x0f; } void nes2sub(label *id, char **next) { @@ -3216,6 +3241,8 @@ void nes2sub(label *id, char **next) { errmsg=OutOfRange; ines_include = 1; use_nes2 = 1; + ines_extension_mask[7] &= ~0x0c; + ines_extension_mask[8] &= ~0xf0; } void nes2tv(label *id, char **next) { @@ -3232,11 +3259,14 @@ void nes2tv(label *id, char **next) { errmsg=OutOfRange; ines_include = 1; use_nes2 = 1; + ines_extension_mask[7] &= ~0x0c; + ines_extension_mask[12] &= ~0x03; } void nes2vs(label *id, char **next) { nes2vs_num = 1; ines_include = 1; use_nes2 = 1; + ines_extension_mask[7] &= ~0x0d; } void nes2bram(label *id, char **next) { @@ -3246,6 +3276,8 @@ void nes2bram(label *id, char **next) { errmsg=OutOfRange; ines_include = 1; use_nes2 = 1; + ines_extension_mask[7] &= ~0x0c; + ines_extension_mask[10] &= ~0xf0; } void nes2chrbram(label *id, char **next) { @@ -3255,4 +3287,6 @@ void nes2chrbram(label *id, char **next) { errmsg=OutOfRange; ines_include = 1; use_nes2 = 1; + ines_extension_mask[7] &= ~0x0c; + ines_extension_mask[11] &= ~0xf0; } \ No newline at end of file From 8a53191e79dbf38a934c1d2f4002d726e1235495 Mon Sep 17 00:00:00 2001 From: NaOH Date: Sat, 8 Feb 2020 17:18:50 -0800 Subject: [PATCH 4/7] IPS output and compare value support --- asm6f.c | 460 ++++++++++++++++++++++++++++++++++++++++++++++++----- readme.txt | 17 ++ 2 files changed, 441 insertions(+), 36 deletions(-) diff --git a/asm6f.c b/asm6f.c index 0ff9200..3e9b35e 100644 --- a/asm6f.c +++ b/asm6f.c @@ -56,6 +56,7 @@ #define INITLISTSIZE 128 // initial label list size #define BUFFSIZE 8192 // file buffer (inputbuff, outputbuff) size #define STACKBUFFSIZE 512 // stack-allocated buffer size. +#define IPS_RLE_EXTRACT 0x20 // if this many bytes in a row is encountered, use RLE encoding. #define HEADERSIZE 0x10 // size of an ines/nes2 header #define WORDMAX 128 // used with getword() #define LINEMAX 2048 // plenty of room for nested equates @@ -171,6 +172,7 @@ void nothing(label*,char**); void include(label*,char**); void incbin(label*,char**); void incnes(label*,char**); +void clearpatch(label*,char**); void dw(label*,char**); void db(label*,char**); void dl(label*,char**); @@ -194,6 +196,8 @@ void ende(label*,char**); void ignorenl(label*,char**); // [freem addition] "ignorenl" void endinl(label*,char**); // [freem addition] "endinl" void fillval(label*,char**); +void compfill(label*,char**); // [NaOH addition] +void endcompfill(label*,char**); // [NaOH addition] void expandmacro(label*,char**,int,char*); void expandrept(int,char*); void make_error(label*,char**); @@ -223,6 +227,29 @@ int filepos=0; // [NaOH] keeps track of last byte edited in file. int filesize=0; +// ips offset for the start of the output buffer. +// if output buffer is empty, this should agree with filepos. +int ips_outpos=0; + +typedef struct { + int offset; + int length; + byte* contents; // if nullptr, rle. + byte rle_content; + int suppress; // if true, don't write to ips output. +} ips_hunk; + +// list of pointers to IPS hunks +ips_hunk** ips_hunk_list; +int ips_hunk_list_c; +int ips_hunk_list_max = 0; + +void ips_append_hunk(); +// sorts and combines adjacent/overlapping hunks. +void ips_simplify_hunks(); +// writes patch to output file +void ips_write(FILE* file); + enum optypes {ACC,IMM,IND,INDX,INDY,ZPX,ZPY,ABSX,ABSY,ZP,ABS,REL,IMP}; int opsize[]={0,1,2,1,1,1,1,2,2,1,2,1,0}; char ophead[]={0,'#','(','(','(',0,0,0,0,0,0,0,0}; @@ -426,6 +453,7 @@ struct { {"INCLUDE",include},{"INCSRC",include}, {"INCBIN",incbin},{"BIN",incbin}, {"INCNES",incnes}, + {"CLEARPATCH",clearpatch}, {"HEX",hex}, {"WORD",dw},{"DW",dw},{"DCW",dw},{"DC.W",dw}, {"BYTE",db},{"DB",db},{"DCB",db},{"DC.B",db}, @@ -441,6 +469,8 @@ struct { {"IGNORENL",ignorenl}, {"ENDINL",endinl}, {"FILLVALUE",fillval}, + {"COMPARE", compfill}, + {"ENDCOMPARE", endcompfill}, {"DL",dl}, {"DH",dh}, {"ERROR",make_error}, @@ -473,8 +503,10 @@ char MissingOperand[]="Missing operand."; char DivZero[]="Divide by zero."; char BadAddr[]="Can't determine address."; char NeedName[]="Need a name."; +char CantCreateFile[]="Can't create output file."; char CantOpen[]="Can't open file."; char CantWrite[]="Write error."; +char CompFailed[]="Compare failed. Byte at 0x________ was 0x++."; char CantSeek[]="Can't seek in file."; char CantSeekEnum[]="Can't seek in enum mode."; char InvalidHeader[]="iNES header invalid."; @@ -511,6 +543,7 @@ int skipline[IFNESTS];//1 on an IF statement that is false const char *errmsg; char *inputfilename=0; char *outputfilename=0; +char *ipsfilename=0; char *listfilename=0; char *cdlfilename=0; int verboselisting=0;//expand REPT loops in listing @@ -518,6 +551,7 @@ int genfceuxnl=0;//[freem addition] generate FCEUX .nl files for symbolic debugg int genmesenlabels=0; //generate label files for use with Mesen int gencdl=0; //generate CDL file int genlua=0;//generate lua symbol file +int genips=0; //[NaOH] generate .ips patch. const char *listerr=0;//error message for list file label *labelhere;//points to the label being defined on the current line (for EQU, =, etc) FILE *listfile=0; @@ -541,6 +575,7 @@ int lastcommentpos = -1; int nooutput=0;//supress output (use with ENUM) int nonl=0;//[freem addition] supress output to .nl files int defaultfiller;//default fill value +int comparefiller=0; // compare on write to defaultfiller int insidemacro=0;//macro/rept is being expanded int verbose=1; @@ -1824,6 +1859,7 @@ void showhelp(void) { puts("\t-f\t\texport Lua symbol file"); puts("\t-c\t\texport .cdl for use with FCEUX/Mesen"); puts("\t-m\t\texport Mesen-compatible label file (.mlb)\n"); + puts("\t-i\t\tbuild .ips format patch file instead of binary."); puts("See README.TXT for more info.\n"); } @@ -1900,6 +1936,9 @@ int main(int argc,char **argv) { case 'f': genlua=1; break; + case 'i': + genips=1; + break; default: fatal_error("unknown option: %s",argv[i]); } @@ -1921,6 +1960,10 @@ int main(int argc,char **argv) { if(!outputfilename) { outputfilename = replace_ext(inputfilename, ".bin"); } + + if(genips) { + ipsfilename = replace_ext(outputfilename, ".ips"); + } if(listfilename==true_ptr) { //if listfile was wanted but no name was specified, use srcfile.LST listfilename = replace_ext(inputfilename, ".lst"); @@ -1985,11 +2028,27 @@ int main(int argc,char **argv) { message("%s written (%i bytes).\n",outputfilename,filesize); } else remove(outputfilename); - } else { + } else if (!genips) { if(!error) fputs("nothing to do!", stderr); error = 1; } + + if (genips) + { + puts(ipsfilename); + FILE* ipsfile = fopen(ipsfilename, "wb"); + if (!ipsfile) + { + errmsg = CantWrite; + } + else + { + ips_write(ipsfile); + fclose(ipsfile); + } + } + if(listfile) listline(0,0); @@ -2044,15 +2103,310 @@ char* replace_ext(char* in, char* ext) byte listbuff[LISTMAX]; int listcount; +ips_hunk* malloc_ips_hunk_regular(size_t offset, byte* p, size_t count) +{ + assert(count > 0); + + ips_hunk* hunk = (ips_hunk*)my_malloc(sizeof(ips_hunk)); + hunk->offset = offset; + hunk->length = count; + hunk->contents = (byte*)my_malloc(count); + hunk->suppress = 0; + memcpy(hunk->contents, p, count); + return hunk; +} + +ips_hunk* malloc_ips_hunk_rle(size_t offset, byte b, size_t count) +{ + assert(count > 0); + + ips_hunk* hunk = (ips_hunk*)my_malloc(sizeof(ips_hunk)); + hunk->offset = offset; + hunk->length = count; + hunk->contents = 0; + hunk->rle_content = b; + hunk->suppress = 0; + return hunk; +} + +void ips_append_hunk(ips_hunk* hunk) +{ + // resize array + if (ips_hunk_list_max == 0) + { + ips_hunk_list_max = 0x40; + ips_hunk_list = (ips_hunk**) my_malloc(sizeof(ips_hunk*) * ips_hunk_list_max); + } + else if (ips_hunk_list_max <= ips_hunk_list_c + 1) + { + // copy to new array, 4x larger. + ips_hunk** prev = ips_hunk_list; + ips_hunk_list_max *= 4; + ips_hunk_list = (ips_hunk**) my_malloc(sizeof(ips_hunk*) * ips_hunk_list_max); + memcpy(ips_hunk_list, prev, ips_hunk_list_c * sizeof(ips_hunk*)); + free(prev); + } + + ips_hunk_list[ips_hunk_list_c++] = hunk; +} + +void write_ips_hunk_regular(FILE* f, size_t offset, byte *p, size_t count) +{ + assert(count <= 0xffff); + byte header[5] = { + (offset & 0xff0000) >> 16, + (offset & 0xff00) >> 8, + (offset & 0xff), + (count & 0xff00) >> 8, + count & 0xff + }; + + // write header + if ( fwrite(header, 5, 1, f) < 1 ) + { + errmsg=CantWrite; + return; + } + + // write payload + if (fwrite(p,1,count,f) < count || fflush(f)) + { + errmsg=CantWrite; + } +} + +void write_ips_hunk_rle(FILE* f, size_t offset, byte b, size_t count) +{ + assert(count <= 0xffff); + byte header[8] = { + (offset & 0xff0000) >> 16, + (offset & 0xff00) >> 8, + (offset & 0xff), + 0, + 0, + (count & 0xff00) >> 8, + count & 0xff, + b + }; + + if ( + fwrite(header, 8, 1, f) < 1 + || fflush(f) + ) + { + errmsg=CantWrite; + } +} + +// writes all hunks to the output file. +void ips_write(FILE* output) +{ + assert(genips); + + // write IPS header. + if ( fwrite("PATCH", 5, 1, output) < 1 ) + { + errmsg=CantWrite; + return; + } + + // write hunks + for (size_t i = 0; i < ips_hunk_list_c; ++i) + { + ips_hunk* hunk = ips_hunk_list[i]; + if (hunk->suppress) continue; + if (hunk->contents) + { + write_ips_hunk_regular(output, hunk->offset, hunk->contents, hunk->length); + } + else + { + write_ips_hunk_rle(output, hunk->offset, hunk->rle_content, hunk->length); + } + } + + if ( fwrite("EOF", 3, 1, output) < 1 ) + { + errmsg=CantWrite; + return; + } +} + +void flush_output_ips() +{ + // write IPS hunks. + if (outcount == 0) + { + // do nothing. + } + else if (outcount <= 3) + { + // RLE is never a good choice for hunks this small. + ips_append_hunk(malloc_ips_hunk_regular(ips_outpos, outputbuff, outcount)); + } + else + { + // split into RLE and non-RLE hunks + size_t hunk_start = 0; + size_t rle_start = 0; + byte rle_cmp = outputbuff[0]; + for (size_t i = 0; i <= outcount; ++i) + { + int b = (i == outcount) + ? -1 + : outputbuff[i]; + if (b != (int) rle_cmp) + { + if (i - rle_start >= IPS_RLE_EXTRACT) + { + if (rle_start > hunk_start) + { + ips_append_hunk(malloc_ips_hunk_regular( + ips_outpos + hunk_start, outputbuff + hunk_start, rle_start - hunk_start + )); + } + ips_append_hunk(malloc_ips_hunk_rle( + ips_outpos + rle_start, rle_cmp, i - rle_start + )); + hunk_start = i; + } + else if (b == -1) + { + ips_append_hunk(malloc_ips_hunk_regular( + ips_outpos + hunk_start, outputbuff + hunk_start, i - hunk_start + )); + break; + } + + rle_start = i; + rle_cmp = b; + } + } + } + ips_outpos += outcount; +} + +// removes all hunks from the list. +void ips_clear() +{ + // force output flush because + // output buffer may be written to ips eventually. + flush_output(1); + + // clear list. + for (size_t i = 0; i < ips_hunk_list_c; ++i) + { + ips_hunk* hunk = ips_hunk_list[i]; + if (hunk->contents) + { + free(hunk->contents); + } + free(hunk); + } + + ips_hunk_list_c = 0; +} + +// gets cmp value in ips file +int ips_get_cmp_value(size_t location) +{ + assert(genips); + int value = -1; + for (size_t i = 0; i < ips_hunk_list_c; ++i) + { + ips_hunk* hunk = ips_hunk_list[i]; + if (hunk->offset <= location && location < hunk->offset + hunk->length) + { + if (hunk->contents) + { + value = hunk->contents[location - hunk->offset]; + } + else + { + value = hunk->rle_content; + } + } + } + return value; +} + +// gets output value at the given position +// negative value if couldn't read it or not set +int get_cmp_value(size_t location) +{ + if (genips) + { + return ips_get_cmp_value(location); + } + else + { + // TODO optimize + int b; + size_t prevseek = ftell(outputfile); + fseek(outputfile, location, SEEK_SET); + if (fread(&b,1,1,outputfile) < 1) + { + b = -1; + } + fseek(outputfile, prevseek, SEEK_SET); + return b; + } +} + +// flushes output buffer void flush_output(int force) { - if(outcount>=BUFFSIZE || force) { - if(fwrite(outputbuff,1,outcount,outputfile)=BUFFSIZE || force || (genips && outcount >= 0xffff)) { + + // flush ips output + if (genips) { + flush_output_ips(); + } + + // flush binary output + if (outputfile) + { + if(fwrite(outputbuff,1,outcount,outputfile)= 0) + { + errmsg = CompFailed; + int loc = filepos + outcount; + sprintf(CompFailed + 26, "%0*x was 0x%x.", 6, loc, b); + return; + } + } + + // write to outputbuffer + outputbuff[outcount++]=*p; + + p++; + + flush_output(0); + + if (errmsg) + { + return; + } + } +} + // checks if we need to start a new file for outputting to. void output_file() { @@ -2075,13 +2429,21 @@ void output_file() // when starting a new pass, reopen file and possibly insert iNES header. if(oldpass!=pass) { oldpass=pass; + + if (genips) + { + ips_clear(); + ips_outpos = 0; + } + + // binary output. if(outputfile) fclose(outputfile); outputfile=fopen(outputfilename,"wb+"); assert(filepos == 0); assert(filesize == 0); outcount=0; if(!outputfile) { - errmsg="Can't create output file."; + errmsg=CantCreateFile; return; } @@ -2106,8 +2468,7 @@ void output_file() } // write header - if ( fwrite(ineshdr,1,HEADERSIZE,outputfile) < (size_t)HEADERSIZE || fflush( outputfile ) ) - errmsg=CantWrite; + output_buffer(ineshdr, HEADERSIZE); filepos = sizeof(ineshdr); filesize = sizeof(ineshdr); } @@ -2145,25 +2506,20 @@ void output(byte *p,int size, int cdlflag) { if (nooutput) return; - if(!outputfile) return; + if(!outputfile || !genips) return; // write data. + output_buffer(p, size); + while(size--) { if(listfile && listcount filesize) filesize = filepos; - - // write to outputbuffer - outputbuff[outcount++]=*p; - - // advance source - p++; - - flush_output(0); } } @@ -2187,7 +2543,7 @@ void output_seek(int pos) errmsg = CantSeekEnum; return; } - + // ensure output file exists. output_file(); @@ -2221,29 +2577,36 @@ void output_seek(int pos) // FIXME: we don't actually need a file in order to set the next output position. // Additionally, there is no need to pad until a write actually occurs. - - // seek in outputfile. - if (pos > filesize) + + if (outputfile) { - // past end of file -- pad with zeros - filepos = filesize; - fseek(outputfile, filepos, SEEK_SET); - while (filepos < pos) + // seek in outputfile. + if (pos > filesize) { - output(&padbyte, 1, NONE); + // past end of file -- pad with zeros + filepos = filesize; + ips_outpos = filepos; + fseek(outputfile, filepos, SEEK_SET); + while (filepos < pos) + { + output(&padbyte, 1, NONE); + } + } + else if (pos < filesize) + { + // within file bounds -- seek there. + filepos = pos; + fseek(outputfile, filepos, SEEK_SET); } - } - else if (pos < filesize) - { - // within file bounds -- seek there. - filepos = pos; - fseek(outputfile, filepos, SEEK_SET); - } - if (filepos != ftell(outputfile)) - { - errmsg = CantSeek; - } + if (filepos != ftell(outputfile)) + { + errmsg = CantSeek; + } + } + + filepos = pos; + ips_outpos = pos; // ensures the above calls to output (for padding) won't modify addr. addr = prevaddr; @@ -2545,6 +2908,23 @@ void incnes(label *id, char **next) { if (cdl) fclose(cdl); } +void clearpatch(label *id, char **next) +{ + if (genips) + { + flush_output(1); + + // mark sections as non-output. + // we don't remove them because we may wish to + // read from them (e.g. compare) + for (size_t i = 0; i < ips_hunk_list_c; ++i) + { + ips_hunk* hunk = ips_hunk_list[i]; + hunk->suppress = 1; + } + } +} + void hex(label *id,char **next) { char buff[LINEMAX]; char *src; @@ -3066,6 +3446,14 @@ void fillval(label *id,char **next) { defaultfiller=eval(next,WHOLEEXP); } +void compfill(label *id,char **next) { + comparefiller=1; +} + +void endcompfill(label *id,char **next) { + comparefiller=0; +} + void make_error(label *id,char **next) { char *s=*next; reverse(tmpstr,s+strspn(s,whitesp2)); //eat whitesp, quotes off both ends diff --git a/readme.txt b/readme.txt index 62eb8a7..ea8cf0a 100644 --- a/readme.txt +++ b/readme.txt @@ -40,6 +40,7 @@ Options: -f export Lua symbol file -c export .cdl for use with FCEUX/Mesen -m export Mesen-compatible label file (.mlb) + -i build .ips patch file instead of binary output. Default output is .bin Default listing is .lst @@ -259,6 +260,22 @@ SKIPREL x SEEKREL x BASE $+x +COMPARE / ENDCOMPARE + + when enabled, every byte that overwrites a previously written byte + will be compared to the current fillvalue (see FILLVALUE), and if + they differ, a fatal error will be thrown. + + This is useful, for example, to assert that one doesn't overwrite + any important data while patching. + +CLEARPATCH + + Clears all data written so far in the .ips patch. + You can use this to specify that the previous data written is not part of the patch. + This is ignored when the -i flag is not used. + + -------------------------------------------------------------- iNES directives -------------------------------------------------------------- From a10b5ea0438765ca17018e7ce07914ca6d2978fd Mon Sep 17 00:00:00 2001 From: NaOH Date: Sun, 9 Feb 2020 13:13:30 -0800 Subject: [PATCH 5/7] IPS ordering --- asm6f.c | 197 +++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 168 insertions(+), 29 deletions(-) diff --git a/asm6f.c b/asm6f.c index 3e9b35e..12fa903 100644 --- a/asm6f.c +++ b/asm6f.c @@ -231,22 +231,20 @@ int filesize=0; // if output buffer is empty, this should agree with filepos. int ips_outpos=0; -typedef struct { +typedef struct ips_hunk_t { int offset; int length; byte* contents; // if nullptr, rle. byte rle_content; int suppress; // if true, don't write to ips output. + struct ips_hunk_t* next; } ips_hunk; -// list of pointers to IPS hunks -ips_hunk** ips_hunk_list; -int ips_hunk_list_c; -int ips_hunk_list_max = 0; +// linked list of IPS hunks +ips_hunk* ips_hunk_head = 0; +ips_hunk* ips_hunk_tail = 0; void ips_append_hunk(); -// sorts and combines adjacent/overlapping hunks. -void ips_simplify_hunks(); // writes patch to output file void ips_write(FILE* file); @@ -2112,6 +2110,7 @@ ips_hunk* malloc_ips_hunk_regular(size_t offset, byte* p, size_t count) hunk->length = count; hunk->contents = (byte*)my_malloc(count); hunk->suppress = 0; + hunk->next = 0; memcpy(hunk->contents, p, count); return hunk; } @@ -2126,28 +2125,24 @@ ips_hunk* malloc_ips_hunk_rle(size_t offset, byte b, size_t count) hunk->contents = 0; hunk->rle_content = b; hunk->suppress = 0; + hunk->next = 0; return hunk; } void ips_append_hunk(ips_hunk* hunk) { - // resize array - if (ips_hunk_list_max == 0) + hunk->next = 0; + + if (ips_hunk_head == 0) { - ips_hunk_list_max = 0x40; - ips_hunk_list = (ips_hunk**) my_malloc(sizeof(ips_hunk*) * ips_hunk_list_max); + ips_hunk_head = hunk; } - else if (ips_hunk_list_max <= ips_hunk_list_c + 1) + else { - // copy to new array, 4x larger. - ips_hunk** prev = ips_hunk_list; - ips_hunk_list_max *= 4; - ips_hunk_list = (ips_hunk**) my_malloc(sizeof(ips_hunk*) * ips_hunk_list_max); - memcpy(ips_hunk_list, prev, ips_hunk_list_c * sizeof(ips_hunk*)); - free(prev); + ips_hunk_tail->next = hunk; } - ips_hunk_list[ips_hunk_list_c++] = hunk; + ips_hunk_tail = hunk; } void write_ips_hunk_regular(FILE* f, size_t offset, byte *p, size_t count) @@ -2198,6 +2193,144 @@ void write_ips_hunk_rle(FILE* f, size_t offset, byte b, size_t count) } } +void ips_free_hunk(ips_hunk* hunk) +{ + if (hunk->contents) + { + free(hunk->contents); + } + free(hunk); +} + +int ips_changed = 0; + +// combines/swaps adjacent/overlapping hunks. +// bubble sort ips entries. +// returns true if a change was made. +void ips_simplify_hunk(ips_hunk** hunk_ptr) +{ + ips_hunk* hunk; + ips_hunk* next; + +tail_recurse: + hunk = *hunk_ptr; + if (hunk == 0) return; + next = hunk->next; + + // this node is not used. + if (hunk->suppress || hunk->length == 0) goto remove_node; + + // update tail + ips_hunk_tail = hunk; + + // reached end. + if (next == 0) return; + + // don't merge with suppressed chunk + if (next->suppress) + { + // continue + hunk_ptr = &hunk->next; + goto tail_recurse; + }; + + // totally contained in the next one? + if (hunk->offset >= next->offset + && hunk->offset + hunk->length <= next->offset + next->length) + { + goto remove_node; + } + + // the next one starts after this one + if (next->offset > hunk->offset) + { + // but they overlap + if (hunk->offset + hunk->length > next->offset) + { + // truncate this one + ips_changed = 1; + hunk->length = next->offset - hunk->offset; + } + else + { + // no change; proceed to the next one. + hunk_ptr = &hunk->next; + goto tail_recurse; + } + } + // the next one is totally contained in this one? + else if (next->offset >= hunk->offset + && next->offset + next->length <= hunk->offset + hunk->length) + { + ips_changed = 1; + + // duplicate us to appear after theirs + if (next->offset + next->length < hunk->offset + hunk->length) + { + ips_hunk* newhunk = (ips_hunk*)my_malloc(sizeof(ips_hunk)); + newhunk->offset = next->offset + next->length; + newhunk->length = hunk->offset + hunk->length - newhunk->offset; + if (hunk->contents) + { + hunk->contents = (byte*)my_malloc(newhunk->length); + memcpy( + hunk->contents, + hunk->contents + newhunk->offset - hunk->offset, + newhunk->length + ); + } + else + { + newhunk->contents = 0; + newhunk->rle_content = hunk->rle_content; + } + newhunk->next = next->next; + } + + // truncate our length + hunk->length = next->offset - hunk->offset; + } + // the next one starts before this one + else if (next->offset < hunk->offset) + { + ips_changed = 1; + + if (next->offset + next->length > hunk->offset) + { + // not disjoint. + // truncate ours from the left. + + int removed = next->offset + next->length - hunk->offset; + + hunk->length -= removed; + if (hunk->contents) + { + memmove(hunk->contents, hunk->contents + removed, hunk->length); + } + } + + // swap + *hunk_ptr = next; + hunk->next = next->next; + next->next = hunk; + + // proceed from the new location of this hunk. + hunk_ptr = &next->next; + goto tail_recurse; + } + + if (0) + { + remove_node: + ips_changed = 1; + *hunk_ptr = next; + ips_free_hunk(hunk); + } + + // retry this node + goto tail_recurse; +} + // writes all hunks to the output file. void ips_write(FILE* output) { @@ -2210,10 +2343,16 @@ void ips_write(FILE* output) return; } + // O(n^2) + while (1) { + ips_simplify_hunk(&ips_hunk_head); + if (!ips_changed) break; + ips_changed = 0; + }; + // write hunks - for (size_t i = 0; i < ips_hunk_list_c; ++i) + for (ips_hunk* hunk = ips_hunk_head; hunk != 0; hunk = hunk->next) { - ips_hunk* hunk = ips_hunk_list[i]; if (hunk->suppress) continue; if (hunk->contents) { @@ -2294,17 +2433,19 @@ void ips_clear() flush_output(1); // clear list. - for (size_t i = 0; i < ips_hunk_list_c; ++i) + for (ips_hunk* hunk = ips_hunk_head; hunk != 0; ) { - ips_hunk* hunk = ips_hunk_list[i]; if (hunk->contents) { free(hunk->contents); } - free(hunk); + ips_hunk* tmp = hunk; + hunk = hunk->next; + free(tmp); } - ips_hunk_list_c = 0; + ips_hunk_head = 0; + ips_hunk_tail = 0; } // gets cmp value in ips file @@ -2312,9 +2453,8 @@ int ips_get_cmp_value(size_t location) { assert(genips); int value = -1; - for (size_t i = 0; i < ips_hunk_list_c; ++i) + for (ips_hunk* hunk = ips_hunk_head; hunk != 0; hunk = hunk->next) { - ips_hunk* hunk = ips_hunk_list[i]; if (hunk->offset <= location && location < hunk->offset + hunk->length) { if (hunk->contents) @@ -2917,9 +3057,8 @@ void clearpatch(label *id, char **next) // mark sections as non-output. // we don't remove them because we may wish to // read from them (e.g. compare) - for (size_t i = 0; i < ips_hunk_list_c; ++i) + for (ips_hunk* hunk = ips_hunk_head; hunk != 0; hunk = hunk->next) { - ips_hunk* hunk = ips_hunk_list[i]; hunk->suppress = 1; } } From e154a98d7ad45ef98b8dc2a588847b969cf2fbf9 Mon Sep 17 00:00:00 2001 From: NaOH Date: Fri, 12 Jun 2020 13:20:46 -0700 Subject: [PATCH 6/7] 'COMPARE' directive no longer suffers false positives --- asm6f.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/asm6f.c b/asm6f.c index 12fa903..63961d0 100644 --- a/asm6f.c +++ b/asm6f.c @@ -2523,7 +2523,7 @@ void output_buffer(byte* p, size_t count) // compare if (comparefiller) { - int b = get_cmp_value(filepos + outcount); + int b = get_cmp_value(filepos + i); if (b != defaultfiller && b >= 0) { errmsg = CompFailed; From 428a598e357b5ec4e7c9beb13477168f4e74907a Mon Sep 17 00:00:00 2001 From: NaOH Date: Sat, 13 Jun 2020 17:31:32 -0700 Subject: [PATCH 7/7] output no longer fails when not generating .ips --- asm6f.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/asm6f.c b/asm6f.c index 63961d0..6b666fe 100644 --- a/asm6f.c +++ b/asm6f.c @@ -2646,7 +2646,7 @@ void output(byte *p,int size, int cdlflag) { if (nooutput) return; - if(!outputfile || !genips) return; + if(!outputfile && !genips) return; // write data. output_buffer(p, size);