//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" //get rid of dependencies on libstdc++, they eat 200KB on Windows void* operator new(size_t n) { return malloc(n); } // forget allocation failures, let them segfault. void operator delete(void * p) { free(p); } extern "C" void __cxa_pure_virtual() { while(1); } //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); } 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); 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; } }; 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(".bml")); //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(".bml")); 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; } enum { ch_crc32, ch_last }; 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 LPCWSTR FindRomForSum(int type, void* sum) { for (unsigned int i=0;isum=(uint8_t*)malloc(checkmap_sum_size[type]); memcpy(item->sum, sum, checkmap_sum_size[type]); item->name=wcsdup(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 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 (!verifyinput && outrom.ptr) errinf.level=el_warning; } 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.level=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) { //pick roms file* roms[2]={NULL, NULL}; for (int i=0;i<2;i++) { LPCWSTR romname=((i==0)?inromname:outromname); roms[i] = file::create(romname); if (!roms[i]) { return error(el_broken, "Couldn't read this ROM. What exactly are you doing?"); } if (shouldRemoveHeader(romname, roms[i]->len()) && (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) { 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) { struct mem rommem[2]={ roms[0]->read(), roms[1]->read() }; errinf=ipserrors[ips_create(rommem[0], rommem[1], patchmem)]; free(rommem[0].ptr); free(rommem[1].ptr); } 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) { struct mem rommem[2]={ roms[0]->read(), roms[1]->read() }; errinf=bpserrors[bps_create_linear(rommem[0], rommem[1], manifest, patchmem)]; free(rommem[0].ptr); free(rommem[1].ptr); } 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 && roms[0]->len() > roms[1]->len()) { 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."); } 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.level