#include "snesreader.hpp" #if defined(_WIN32) #define dllexport __declspec(dllexport) #else #define dllexport #endif #include "fex/fex.h" #include "libjma/jma.h" extern "C" char* uncompressStream(int, int); //micro-bunzip #define QT_CORE_LIB #include #include #include using namespace nall; dllexport const char* snesreader_supported() { return "*.zip *.z *.7z *.rar *.gz *.bz2 *.jma"; } void snesreader_apply_ips(const char *filename, uint8_t *&data, unsigned &size) { file fp; if(fp.open(filename, file::mode_read) == false) return; unsigned psize = fp.size(); uint8_t *pdata = new uint8_t[psize]; fp.read(pdata, psize); fp.close(); if(psize < 8 || pdata[0] != 'P' || pdata[1] != 'A' || pdata[2] != 'T' || pdata[3] != 'C' || pdata[4] != 'H') { delete[] pdata; return; } unsigned outsize = 0; uint8_t *outdata = new uint8_t[16 * 1024 * 1024]; memset(outdata, 0, 16 * 1024 * 1024); memcpy(outdata, data, size); unsigned offset = 5; while(offset < psize - 3) { unsigned addr; addr = pdata[offset++] << 16; addr |= pdata[offset++] << 8; addr |= pdata[offset++] << 0; unsigned size; size = pdata[offset++] << 8; size |= pdata[offset++] << 0; if(size == 0) { //RLE size = pdata[offset++] << 8; size |= pdata[offset++] << 0; for(unsigned n = addr; n < addr + size;) { outdata[n++] = pdata[offset]; if(n > outsize) outsize = n; } offset++; } else { //uncompressed for(unsigned n = addr; n < addr + size;) { outdata[n++] = pdata[offset++]; if(n > outsize) outsize = n; } } } delete[] pdata; delete[] data; data = outdata; size = max(size, outsize); } bool snesreader_load_normal(const char *filename, uint8_t *&data, unsigned &size) { file fp; if(fp.open(filename, file::mode_read) == false) return false; size = fp.size(); data = new uint8_t[size]; fp.read(data, size); fp.close(); return true; } #include "filechooser.cpp" bool snesreader_load_fex(string &filename, uint8_t *&data, unsigned &size) { fex_t *fex; fex_open(&fex, filename); if(fex_done(fex)) { fex_close(fex); return false; } if(!fileChooser) fileChooser = new FileChooser; fileChooser->list.reset(); while(fex_done(fex) == false) { fex_stat(fex); const char *name = fex_name(fex); //only add valid ROM extensions to list (ignore text files, save RAM files, etc) if(striend(name, ".sfc") || striend(name, ".smc") || striend(name, ".swc") || striend(name, ".fig") || striend(name, ".bs") || striend(name, ".st") || striend(name, ".gb") || striend(name, ".sgb") || striend(name, ".gbc") || striend(filename, ".gz") //GZip files only contain a single file ) { fileChooser->list[fileChooser->list.size()] = name; } fex_next(fex); } string name = fileChooser->exec(); if(name == "") { fex_close(fex); return false; } fex_rewind(fex); while(fex_done(fex) == false) { fex_stat(fex); if(name == fex_name(fex)) { size = fex_size(fex); data = new uint8_t[size]; fex_read(fex, data, size); fex_close(fex); if(fileChooser->list.size() > 1) { strtr(name, "\\", "/"); strtr(filename, "\\", "/"); //retain only path from filename, "/foo/bar.7z" -> "/foo/" for(signed i = filename.length() - 1; i >= 0; i--) { if(filename[i] == '/') { filename[i + 1] = 0; break; } } //append only filename from archive, "foo/bar.sfc" -> "bar.sfc" lstring part; part.split("/", name); filename = string() << filename << part[part.size() - 1]; } return true; } fex_next(fex); } fex_close(fex); return false; } bool snesreader_load_bz2(const char *filename, uint8_t *&data, unsigned &size) { //TODO: need a way to get the size of a bzip2 file, so we can pre-allocate //a buffer to decompress into memory. for now, use a temporary file. string name = "/tmp/.bz2_temporary_decompression_object"; FILE *wr; wr = fopen_utf8(name, "wb"); if(!wr) { //try the local directory name = ".bz2_temporary_decompression_object"; wr = fopen_utf8(name, "wb"); //can't get write access, so give up if(!wr) return false; } FILE *fp = fopen_utf8(filename, "rb"); uncompressStream(fileno(fp), fileno(wr)); fclose(fp); fclose(wr); bool success = snesreader_load_normal(name, data, size); unlink(name); return success; } bool snesreader_load_jma(const char *filename, uint8_t *&data, unsigned &size) { try { JMA::jma_open JMAFile(filename); std::string name; std::vector file_info = JMAFile.get_files_info(); for(std::vector::iterator i = file_info.begin(); i != file_info.end(); i++) { name = i->name; size = i->size; break; } data = new uint8_t[size]; JMAFile.extract_file(name, data); return true; } catch(JMA::jma_errors) { return false; } } dllexport bool snesreader_load(string &filename, uint8_t *&data, unsigned &size) { if(file::exists(filename) == false) return false; bool success = false; if(striend(filename, ".zip") || striend(filename, ".z") || striend(filename, ".7z") || striend(filename, ".rar") || striend(filename, ".gz")) { success = snesreader_load_fex(filename, data, size); } else if(striend(filename, ".bz2")) { success = snesreader_load_bz2(filename, data, size); } else if(striend(filename, ".jma")) { success = snesreader_load_jma(filename, data, size); } else { success = snesreader_load_normal(filename, data, size); } if(success == false) return false; //apply IPS patch, if it exists string patchname = filename; for(int i = patchname.length() - 1; i >= 0; i--) { if(patchname[i] == '.') { patchname[i] = 0; break; } } patchname << ".ips"; if(file::exists(patchname)) snesreader_apply_ips(patchname, data, size); //remove copier header, if it exists if((size & 0x7fff) == 512) memmove(data, data + 512, size -= 512); return true; }