mirror of
https://github.com/bsnes-emu/bsnes.git
synced 2025-04-02 10:42:14 -04:00
byuu says: Finally, a new release. I have been very busy finishing up SNES box, cartridge and PCB scanning plus cataloguing the data, however this release still has some significant improvements. Most notably would be randomization on startup. This will help match the behavior of real hardware and uninitialized memory + registers. It should help catch homebrew software that forgets to initialize things properly. Of course, I was not able to test the complete library, so it is possible that if I've randomized anything that should be constant, that this could cause a regression. You can disable this randomization for netplay or to work around any incompatibilities by editing bsnes.cfg and setting snes.random to false. The GUI also received some updates. Widget sizes are now computed based on font sizes, giving it a perfectly native look (because it is native.) I've also added a hotkey remapping screen to the input settings. Not only can you remap inputs to controllers now, but those who did not know the hotkey bindings can now quickly see which ones exist and what they are mapped to. Changelog (since v077): - memory and most registers are now randomly initialized on power-up - fixed auto joypad polling issue in Super Star Wars - fixed .nec and .rtc file extensions (they were missing the dot) [krom] - PPU/accuracy now clears overscan region on any frame when it is disabled - PPU/compatibility no longer auto-blends hires pixels (use NTSC filter for this) - added hotkey remapping dialog to input settings window - added a few new hotkeys, including quick-reset - phoenix API now auto-sizes widgets based on font sizes - file dialog once again remembers previously selected file when possible
319 lines
9.2 KiB
C++
Executable file
319 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;
|
|
}
|