mirror of https://github.com/bsnes-emu/bsnes.git
228 lines
6.1 KiB
C++
228 lines
6.1 KiB
C++
|
#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 <QtGui>
|
||
|
|
||
|
#include <nall/file.hpp>
|
||
|
#include <nall/string.hpp>
|
||
|
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<JMA::jma_public_file_info> file_info = JMAFile.get_files_info();
|
||
|
for(std::vector<JMA::jma_public_file_info>::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;
|
||
|
}
|