bsnes/snespurify/snespurify.cpp

320 lines
9.2 KiB
C++
Executable File

#include <nall/directory.hpp>
#include <nall/file.hpp>
#include <nall/filemap.hpp>
#include <nall/foreach.hpp>
#include <nall/platform.hpp>
#include <nall/string.hpp>
#include <nall/vector.hpp>
#include <nall/snes/cartridge.hpp>
using namespace nall;
#include <phoenix/phoenix.hpp>
using namespace phoenix;
static const char applicationTitle[] = "snespurify v10";
struct Application : Window {
Font font;
VerticalLayout layout;
HorizontalLayout pathLayout;
Label pathLabel;
LineEdit pathBox;
Button pathScan;
Button pathBrowse;
ListView fileList;
HorizontalLayout controlLayout;
Button selectAll;
Button unselectAll;
Widget spacer;
Button fixSelected;
struct FileInfo {
string filename;
string problem;
string solution;
};
linear_vector<FileInfo> fileInfo;
lstring errors;
void main();
void enable(bool);
void scan();
void scan(const string &pathname);
void analyze(const string &filename);
void repair();
} application;
void Application::main() {
#if defined(PLATFORM_WIN)
font.setFamily("Tahoma");
font.setSize(8);
#else
font.setFamily("Sans");
font.setSize(8);
#endif
setTitle(applicationTitle);
setGeometry({ 128, 128, 600, 360 });
setWidgetFont(font);
pathLabel.setText("Path to scan:");
pathScan.setText("Scan");
pathBrowse.setText("Browse ...");
fileList.setHeaderText("Filename", "Problem", "Solution");
fileList.setHeaderVisible();
fileList.setCheckable();
selectAll.setText("Select All");
unselectAll.setText("Clear All");
fixSelected.setText("Correct");
layout.setMargin(5);
pathLayout.append(pathLabel, 0, 0, 5);
pathLayout.append(pathBox, ~0, 0, 5);
pathLayout.append(pathScan, 80, 0, 5);
pathLayout.append(pathBrowse, 80, 0 );
layout.append(pathLayout, 5);
layout.append(fileList, ~0, ~0, 5);
controlLayout.append(selectAll, 80, 0, 5);
controlLayout.append(unselectAll, 80, 0, 5);
controlLayout.append(spacer, ~0, 0, 5);
controlLayout.append(fixSelected, 80, 0 );
layout.append(controlLayout );
append(layout);
onClose = &OS::quit;
pathBox.onActivate = pathScan.onTick = { &Application::scan, this };
pathBrowse.onTick = []() {
string pathname = OS::folderSelect(application, "");
if(pathname != "") application.pathBox.setText(pathname);
};
selectAll.onTick = []() {
unsigned count = application.fileInfo.size();
for(unsigned i = 0; i < count; i++) application.fileList.setChecked(i, true);
};
unselectAll.onTick = []() {
unsigned count = application.fileInfo.size();
for(unsigned i = 0; i < count; i++) application.fileList.setChecked(i, false);
};
fixSelected.onTick = { &Application::repair, this };
setVisible();
}
//don't allow actions to be taken while files are being scanned or fixed
void Application::enable(bool state) {
if(state == false) {
setTitle({ applicationTitle, " - working ..." });
} else {
setTitle(applicationTitle);
}
pathBox.setEnabled(state);
pathScan.setEnabled(state);
pathBrowse.setEnabled(state);
fileList.setEnabled(state);
selectAll.setEnabled(state);
unselectAll.setEnabled(state);
fixSelected.setEnabled(state);
}
void Application::scan() {
fileInfo.reset();
fileList.reset();
string pathname = pathBox.text();
if(pathname == "") {
MessageWindow::information(application, "Please specify a directory to scan");
return;
}
pathname.transform("\\", "/");
if(pathname.endswith("/") == false) pathname.append("/");
if(directory::exists(pathname) == false) {
MessageWindow::warning(application, "Specified directory does not exist");
return;
}
enable(false);
scan(pathname);
enable(true);
if(fileInfo.size() == 0) {
MessageWindow::information(application, "All files are correct");
return;
}
unsigned counter = 0;
foreach(info, fileInfo) {
fileList.append(notdir(info.filename), info.problem, info.solution);
fileList.setChecked(counter++, true);
}
fileList.autoSizeColumns();
}
void Application::scan(const string &pathname) {
lstring files = directory::files(pathname);
foreach(file, files) {
OS::processEvents();
analyze({ pathname, file });
}
//recursion
lstring folders = directory::folders(pathname);
foreach(folder, folders) scan({ pathname, folder });
}
void Application::analyze(const string &filename) {
if(file::exists(filename) == false) return;
if(filename.iendswith(".sfc") || filename.iendswith(".bs") || filename.iendswith(".st")
|| filename.iendswith(".gb") || filename.iendswith(".gbc") || filename.iendswith(".sgb")
|| filename.iendswith(".smc") || filename.iendswith(".swc") || filename.iendswith(".fig") || filename.iendswith(".ufo")
|| filename.iendswith(".gd3") || filename.iendswith(".gd7") || filename.iendswith(".dx2") || filename.iendswith(".mgd")
|| filename.iendswith(".mgh") || filename.iendswith(".048") || filename.iendswith(".058") || filename.iendswith(".068")
|| filename.iendswith(".078") || filename.iendswith(".usa") || filename.iendswith(".eur") || filename.iendswith(".jap")
|| filename.iendswith(".aus") || filename.iendswith(".bsx")
) {
filemap map(filename, filemap::mode::read);
unsigned filesize = map.size();
SNESCartridge information(map.data(), filesize);
//note: the ordering of rules is very important
switch(information.type) {
case SNESCartridge::TypeNormal:
case SNESCartridge::TypeBsxSlotted:
case SNESCartridge::TypeBsxBios:
case SNESCartridge::TypeSufamiTurboBios:
case SNESCartridge::TypeSuperGameBoy1Bios:
case SNESCartridge::TypeSuperGameBoy2Bios: {
if((filesize & 0x7fff) == 512) {
FileInfo info;
info.filename = filename;
info.problem = "Copier header present";
info.solution = "Remove copier header";
fileInfo.append(info);
}
if(filename.endswith(".sfc") == false) {
FileInfo info;
info.filename = filename;
info.problem = "Wrong file extension";
info.solution = "Rename to .sfc";
fileInfo.append(info);
}
break;
}
case SNESCartridge::TypeBsx: {
if((filesize & 0x7fff) == 512) {
FileInfo info;
info.filename = filename;
info.problem = "Copier header present";
info.solution = "Remove copier header";
fileInfo.append(info);
}
if(filename.endswith(".bs") == false) {
FileInfo info;
info.filename = filename;
info.problem = "Wrong file extension";
info.solution = "Rename to .bs";
fileInfo.append(info);
}
break;
}
case SNESCartridge::TypeSufamiTurbo: {
if((filesize & 0x7fff) == 512) {
FileInfo info;
info.filename = filename;
info.problem = "Copier header present";
info.solution = "Remove copier header";
fileInfo.append(info);
}
if(filename.endswith(".st") == false) {
FileInfo info;
info.filename = filename;
info.problem = "Wrong file extension";
info.solution = "Rename to .st";
fileInfo.append(info);
}
break;
}
case SNESCartridge::TypeGameBoy: {
if(filename.endswith(".gb") == false && filename.endswith(".gbc") == false && filename.endswith(".sgb") == false) {
FileInfo info;
info.filename = filename;
info.problem = "Wrong file extension";
info.solution = "Rename to .gb";
fileInfo.append(info);
}
break;
}
}
}
}
void Application::repair() {
enable(false);
errors.reset();
for(unsigned n = 0; n < fileInfo.size(); n++) {
if(fileList.checked(n) == false) continue;
OS::processEvents();
FileInfo &info = fileInfo[n];
if(info.solution == "Remove copier header") {
file fp;
if(fp.open(info.filename, file::mode::read)) {
unsigned size = fp.size();
uint8_t *data = new uint8_t[size];
fp.read(data, size);
fp.close();
if(fp.open(info.filename, file::mode::write)) {
fp.write(data + 512, size - 512);
fp.close();
}
}
} else if(info.solution == "Rename to .sfc") {
rename(info.filename, string(nall::basename(info.filename), ".sfc"));
} else if(info.solution == "Rename to .bs") {
rename(info.filename, string(nall::basename(info.filename), ".bs"));
} else if(info.solution == "Rename to .st") {
rename(info.filename, string(nall::basename(info.filename), ".st"));
} else if(info.solution == "Rename to .gb") {
rename(info.filename, string(nall::basename(info.filename), ".gb"));
}
}
if(errors.size() == 0) {
MessageWindow::information(application, "Selected problems have been corrected");
} else {
string output;
for(unsigned i = 0; i < 3 && i < errors.size(); i++) output.append(string(errors[i], "\n"));
if(errors.size() > 3) output.append("\n(too many errors to show ...)");
MessageWindow::information(application, {
"Selected problems have been corrected, but there were errors:\n\n",
output
});
}
fileInfo.reset();
fileList.reset();
enable(true);
}
int main() {
application.main();
OS::main();
return 0;
}