bsnes/snesreader/snesreader.cpp

228 lines
6.1 KiB
C++
Executable File

#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;
}