mirror of https://github.com/bsnes-emu/bsnes.git
Update to v070r04 release.
byuu says: - fixed new config file input driver name (you'll have to delete your old config, or change to a different driver and back and restart) - fixed slot loader windows' OK button placement - fixed nall/directory.hpp when list size was zero - rewrote nall/function.hpp, no longer requires <functional> or union tricks - added state manager The state manager is a little bit different this time. It's functionally identical to bsnes/Qt, 100% of the way. But when you save slots, it stores them in RAM. It only writes the BSA archive upon ROM unload / program exit. Yes, this means that technically if the emulator crashes, you'll lose your states. But a) that very rarely happens, and b) the old way was thrashing the disk like crazy, every letter you typed dumped up to 8MB to disk. With this new method, I can simply store a boolean valid flag before each slot, and pack the file better. Before, a save on only slot 3 would be 3*state size (~1.2mb), it will now be 3bytes+state size (~400kb.) I have also added a proper signature because of this, so it will detect when you load an archive for a previous serializer version and ignore it. When you go to save (unload the game), if there are no valid slots, the BSA archive gets unlinked (deleted.) I am also planning a feature around the now-hidden "slot 0". My idea is for it to be a fallback slot. How many times have you loaded a state when you meant to save and said, "shit, now I lost some of my progress"? The idea is that whenever you load a state, right before loading, it will save to slot 0. When you unload the game, or exit the emulator, it will also save to slot 0. You will be able to load from slot 0 from the menu, but not save to it. It will appear at the bottom of the load list. And lastly, I'll add an advanced option to auto-load slot 0 if it exists, which will enable "close the emulator and restart where you left off." functionality.
This commit is contained in:
parent
3ffa44cef9
commit
775c111fef
|
@ -46,7 +46,7 @@ struct directory {
|
|||
}
|
||||
FindClose(handle);
|
||||
}
|
||||
sort(&list[0], list.size());
|
||||
if(list.size() > 0) sort(&list[0], list.size());
|
||||
return list;
|
||||
}
|
||||
|
||||
|
@ -70,7 +70,7 @@ struct directory {
|
|||
}
|
||||
FindClose(handle);
|
||||
}
|
||||
sort(&list[0], list.size());
|
||||
if(list.size() > 0) sort(&list[0], list.size());
|
||||
return list;
|
||||
}
|
||||
|
||||
|
@ -94,7 +94,7 @@ struct directory {
|
|||
}
|
||||
closedir(dp);
|
||||
}
|
||||
sort(&list[0], list.size());
|
||||
if(list.size() > 0) sort(&list[0], list.size());
|
||||
return list;
|
||||
|
||||
}
|
||||
|
@ -112,7 +112,7 @@ struct directory {
|
|||
}
|
||||
closedir(dp);
|
||||
}
|
||||
sort(&list[0], list.size());
|
||||
if(list.size() > 0) sort(&list[0], list.size());
|
||||
return list;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,103 +1,56 @@
|
|||
#ifndef NALL_FUNCTION_HPP
|
||||
#define NALL_FUNCTION_HPP
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
|
||||
namespace nall {
|
||||
template<typename T> class function;
|
||||
|
||||
template<typename R, typename... P>
|
||||
class function<R (P...)> {
|
||||
private:
|
||||
struct base1 { virtual void func1(P...) {} };
|
||||
struct base2 { virtual void func2(P...) {} };
|
||||
struct derived : base1, virtual base2 {};
|
||||
template<typename R, typename... P> class function<R (P...)> {
|
||||
struct container {
|
||||
virtual R operator()(P... p) const = 0;
|
||||
virtual container* copy() const = 0;
|
||||
} *callback;
|
||||
|
||||
struct data_t {
|
||||
R (*callback)(const data_t&, P...);
|
||||
R (*callback_global)(P...);
|
||||
struct {
|
||||
R (derived::*callback_member)(P...);
|
||||
void *callback_object;
|
||||
};
|
||||
std::function<R (P...)> callback_lambda;
|
||||
} data;
|
||||
struct global : container {
|
||||
R (*function)(P...);
|
||||
R operator()(P... p) const { return function(std::forward<P>(p)...); }
|
||||
container* copy() const { return new global(function); }
|
||||
global(R (*function_)(P...)) : function(function_) {}
|
||||
};
|
||||
|
||||
static R callback_global(const data_t &data, P... p) {
|
||||
return data.callback_global(std::forward<P>(p)...);
|
||||
}
|
||||
template<typename C> struct member : container {
|
||||
R (C::*function)(P...);
|
||||
C *object;
|
||||
R operator()(P... p) const { return (object->*function)(std::forward<P>(p)...); }
|
||||
container* copy() const { return new member(function, object); }
|
||||
member(R (C::*function_)(P...), C *object_) : function(function_), object(object_) {}
|
||||
};
|
||||
|
||||
template<typename C>
|
||||
static R callback_member(const data_t &data, P... p) {
|
||||
return (((C*)data.callback_object)->*((R (C::*&)(P...))data.callback_member))(std::forward<P>(p)...);
|
||||
}
|
||||
|
||||
static R callback_lambda(const data_t &data, P... p) {
|
||||
return data.callback_lambda(std::forward<P>(p)...);
|
||||
}
|
||||
template<typename L> struct lambda : container {
|
||||
L *object;
|
||||
R operator()(P... p) const { return (*object)(std::forward<P>(p)...); }
|
||||
container* copy() const { return new lambda(*object); }
|
||||
lambda(const L& object_) { object = new L(object_); }
|
||||
~lambda() { delete object; }
|
||||
};
|
||||
|
||||
public:
|
||||
R operator()(P... p) const { return data.callback(data, std::forward<P>(p)...); }
|
||||
operator bool() const { return data.callback; }
|
||||
void reset() { data.callback = 0; }
|
||||
operator bool() const { return callback; }
|
||||
R operator()(P... p) const { return (*callback)(std::forward<P>(p)...); }
|
||||
|
||||
function& operator=(const function &source) {
|
||||
data.callback = source.data.callback;
|
||||
data.callback_global = source.data.callback_global;
|
||||
data.callback_member = source.data.callback_member;
|
||||
data.callback_object = source.data.callback_object;
|
||||
data.callback_lambda = source.data.callback_lambda;
|
||||
if(callback) { delete callback; callback = 0; }
|
||||
if(source.callback) callback = source.callback->copy();
|
||||
return *this;
|
||||
}
|
||||
|
||||
function(const function &source) {
|
||||
operator=(source);
|
||||
}
|
||||
|
||||
//no pointer
|
||||
function() {
|
||||
data.callback = 0;
|
||||
}
|
||||
|
||||
//symbolic link pointer (nall/dl.hpp::sym, etc)
|
||||
function(void *callback) {
|
||||
data.callback = callback ? &callback_global : 0;
|
||||
data.callback_global = (R (*)(P...))callback;
|
||||
}
|
||||
|
||||
//global function pointer
|
||||
function(R (*callback)(P...)) {
|
||||
data.callback = &callback_global;
|
||||
data.callback_global = callback;
|
||||
}
|
||||
|
||||
//member function pointer
|
||||
template<typename C>
|
||||
function(R (C::*callback)(P...), C *object) {
|
||||
static_assert(sizeof data.callback_member >= sizeof callback, "callback_member is too small");
|
||||
data.callback = &callback_member<C>;
|
||||
(R (C::*&)(P...))data.callback_member = callback;
|
||||
data.callback_object = object;
|
||||
}
|
||||
|
||||
//const member function pointer
|
||||
template<typename C>
|
||||
function(R (C::*callback)(P...) const, C *object) {
|
||||
static_assert(sizeof data.callback_member >= sizeof callback, "callback_member is too small");
|
||||
data.callback = &callback_member<C>;
|
||||
(R (C::*&)(P...))data.callback_member = (R (C::*&)(P...))callback;
|
||||
data.callback_object = object;
|
||||
}
|
||||
|
||||
//lambda function pointer
|
||||
template<typename T>
|
||||
function(const T &callback) {
|
||||
static_assert(std::is_same<R, typename std::result_of<T(P...)>::type>::value, "lambda mismatch");
|
||||
data.callback = &callback_lambda;
|
||||
data.callback_lambda = callback;
|
||||
}
|
||||
function(const function &source) { operator=(source); }
|
||||
function() : callback(0) {}
|
||||
function(void *function) : callback(0) { if(function) callback = new global((R (*)(P...))function); }
|
||||
function(R (*function)(P...)) { callback = new global(function); }
|
||||
template<typename C> function(R (C::*function)(P...), C *object) { callback = new member<C>(function, object); }
|
||||
template<typename C> function(R (C::*function)(P...) const, C *object) { callback = new member<C>((R (C::*)(P...))function, object); }
|
||||
template<typename L> function(const L& object) { callback = new lambda<L>(object); }
|
||||
~function() { if(callback) delete callback; }
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -112,6 +112,7 @@ namespace nall {
|
|||
imode = Size;
|
||||
idata = 0;
|
||||
isize = 0;
|
||||
icapacity = 0;
|
||||
}
|
||||
|
||||
serializer(unsigned capacity) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
namespace SNES {
|
||||
namespace Info {
|
||||
static const char Name[] = "bsnes";
|
||||
static const char Version[] = "070.03";
|
||||
static const char Version[] = "070.04";
|
||||
static const unsigned SerializerVersion = 13;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,8 +76,8 @@ void Cartridge::unload() {
|
|||
saveMemory(SNES::memory::stBram, slotBName, ".srm");
|
||||
saveMemory(SNES::memory::gbram, slotAName, ".sav");
|
||||
saveMemory(SNES::memory::gbrtc, slotAName, ".rtc");
|
||||
baseName = slotAName = slotBName = "";
|
||||
utility.cartridgeUnloaded();
|
||||
baseName = slotAName = slotBName = "";
|
||||
}
|
||||
|
||||
bool Cartridge::loadCartridge(SNES::MappedRAM &memory, string &XML, const char *filename) {
|
||||
|
|
|
@ -2,7 +2,7 @@ FileBrowser fileBrowser;
|
|||
|
||||
void FileBrowser::create() {
|
||||
application.windows.append(this);
|
||||
Window::create(0, 0, 256, 256, "Load Cartridge");
|
||||
Window::create(0, 0, 256, 256);
|
||||
setDefaultFont(application.proportionalFont);
|
||||
|
||||
unsigned x = 5, y = 5, height = Style::TextBoxHeight;
|
||||
|
@ -32,24 +32,31 @@ void FileBrowser::fileOpen(FileBrowser::Mode requestedMode, function<void (strin
|
|||
filters.reset();
|
||||
switch(mode = requestedMode) {
|
||||
case Mode::Cartridge: {
|
||||
setTitle("Load Cartridge");
|
||||
filters.append(".sfc");
|
||||
break;
|
||||
}
|
||||
case Mode::Satellaview: {
|
||||
setTitle("Load Satellaview Cartridge");
|
||||
filters.append(".bs");
|
||||
break;
|
||||
}
|
||||
case Mode::SufamiTurbo: {
|
||||
setTitle("Load Sufami Turbo Cartridge");
|
||||
filters.append(".st");
|
||||
break;
|
||||
}
|
||||
case Mode::GameBoy: {
|
||||
setTitle("Load Game Boy Cartridge");
|
||||
filters.append(".gb");
|
||||
filters.append(".gbc");
|
||||
filters.append(".sgb");
|
||||
break;
|
||||
}
|
||||
case Mode::Shader: {
|
||||
setTitle("Load Pixel Shader");
|
||||
filters.append(".shader");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -74,6 +74,7 @@ void MainWindow::create() {
|
|||
toolsStateLoad5.create(toolsStateLoad, "Slot 5");
|
||||
toolsSeparator.create(tools);
|
||||
toolsCheatEditor.create(tools, "Cheat Editor ...");
|
||||
toolsStateManager.create(tools, "State Manager ...");
|
||||
|
||||
help.create(*this, "Help");
|
||||
helpAbout.create(help, "About ...");
|
||||
|
@ -148,6 +149,7 @@ void MainWindow::create() {
|
|||
toolsStateLoad5.onTick = []() { utility.loadState(5); };
|
||||
|
||||
toolsCheatEditor.onTick = []() { cheatEditor.setVisible(); };
|
||||
toolsStateManager.onTick = []() { stateManager.setVisible(); };
|
||||
|
||||
helpAbout.onTick = []() {
|
||||
MessageWindow::information(mainWindow, string(
|
||||
|
|
|
@ -49,6 +49,7 @@ struct MainWindow : Window {
|
|||
MenuItem toolsStateLoad5;
|
||||
MenuSeparator toolsSeparator;
|
||||
MenuItem toolsCheatEditor;
|
||||
MenuItem toolsStateManager;
|
||||
Menu help;
|
||||
MenuItem helpAbout;
|
||||
Viewport viewport;
|
||||
|
|
|
@ -16,7 +16,7 @@ void SingleSlotLoader::create() {
|
|||
slotPath.create(*this, x + 50, y, 300, height);
|
||||
slotBrowse.create(*this, x + 355, y, height, height, "..."); y += height + 5;
|
||||
|
||||
okButton.create(*this, x + width - 85, y, 80, Style::ButtonHeight, "Ok"); y += Style::ButtonHeight + 5;
|
||||
okButton.create(*this, x + width - 90, y, 80, Style::ButtonHeight, "Ok"); y += Style::ButtonHeight + 5;
|
||||
|
||||
setGeometry(160, 160, width, y);
|
||||
|
||||
|
@ -106,7 +106,7 @@ void DoubleSlotLoader::create() {
|
|||
slotBPath.create(*this, x + 50, y, 300, height);
|
||||
slotBBrowse.create(*this, x + 355, y, height, height, "..."); y += height + 5;
|
||||
|
||||
okButton.create(*this, x + width - 85, y, 80, Style::ButtonHeight, "Ok"); y += Style::ButtonHeight + 5;
|
||||
okButton.create(*this, x + width - 90, y, 80, Style::ButtonHeight, "Ok"); y += Style::ButtonHeight + 5;
|
||||
|
||||
setGeometry(160, 160, width, y);
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ void Application::main(int argc, char **argv) {
|
|||
|
||||
if(config.video.driver == "") config.video.driver = video.default_driver();
|
||||
if(config.audio.driver == "") config.audio.driver = audio.default_driver();
|
||||
if(config.input.driver == "") config.input.driver = video.default_driver();
|
||||
if(config.input.driver == "") config.input.driver = input.default_driver();
|
||||
|
||||
palette.update();
|
||||
mainWindow.create();
|
||||
|
@ -49,6 +49,7 @@ void Application::main(int argc, char **argv) {
|
|||
inputSettings.create();
|
||||
advancedSettings.create();
|
||||
cheatEditor.create();
|
||||
stateManager.create();
|
||||
utility.setScale(config.video.scale);
|
||||
mainWindow.setVisible();
|
||||
os.run();
|
||||
|
|
|
@ -63,11 +63,11 @@ void CheatEditor::create() {
|
|||
cheatList.create(*this, x, y, 500, 250, "Slot\tOn\tCode\tDescription"); y += 255;
|
||||
cheatList.setHeaderVisible();
|
||||
|
||||
codeLabel.create(*this, x, y, 80, Style::EditBoxHeight, "Code(s):");
|
||||
codeEdit.create (*this, x + 80, y, 420, Style::EditBoxHeight); y += Style::EditBoxHeight + 5;
|
||||
codeLabel.create(*this, x, y, 80, Style::TextBoxHeight, "Code(s):");
|
||||
codeEdit.create (*this, x + 80, y, 420, Style::TextBoxHeight); y += Style::TextBoxHeight + 5;
|
||||
|
||||
descLabel.create(*this, x, y, 80, Style::EditBoxHeight, "Description:");
|
||||
descEdit.create (*this, x + 80, y, 420, Style::EditBoxHeight); y+= Style::EditBoxHeight + 5;
|
||||
descLabel.create(*this, x, y, 80, Style::TextBoxHeight, "Description:");
|
||||
descEdit.create (*this, x + 80, y, 420, Style::TextBoxHeight); y+= Style::TextBoxHeight + 5;
|
||||
|
||||
setGeometry(160, 160, 510, y);
|
||||
synchronize();
|
||||
|
|
|
@ -0,0 +1,144 @@
|
|||
StateManager stateManager;
|
||||
|
||||
void StateManager::create() {
|
||||
application.windows.append(this);
|
||||
Window::create(0, 0, 256, 256, "State Manager");
|
||||
setDefaultFont(application.proportionalFont);
|
||||
|
||||
unsigned x = 5, y = 5;
|
||||
|
||||
stateList.create(*this, x, y, 500, 250, "Slot\tDescription"); y += 255;
|
||||
stateList.setHeaderVisible();
|
||||
|
||||
descLabel.create(*this, x, y, 80, Style::TextBoxHeight, "Description:");
|
||||
descEdit.create(*this, x + 80, y, 420, Style::TextBoxHeight); y += Style::TextBoxHeight + 5;
|
||||
|
||||
loadButton.create(*this, x + 505 - 85 - 85 - 85, y, 80, Style::ButtonHeight, "Load");
|
||||
saveButton.create(*this, x + 505 - 85 - 85, y, 80, Style::ButtonHeight, "Save");
|
||||
eraseButton.create(*this, x + 505 - 85, y, 80, Style::ButtonHeight, "Erase"); y += Style::ButtonHeight + 5;
|
||||
|
||||
setGeometry(160, 160, 510, y);
|
||||
synchronize();
|
||||
|
||||
stateList.onActivate = { &StateManager::slotLoad, this };
|
||||
stateList.onChange = { &StateManager::synchronize, this };
|
||||
descEdit.onChange = { &StateManager::slotSaveDescription, this };
|
||||
loadButton.onTick = { &StateManager::slotLoad, this };
|
||||
saveButton.onTick = { &StateManager::slotSave, this };
|
||||
eraseButton.onTick = { &StateManager::slotErase, this };
|
||||
}
|
||||
|
||||
void StateManager::synchronize() {
|
||||
descEdit.setText("");
|
||||
descEdit.setEnabled(false);
|
||||
if(auto position = stateList.selection()) {
|
||||
if(slot[position()].capacity() > 0) {
|
||||
descEdit.setText(slotLoadDescription(position()));
|
||||
descEdit.setEnabled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StateManager::refresh() {
|
||||
for(unsigned i = 0; i < 32; i++) {
|
||||
stateList.setItem(i, string(
|
||||
strunsigned<2, ' '>(i + 1), "\t",
|
||||
slotLoadDescription(i)
|
||||
));
|
||||
}
|
||||
stateList.resizeColumnsToContent();
|
||||
}
|
||||
|
||||
void StateManager::load() {
|
||||
stateList.reset();
|
||||
for(unsigned i = 0; i < 32; i++) {
|
||||
slot[i] = serializer();
|
||||
stateList.addItem("");
|
||||
}
|
||||
|
||||
string filename = string(cartridge.baseName, ".bsa");
|
||||
file fp;
|
||||
if(fp.open(string(cartridge.baseName, ".bsa"), file::mode_read)) {
|
||||
if(fp.readl(4) == 0x31415342) {
|
||||
if(fp.readl(4) == SNES::Info::SerializerVersion) {
|
||||
for(unsigned i = 0; i < 32; i++) {
|
||||
if(fp.read() == false) continue;
|
||||
uint8_t *data = new uint8_t[SNES::system.serialize_size()];
|
||||
fp.read(data, SNES::system.serialize_size());
|
||||
slot[i] = serializer(data, SNES::system.serialize_size());
|
||||
delete[] data;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
void StateManager::save() {
|
||||
bool hasSave = false;
|
||||
for(unsigned i = 0; i < 32; i++) {
|
||||
if(slot[i].capacity() > 0) hasSave = true;
|
||||
}
|
||||
|
||||
if(hasSave == false) {
|
||||
unlink(string(cartridge.baseName, ".bsa"));
|
||||
} else {
|
||||
file fp;
|
||||
if(fp.open(string(cartridge.baseName, ".bsa"), file::mode_write)) {
|
||||
fp.writel(0x31415342, 4); //'BSA1'
|
||||
fp.writel(SNES::Info::SerializerVersion, 4);
|
||||
|
||||
for(unsigned i = 0; i < 32; i++) {
|
||||
if(slot[i].capacity() == 0) {
|
||||
fp.write(false);
|
||||
} else {
|
||||
fp.write(true);
|
||||
fp.write(slot[i].data(), slot[i].capacity());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StateManager::slotLoad() {
|
||||
if(auto position = stateList.selection()) {
|
||||
serializer s(slot[position()].data(), slot[position()].capacity());
|
||||
SNES::system.unserialize(s);
|
||||
}
|
||||
}
|
||||
|
||||
void StateManager::slotSave() {
|
||||
if(auto position = stateList.selection()) {
|
||||
SNES::system.runtosave();
|
||||
slot[position()] = SNES::system.serialize();
|
||||
}
|
||||
refresh();
|
||||
synchronize();
|
||||
descEdit.setFocused();
|
||||
}
|
||||
|
||||
void StateManager::slotErase() {
|
||||
if(auto position = stateList.selection()) {
|
||||
slot[position()] = serializer();
|
||||
}
|
||||
refresh();
|
||||
synchronize();
|
||||
}
|
||||
|
||||
string StateManager::slotLoadDescription(unsigned i) {
|
||||
if(slot[i].capacity() == 0) return "(empty)";
|
||||
char text[512];
|
||||
strlcpy(text, (const char*)slot[i].data() + HeaderLength, 512);
|
||||
return text;
|
||||
}
|
||||
|
||||
void StateManager::slotSaveDescription() {
|
||||
if(auto position = stateList.selection()) {
|
||||
string text = descEdit.text();
|
||||
if(slot[position()].capacity() > 0) {
|
||||
strlcpy((char*)slot[position()].data() + HeaderLength, (const char*)text, 512);
|
||||
}
|
||||
}
|
||||
refresh();
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
struct StateManager : Window {
|
||||
ListBox stateList;
|
||||
Label descLabel;
|
||||
TextBox descEdit;
|
||||
Button loadButton;
|
||||
Button saveButton;
|
||||
Button eraseButton;
|
||||
|
||||
void create();
|
||||
void synchronize();
|
||||
void refresh();
|
||||
void load();
|
||||
void save();
|
||||
void slotLoad();
|
||||
void slotSave();
|
||||
void slotErase();
|
||||
string slotLoadDescription(unsigned slot);
|
||||
void slotSaveDescription();
|
||||
|
||||
private:
|
||||
enum : unsigned {
|
||||
HeaderLength = 28,
|
||||
DescriptionLength = 512,
|
||||
};
|
||||
serializer slot[32];
|
||||
};
|
||||
|
||||
extern StateManager stateManager;
|
|
@ -1,2 +1,3 @@
|
|||
#include "../base.hpp"
|
||||
#include "cheat-editor.cpp"
|
||||
#include "state-manager.cpp"
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
#include "cheat-editor.hpp"
|
||||
#include "state-manager.hpp"
|
||||
|
|
|
@ -60,6 +60,7 @@ void Utility::setShader() {
|
|||
void Utility::cartridgeLoaded() {
|
||||
SNES::system.power();
|
||||
cheatEditor.load(cartridge.baseName);
|
||||
stateManager.load();
|
||||
mainWindow.synchronize();
|
||||
utility.setTitle(notdir(cartridge.baseName));
|
||||
utility.showMessage(string("Loaded ", notdir(cartridge.baseName)));
|
||||
|
@ -68,6 +69,7 @@ void Utility::cartridgeLoaded() {
|
|||
void Utility::cartridgeUnloaded() {
|
||||
SNES::cartridge.unload();
|
||||
cheatEditor.save(cartridge.baseName);
|
||||
stateManager.save();
|
||||
mainWindow.synchronize();
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue