//Module name: Floating IPS, shared core for all frontends //Author: Alcaro //Date: See Git history //Licence: GPL v3.0 or higher #include "flips.h" #include "crc32.h" #ifdef __MINGW32__ //get rid of dependencies on libstdc++, they waste 200KB on this platform void* operator new(size_t n) { return malloc(n); } // forget allocation failures, let them segfault. void operator delete(void * p) { free(p); } void operator delete(void * p, size_t n) { free(p); } extern "C" void __cxa_pure_virtual() { abort(); } #if __GNUC__ && (__cpp_rtti || __cpp_exceptions) #warning "Consider building with -fno-exceptions -fno-rtti, to avoid dependencies on libgcc_s_sjlj-1.dll and libstdc++-6.dll." #endif #endif //TODO: source ROM chooser //given a target, from all known source ROMs with same extension, find the most similar // read 1MB from each; check how many bytes are same and same location as target // (only counting offsets where both source and target bytes are different from previous, to avoid false positives on long runs of 00) // if one is >80% same (not counting repeated runs), and all others are <20%, use that //if no match, multiple matches, or one or more half-matches: // read first 64K from each of the listed sources, create BPS, and see if one is <20% of size of bps with blank source, while others are >70% //if still nothing unambiguous: // fail //this goes on a separate thread //TODO: more manual GUI // has three text fields, for patch / source / target, and 'pick' buttons nearby that open file dialogs // also Create / Apply / ApplyRun buttons at the bottom //auto selection (only if target field is blank): // patch -> source (crc32 database) // target -> source (algorithm above) // target -> patch (set extension to bps) // (source&patch) -> target (source extension, name/path from patch; also happens if triggering patch->source) //all text fields are scrolled to the end on creation //no auto selection in simple UI //applying or creating clears patch/target, but keeps source //ips creation is allowed, but only by changing filetype in picker, or editing text field; it does not keep last used type // ips application remains unchanged // multi-patching is allowed, each filename is wrapped in <>; if filename doesn't start or end with right char, it's a single name // colon-separated or semicolon-separated are more common, but they're plausible (uncommon, but possible) in filenames // (Super Mario World 2: Yoshi's Island, or Marios;Gate, for example), and I don't want to fail on that // multi-creation is not allowed; technically feasible, but super rare, should probably be CLI instead // if first file isn't found, it tries using the entire field as filename, in case it actually contains semicolon //TODO: delete struct mem ReadWholeFile(LPCWSTR filename) { return file::read(filename); } bool WriteWholeFile(LPCWSTR filename, struct mem data) { return filewrite::write(filename, data); } bool WriteWholeFileWithHeader(LPCWSTR filename, struct mem header, struct mem data) { filewrite* f = filewrite::create(filename); if (!f) return false; bool ret = (f->append(header.ptr, 512) && f->append(data.ptr, data.len)); // do not use header.len, that'd prepend the entire file delete f; return ret; } void FreeFileMemory(struct mem mem) { free(mem.ptr); } class file_libc : public file { size_t size; FILE* io; public: static file* create(const char * filename) { FILE* f = fopen(filename, "rb"); if (!f) return NULL; return new file_libc(f); } private: file_libc(FILE* io) : io(io) { fseek(io, 0, SEEK_END); size = ftell(io); } public: size_t len() { return size; } bool read(uint8_t* target, size_t start, size_t len) { fseek(io, start, SEEK_SET); return (fread(target, 1,len, io) == len); } ~file_libc() { fclose(io); } }; file* file::create_libc(const char * filename) { return file_libc::create(filename); } class filewrite_libc : public filewrite { FILE* io; public: static filewrite* create(const char * filename) { FILE* f = fopen(filename, "wb"); if (!f) return NULL; return new filewrite_libc(f); } private: filewrite_libc(FILE* io) : io(io) {} public: bool append(const uint8_t* data, size_t len) { return (fwrite(data, 1,len, io)==len); } ~filewrite_libc() { fclose(io); } }; filewrite* filewrite::create_libc(const char * filename) { return filewrite_libc::create(filename); } class filemap_fallback : public filemap { public: size_t m_len; uint8_t* m_ptr; static filemap* create(file* f) { if (!f) return NULL; size_t len = f->len(); uint8_t* ptr = (uint8_t*)malloc(len); if (!ptr) return NULL; if (!f->read(ptr, 0, len)) { free(ptr); return NULL; } return new filemap_fallback(f, len, ptr); } size_t len() { return m_len; } const uint8_t * ptr() { return m_ptr; } //delete the file early, to avoid file sharing issues on Windows (and because keeping it is useless) // https://github.com/Alcaro/Flips/pull/14 filemap_fallback(file* f, size_t len, uint8_t* ptr) : m_len(len), m_ptr(ptr) { delete f; } ~filemap_fallback() { free(m_ptr); } }; filemap* filemap::create_fallback(LPCWSTR filename) { return filemap_fallback::create(file::create(filename)); } LPWSTR GetExtension(LPCWSTR fname) { LPWSTR ptr1=(LPWSTR)fname; LPWSTR ptr2; ptr2=wcsrchr(ptr1, '/'); if (ptr2) ptr1=ptr2; #ifdef FLIPS_WINDOWS ptr2=wcsrchr(ptr1, '\\'); if (ptr2) ptr1=ptr2; #endif ptr2=wcsrchr(ptr1, '.'); if (ptr2) ptr1=ptr2; if (*ptr1=='.') return ptr1; else return wcsrchr(ptr1, '\0'); } LPWSTR GetBaseName(LPCWSTR fname) { LPWSTR ptr1=(LPWSTR)fname; LPWSTR ptr2; ptr2=wcsrchr(ptr1, '/'); if (ptr2) ptr1=ptr2+1; #ifdef FLIPS_WINDOWS ptr2=wcsrchr(ptr1, '\\'); if (ptr2) ptr1=ptr2+1; #endif return ptr1; } bool forceKeepHeader=false; #ifndef FLIPS_CLI bool guiActive=false; #endif struct mem file::read() { struct mem out; out.len = len(); out.ptr = (uint8_t*)malloc(out.len + sizeof(WCHAR)); memset(out.ptr + out.len, 0, sizeof(WCHAR)); if (!read(out.ptr, 0, out.len)) { free(out.ptr); struct mem err = {NULL, 0}; return err; } return out; } struct mem file::read(LPCWSTR filename) { struct mem err = {NULL, 0}; file* f = file::create(filename); if (!f) return err; struct mem ret = f->read(); delete f; return ret; } bool filewrite::write(LPCWSTR filename, struct mem data) { filewrite* f = filewrite::create(filename); if (!f) return false; bool ret = f->append(data.ptr, data.len); delete f; return ret; } class fileheader : public file { file* child; public: fileheader(file* child) : child(child) {} size_t len() { return child->len()-512; } bool read(uint8_t* target, size_t start, size_t len) { return child->read(target, start+512, len); } ~fileheader() { delete child; } }; class fileheadermap : public filemap { filemap* child; public: fileheadermap(filemap* child) : child(child) {} size_t len() { return child->len()-512; } //bool read(uint8_t* target, size_t start, size_t len) { return child->read(target, start+512, len); } const uint8_t* ptr() { return child->ptr()+512; } ~fileheadermap() { delete child; } }; const struct errorinfo ipserrors[]={ { el_ok, NULL },//ips_ok { el_unlikelythis, "The patch was applied, but is most likely not intended for this ROM." },//ips_notthis { el_unlikelythis, "The patch was applied, but did nothing. You most likely already had the output file of this patch." },//ips_thisout { el_warning, "The patch was applied, but appears scrambled or malformed." },//ips_suspicious { el_broken, "The patch is broken and can't be used." },//ips_invalid { el_broken, "The IPS format does not support files larger than 16MB." },//ips_16MB { el_warning, "The files are identical! The patch will do nothing." },//ips_identical }; const struct errorinfo bpserrors[]={ { el_ok, NULL },//bps_ok, { el_notthis, "That's the output file already." },//bps_to_output { el_notthis, "This patch is not intended for this ROM." },//bps_not_this { el_broken, "This patch is broken and can't be used." },//bps_broken { el_broken, "Couldn't read input patch. What exactly are you doing?" },//bps_io { el_warning, "The files are identical! The patch will do nothing." },//bps_identical { el_broken, "These files are too big for this program to handle." },//bps_too_big { el_broken, "These files are too big for this program to handle." },//bps_out_of_mem (same message as above, it's accurate for both.) { el_broken, "Patch creation was canceled." },//bps_canceled }; LPCWSTR GetManifestName(LPCWSTR romname) { //static WCHAR manifestname[MAX_PATH]; //wcscpy(manifestname, romname); //LPWSTR manifestext=GetExtension(manifestname); //if (!manifestext) manifestext=wcschr(manifestname, '\0'); //wcscpy(manifestext, TEXT(".xml")); //return manifestname; static WCHAR * manifestname=NULL; if (manifestname) free(manifestname); manifestname=(WCHAR*)malloc((wcslen(romname)+1+4)*sizeof(WCHAR)); wcscpy(manifestname, romname); LPWSTR manifestext=GetExtension(manifestname); if (manifestext) wcscpy(manifestext, TEXT(".xml")); return manifestname; } enum patchtype IdentifyPatch(file* patch) { size_t len = patch->len(); uint8_t data[16]; if (len>16) len=16; patch->read(data, 0, len); if (len>=5 && !memcmp(data, "PATCH", 5)) return ty_ips; if (len>=4 && !memcmp(data, "BPS1", 4)) return ty_bps; if (len>=4 && !memcmp(data, "UPS1", 4)) return ty_ups; return ty_null; } //this is the most inefficient possible implementation, but since it only needs about 10 entries, //performance is irrelevant void config::init_raw(LPWSTR contents) { LPCWSTR header = TEXT("[Flips]\n"); if (wcsncmp(contents, header, wcslen(header)) != 0) return; contents += wcslen(header); //I need to somehow ensure that stepping backwards across whitespace doesn't go before the original string. //This can be done with while (iswspace(*contents)) contents++;, but demanding the header above works just as well. while (true) { LPWSTR key; LPWSTR keyend; LPWSTR val; LPWSTR valend; LPWSTR nextline = wcschr(contents, '\n'); if (nextline != NULL) valend = nextline; else valend = wcschr(contents, '\0'); //do not move inside the conditional, it screws up the strchr while (iswspace(valend[-1])) valend--; *valend = '\0'; LPWSTR sep = wcschr(contents, '='); if (sep != NULL) { key = contents; keyend = sep; val = sep+1; while (iswspace(key[0])) key++; while (iswspace(keyend[-1])) keyend--; *keyend = '\0'; while (iswspace(val[0])) val++; if (valend>val && keyend>key && iswalnum(key[0])) { set(key, val); } } if (!nextline) break; contents = nextline+1; while (contents && iswspace(*contents)) contents++; } for (size_t i=0;i 0 && data.len%sizeof(WCHAR) == 0) { this->init_raw((LPWSTR)(data.ptr)); } free(data.ptr); this->filename = wcsdup(filename); } void config::sort() { //bubble sort, and called for every insertion... super fun //but it's easy, it works, and it's fast for numentries=10 and there's no reason to go much higher than that for (size_t i=0;i 0) { LPWSTR tmp = names[i]; names[i] = names[j]; names[j] = tmp; tmp = values[i]; values[i] = values[j]; values[j] = tmp; } } } void config::set(LPCWSTR name, LPCWSTR value) { for (size_t i=0;inumentries;i++) { if (!wcscmp(name, this->names[i])) { if (value == this->values[i]) return; free(this->values[i]); if (value!=NULL) { this->values[i] = wcsdup(value); } else { free(this->names[i]); this->names[i] = this->names[this->numentries-1]; this->values[i] = this->values[this->numentries-1]; this->numentries--; } return; } } this->numentries++; this->names = (LPWSTR*)realloc(this->names, sizeof(LPWSTR)*this->numentries); this->values = (LPWSTR*)realloc(this->values, sizeof(LPWSTR)*this->numentries); this->names[this->numentries-1] = wcsdup(name); this->values[this->numentries-1] = wcsdup(value); sort(); } LPCWSTR config::get(LPCWSTR name, LPCWSTR def) { for (size_t i=0;inumentries;i++) { if (!wcscmp(name, this->names[i])) { if (this->values[i]) return this->values[i]; else return def; } } return def; } LPWSTR config::flatten() { LPCWSTR header = TEXT("[Flips]\n#Changing this file may void your warranty. Do not report any bugs if you do.\n"); size_t len = wcslen(header); for (size_t i=0;inumentries;i++) { if (this->values[i]!=NULL) { len += wcslen(this->names[i]) + 1 + wcslen(this->values[i]) + 1; } } LPWSTR ret = (LPWSTR)malloc((len+1)*sizeof(WCHAR)); LPWSTR at = ret; at += wsprintf(at, TEXT("%s"), header); for (size_t i=0;inumentries;i++) { if (this->values[i]!=NULL) { at += wsprintf(at, TEXT("%s=%s\n"), this->names[i], this->values[i]); } } return ret; } config::~config() { if (this->filename) { LPWSTR data = this->flatten(); //puts(data); struct mem m = { (uint8_t*)data, wcslen(data)*sizeof(WCHAR) }; filewrite::write(this->filename, m); free(data); free(this->filename); } for (size_t i=0;inumentries;i++) { //printf("#(%s)(%s)\n",this->names[i],this->values[i]); free(this->names[i]); free(this->values[i]); } free(this->names); free(this->values); } config cfg; static LPWSTR EmuGetKey(LPCWSTR filename) { static WCHAR ret[64]; wsprintf(ret, TEXT("emu%s"), GetExtension(filename)); return ret; } LPCWSTR GetEmuFor(LPCWSTR filename) { return cfg.get(EmuGetKey(filename)); } void SetEmuFor(LPCWSTR filename, LPCWSTR emu) { cfg.set(EmuGetKey(filename), emu); } enum { ch_crc32, ch_last }; static LPCWSTR checkmap_typenames[] = { TEXT("rom.crc32.") }; // sizeof rather than strlen to ensure compile-time evaluation; -1 for NUL static const int checkmap_typenames_maxlen = sizeof("rom.crc32.")-1; struct checkmap { uint8_t* sum; LPWSTR name; }; static struct checkmap * checkmap[ch_last]={NULL}; static uint32_t checkmap_len[ch_last]={0}; static const uint8_t checkmap_sum_size[]={ 4 }; static const uint8_t checkmap_sum_size_max = 4; static const int CfgSumNameMaxLen = checkmap_typenames_maxlen + checkmap_sum_size_max*2 + 1; static void CfgSumName(WCHAR* out, int type, const void* sum) { const uint8_t* sum8 = (uint8_t*)sum; wcscpy(out, checkmap_typenames[type]); WCHAR* end = out + wcslen(checkmap_typenames[type]); for (int i=0;isum=(uint8_t*)malloc(checkmap_sum_size[type]); memcpy(item->sum, sum, checkmap_sum_size[type]); item->name=wcsdup(filename); WCHAR cfgname[CfgSumNameMaxLen]; CfgSumName(cfgname, type, sum); cfg.set(cfgname, filename); } struct mem GetRomList() { struct mem out={NULL, 0}; for (unsigned int type=0;type data.len) return; \ memcpy(target, data.ptr, bytes); \ data.ptr += bytes; \ data.len -= bytes #define read_discard(bytes) \ if (bytes > data.len) return; \ data.ptr += bytes; \ data.len -= bytes uint32_t count; read(&count, sizeof(count)); checkmap[type]=(struct checkmap*)malloc(sizeof(struct checkmap)*count*2);//overallocate so I won't need to round the count while (count--) { uint8_t hashlen; read(&hashlen, sizeof(hashlen)); uint16_t strlen; read(&strlen, sizeof(strlen)); if (hashlen==checkmap_sum_size[type]) { if (data.len < hashlen+strlen) return; struct checkmap* item=&checkmap[type][checkmap_len[type]++]; item->sum=(uint8_t*)malloc(checkmap_sum_size[type]); read(item->sum, hashlen); item->name=(WCHAR*)malloc(strlen+sizeof(WCHAR)); read(item->name, strlen); memset((uint8_t*)item->name + strlen, 0, sizeof(WCHAR)); } else { read_discard(hashlen); read_discard(strlen); } } #undef read } } LPCWSTR FindRomForPatch(file* patch, bool * possibleToFind) { if (possibleToFind) *possibleToFind=false; enum patchtype patchtype=IdentifyPatch(patch); if (patchtype==ty_bps) { struct bpsinfo info = bps_get_info(patch, false); if (info.error) return NULL; if (possibleToFind) *possibleToFind=true; return FindRomForSum(ch_crc32, &info.crc_in); } //UPS has checksums too, but screw UPS. Nobody cares. return NULL; } void AddToRomList(file* patch, LPCWSTR path) { enum patchtype patchtype=IdentifyPatch(patch); if (patchtype==ty_bps) { struct bpsinfo info = bps_get_info(patch, false); if (info.error) return; AddRomForSum(ch_crc32, &info.crc_in, path); } } void AddConfigToRomList(LPCWSTR key, LPCWSTR value) { int type; uint8_t sum[checkmap_sum_size_max]; if (CfgSumParseName(&type, sum, key)) AddRomForSum(type, sum, value); } void DeleteRomFromList(LPCWSTR path) { for (unsigned int type=0;typeread(); // There's no real reason to remove this, no patcher knows how to handle these file objects. enum patchtype patchtype=IdentifyPatch(patch); struct errorinfo errinf; removeheader=(removeheader && patchtype==ty_bps); if (removeheader) { inrom.ptr+=512; inrom.len-=512; } struct mem outrom={NULL,0}; struct mem manifest={NULL,0}; errinf=error(el_broken, "Unknown patch format."); if (patchtype==ty_bps) { errinf=bpserrors[bps_apply(patchmem, inrom, &outrom, &manifest, !verifyinput)]; if (errinf.level==el_notthis && !verifyinput && outrom.ptr) errinf.level=el_warning; if (errinf.level==el_notthis) { bpsinfo inf = bps_get_info(patch, false); static char errtextbuf[2][256]; // ugly trick to get the nested invocation for the header remover to not screw up the error static int errtextid=0; // makes it impossible to save the error strings, but Flips doesn't do that anyways char* errtext=errtextbuf[errtextid]; if (++errtextid == 2) errtextid=0; if (inf.size_in != inrom.len) { //http://msdn.microsoft.com/en-us/library/vstudio/tcxf1dw6.aspx says %zX is not supported //this is true up to and including Windows Vista; 7 adds support for it //I could define it to "I", but my GCC does not acknowledge its legitimacy and throws bogus warnings //nor does my GCC accept any supported alternative, so let's just nuke the entire warning. //it's a poor solution, but it's the best I can find #ifdef _WIN32 # ifdef _WIN64 # ifdef __GNUC__ # pragma GCC diagnostic ignored "-Wformat" # endif # define z "I64" # else # define z "" # endif #else # define z "z" #endif sprintf(errtext, "This patch is not intended for this ROM. Expected file size %" z "u, got %" z "u.", inf.size_in, inrom.len); errinf.description=errtext; } else { uint32_t crc = crc32(inrom.ptr, inrom.len); if (inf.crc_in != crc) { sprintf(errtext, "This patch is not intended for this ROM. Expected checksum %.8X, got %.8X.", inf.crc_in, crc); errinf.description=errtext; } } } } if (patchtype==ty_ips) errinf=ipserrors[ips_apply(patchmem, inrom, &outrom)]; if (patchtype==ty_ups) errinf=bpserrors[ups_apply(patchmem, inrom, &outrom)]; if (errinf.level==el_ok) errinf.description="The patch was applied successfully!"; struct manifestinfo defmanifestinfo={true,false,NULL}; if (!manifestinfo) manifestinfo=&defmanifestinfo; if (manifestinfo->use) { if (manifest.ptr) { LPCWSTR manifestname; if (manifestinfo->name) manifestname=manifestinfo->name; else manifestname=GetManifestName(outromname); if (!WriteWholeFile(manifestname, manifest) && manifestinfo->required) { if (errinf.level==el_ok) errinf=error(el_warning, "The patch was applied, but the manifest could not be created."); } } else if (manifestinfo->required && errinf.level==el_ok) { errinf=error(el_warning, "The patch was applied, but there was no manifest present."); } } if (removeheader) { inrom.ptr-=512; inrom.len+=512; if (errinf.levelget(), verifyinput, shouldRemoveHeader(inromname, inrom->len()), outromname, manifestinfo); if (update_rom_list && errinf.level==el_ok) AddToRomList(patch, inromname); delete inrom; return errinf; } struct errorinfo ApplyPatch(LPCWSTR patchname, LPCWSTR inromname, bool verifyinput, LPCWSTR outromname, struct manifestinfo * manifestinfo, bool update_rom_list) { file* patch = file::create(patchname); if (!patch) { return error(el_broken, "Couldn't read input patch. What exactly are you doing?"); } struct errorinfo errinf=ApplyPatchMem(patch, inromname, verifyinput, outromname, manifestinfo, update_rom_list); delete patch; return errinf; } char bpsdProgStr[24]; int bpsdLastPromille=-1; bool bpsdeltaGetProgress(size_t done, size_t total) { if (total<1000) total=1000;//avoid div by zero int promille=done/(total/1000);//don't set this to done*1000/total, it'd just give overflows on huge stuff. 100% is handled later if (promille==bpsdLastPromille) return false; bpsdLastPromille=promille; if (promille>=1000) return false; strcpy(bpsdProgStr, "Please wait... "); bpsdProgStr[15]='0'+promille/100; int digit1=((promille<100)?15:16); bpsdProgStr[digit1+0]='0'+promille/10%10; bpsdProgStr[digit1+1]='.'; bpsdProgStr[digit1+2]='0'+promille%10; bpsdProgStr[digit1+3]='%'; bpsdProgStr[digit1+4]='\0'; return true; } bool bpsdeltaProgressCLI(void* userdata, size_t done, size_t total) { if (!bpsdeltaGetProgress(done, total)) return true; fputs(bpsdProgStr, stdout); putchar('\r'); fflush(stdout); return true; } struct errorinfo CreatePatchToMem(LPCWSTR inromname, LPCWSTR outromname, enum patchtype patchtype, struct manifestinfo * manifestinfo, struct mem * patchmem) { bool usemmap = (patchtype!=ty_bps && patchtype!=ty_bps_moremem); //pick roms filemap* romsmap[2]={NULL, NULL}; file* roms[2]={NULL, NULL}; size_t lens[2]; for (int i=0;i<2;i++) { LPCWSTR romname=((i==0)?inromname:outromname); if (usemmap) { romsmap[i] = filemap::create(romname); if (!romsmap[i]) { if (i==1) delete romsmap[0]; return error(el_broken, "Couldn't read this ROM."); } if (shouldRemoveHeader(romname, romsmap[i]->len()) && (patchtype==ty_bps || patchtype==ty_bps_linear || patchtype==ty_bps_moremem)) { romsmap[i] = new fileheadermap(romsmap[i]); } lens[i] = romsmap[i]->len(); } else { roms[i] = file::create(romname); if (!roms[i]) { if (i==1) delete roms[0]; return error(el_broken, "Couldn't read this ROM."); } lens[i] = roms[i]->len(); if (shouldRemoveHeader(romname, lens[i]) && (patchtype==ty_bps || patchtype==ty_bps_linear || patchtype==ty_bps_moremem)) { roms[i] = new fileheader(roms[i]); } } } struct mem manifest={NULL,0}; struct errorinfo manifesterr={el_ok, NULL}; struct manifestinfo defmanifestinfo={true,false,NULL}; if (!manifestinfo) manifestinfo=&defmanifestinfo; if (patchtype==ty_bps || patchtype==ty_bps_linear || patchtype==ty_bps_moremem) { LPCWSTR manifestname; if (manifestinfo->name) manifestname=manifestinfo->name; else manifestname=GetManifestName(outromname); manifest=ReadWholeFile(manifestname); if (!manifest.ptr) manifesterr=error(el_warning, "The patch was created, but the manifest could not be read."); } else manifesterr=error(el_warning, "The patch was created, but this patch format does not support manifests."); struct errorinfo errinf={ el_broken, "Unknown patch format." }; if (patchtype==ty_ips) { errinf=ipserrors[ips_create(romsmap[0]->get(), romsmap[1]->get(), patchmem)]; } if (patchtype==ty_bps || patchtype==ty_bps_moremem) { #ifndef FLIPS_CLI if (guiActive) { bpsdeltaBegin(); errinf=bpserrors[bps_create_delta(roms[0], roms[1], manifest, patchmem, bpsdeltaProgress, NULL, (patchtype==ty_bps_moremem))]; bpsdeltaEnd(); } else #endif { errinf=bpserrors[bps_create_delta(roms[0], roms[1], manifest, patchmem, bpsdeltaProgressCLI, NULL, (patchtype==ty_bps_moremem))]; } } if (patchtype==ty_bps_linear) { errinf=bpserrors[bps_create_linear(romsmap[0]->get(), romsmap[1]->get(), manifest, patchmem)]; } FreeFileMemory(manifest); if (errinf.level==el_ok) errinf.description="The patch was created successfully!"; if (manifestinfo->required && errinf.level==el_ok && manifesterr.level!=el_ok) errinf=manifesterr; if (errinf.level==el_ok && lens[0] > lens[1]) { errinf=error(el_warning, "The patch was created, but the input ROM is larger than the " "output ROM. Double check whether you've gotten them backwards."); } if (usemmap) { delete romsmap[0]; delete romsmap[1]; } else { delete roms[0]; delete roms[1]; } return errinf; } struct errorinfo CreatePatch(LPCWSTR inromname, LPCWSTR outromname, enum patchtype patchtype, struct manifestinfo * manifestinfo, LPCWSTR patchname) { struct mem patch={NULL,0}; struct errorinfo errinf = CreatePatchToMem(inromname, outromname, patchtype, manifestinfo, &patch); if (errinf.levelread(meta.ptr, info.meta_start, info.meta_size); if (manifestinfo->required) { if (manifestinfo->name) { filewrite::write(manifestinfo->name, meta); } else { fwrite(meta.ptr, 1,meta.len, stdout); free(meta.ptr); return 0; } } } LPCWSTR inromname = FindRomForPatch(patch, NULL); //'z' macro defined above printf("Input ROM: %" z "u bytes, CRC32 %.8X", info.size_in, info.crc_in); if (inromname) wprintf(TEXT(", %s"), inromname); puts(""); printf("Output ROM: %" z "u bytes, CRC32 %.8X\n", info.size_out, info.crc_out); //floating point may lose a little precision, but it's easier than dodging overflows, and this //is the output of inaccurate heuristics anyways, losing a little more makes no difference. //Windows MulDiv could also work, but it's kinda nonportable. //printf("Change index: %i / 1000\n", (int)(info.change_num / (float)info.change_denom * 1000)); if (info.meta_size) { printf("Metadata: %" z "u bytes:\n", info.meta_size); char* meta_iter = (char*)meta.ptr; char* meta_end = meta_iter + meta.len; for (int i=0;i<3;i++) { int n_chars = meta_end-meta_iter; if (n_chars > 75) n_chars = 75; char* nextline = (char*)memchr(meta_iter, '\n', n_chars); if (nextline && nextline-meta_iter < n_chars) n_chars = nextline-meta_iter; if (!nextline && !n_chars) break; // wipe trailing linebreaks printf(" %.*s\n", n_chars, meta_iter); if (!nextline) break; meta_iter = nextline+1; } } free(meta.ptr); return 0; } puts("No information available for this patch type"); return el_broken; } void usage() { GUIClaimConsole(); fputs( // 12345678901234567890123456789012345678901234567890123456789012345678901234567890 flipsversion "\n" "usage:\n" " " #ifndef FLIPS_CLI "flips\n" "or flips patch.bps\n" "or " #endif "flips [--apply] [--exact] patch.bps rom.smc [outrom.smc]\n" "or flips [--create] [--exact] [--bps | etc] clean.smc hack.smc [patch.bps]\n" "\n" // 12345678901234567890123456789012345678901234567890123456789012345678901234567890 "options:\n" "-a --apply: apply IPS, BPS or UPS patch (default if given two arguments)\n" "-c --create: create IPS or BPS patch (default if given three arguments)\n" "-I --info: BPS files contain information about input and output roms, print it\n" //" also estimates how much of the source file is retained\n" //" anything under 400 is fine, anything over 600 should be treated with suspicion\n" //(TODO: --info --verbose) "-i --ips, -b -B --bps --bps-delta, --bps-delta-moremem, --bps-linear:\n" " create this patch format instead of guessing based on file extension\n" " ignored when applying\n" " bps creation styles:\n" " delta is the recommended and default one; it's a good balance between creation\n" " performance and patch size\n" " delta-moremem is usually slightly (~3%) faster than delta, but uses about\n" " twice as much memory; it gives identical patches to delta\n" " linear is the fastest, but tends to give pretty big patches\n" " all BPS patchers can apply all patch styles, the only difference is file size\n" " and creation performance\n" "--exact: do not remove SMC headers when applying or creating a BPS patch\n" " not recommended, may affect patcher compatibility\n" "--ignore-checksum: accept checksum mismatches (BPS only)\n" "-m or --manifest: emit or insert a manifest file as romname.xml (BPS only)\n" "-mfilename or --manifest=filename: emit or insert a manifest file exactly here\n" "-h -? --help: show this information\n" "-v --version: show application version\n" "\n" // 12345678901234567890123456789012345678901234567890123456789012345678901234567890 , stdout); exit(0); } int flipsmain(int argc, WCHAR * argv[]) { enum patchtype patchtype=ty_null; enum { a_default, a_apply_filepicker, a_apply_given, a_create, a_info } action=a_default; int numargs=0; LPCWSTR arg[3]={NULL,NULL,NULL}; bool hasFlags=false; bool ignoreChecksum=false; struct manifestinfo manifestinfo={false, false, NULL}; // { // bool use; // bool required; // LPCWSTR name; // bool success; //}; for (int i=1;i