lsnes rr0-β0

The first release for public testing.
This commit is contained in:
Ilari Liusvaara 2011-09-13 17:50:18 +03:00
commit c932df0faa
62 changed files with 85175 additions and 0 deletions

8
.gitignore vendored Normal file
View file

@ -0,0 +1,8 @@
*.o
*.a
*.lib
*.obj
*.exe
docs
rom
core

1716
Doxyfile Normal file

File diff suppressed because it is too large Load diff

49
Makefile Normal file
View file

@ -0,0 +1,49 @@
FONT_SRC := unifontfull-5.1.20080820.hex
CC := g++-4.5
HOSTCC = $(CC)
OBJECTS = controllerdata.o fieldsplit.o memorymanip.o misc.o movie.o moviefile.o render.o rom.o zip.o fonts/font.o videodumper.o videodumper2.o keymapper.o window.o window-sdl.o settings.o framerate.o mainloop.o rrdata.o specialframes.o png.o lsnesrc.o memorywatch.o
PROGRAMS = lsnes.exe movietrunctest.exe
CFLAGS = $(shell sdl-config --cflags) $(USER_CFLAGS)
HOSTCCFLAGS = $(USER_HOSTCCFLAGS)
LDFLAGS = $(shell sdl-config --libs) $(USER_LDFLAGS)
ifdef NO_LUA
OBJECTS += lua-dummy.o
else
OBJECTS += lua.o
CFLAGS += $(shell pkg-config lua5.1 --cflags)
LDFLAGS += $(shell pkg-config lua5.1 --libs)
endif
ifdef NO_THREADS
CFLAGS += -DNO_THREADS
endif
ifdef BSNES_IS_COMPAT
CFLAGS += -DBSNES_IS_COMPAT
endif
all: $(PROGRAMS)
.PRECIOUS: %.exe %.o
%.exe: %.o $(OBJECTS)
$(CC) -o $@ $^ $(BSNES_PATH)/out/libsnes.a -ldl -lboost_iostreams -lboost_filesystem -lboost_system -lz $(LDFLAGS)
%.o: %.cpp
$(CC) -I. -g -std=c++0x -I$(BSNES_PATH) -c -o $@ $< $(CFLAGS)
fonts/font.o: fonts/$(FONT_SRC) fonts/parsehexfont.exe
fonts/parsehexfont.exe <fonts/$(FONT_SRC) >fonts/font.cpp
$(HOSTCC) -std=c++0x $(HOSTCCFLAGS) -c -o fonts/font.o fonts/font.cpp
$(HOSTCC) -std=c++0x $(HOSTCCFLAGS) -o fonts/verifyhexfont.exe fonts/verifyhexfont.cpp fonts/font.o
fonts/verifyhexfont.exe
fonts/parsehexfont.exe: fonts/parsehexfont.cpp
$(HOSTCC) -std=c++0x $(HOSTCCFLAGS) -o $@ $^
clean:
rm -f fonts/font.o fonts/font.cpp fonts/verifyhexfont.exe fonts/parsehexfont.exe $(OBJECTS) romtest1.o $(PROGRAMS) lsnes.o

395
controllerdata.cpp Normal file
View file

@ -0,0 +1,395 @@
#include "lsnes.hpp"
#include "controllerdata.hpp"
#include <sstream>
#include <iostream>
#include <cstring>
void cdecode::system(fieldsplitter& line, short* controls, unsigned version) throw(std::bad_alloc, std::runtime_error)
{
static controlfield_system p(version);
std::string tmp = line;
p.set_field(tmp);
for(unsigned i = 0; i < MAX_SYSTEM_CONTROLS; i++)
controls[i] = p[i];
}
namespace
{
inline unsigned ccindex(unsigned port, unsigned controller, unsigned control) throw(std::logic_error)
{
if(port >= MAX_PORTS || controller >= MAX_CONTROLLERS_PER_PORT || control >= CONTROLLER_CONTROLS) {
std::ostringstream x;
x << "ccindex: Invalid (port, controller, control) tuple (" << port << "," << controller
<< "," << control << ")";
throw std::logic_error(x.str());
}
return MAX_SYSTEM_CONTROLS + port * CONTROLLER_CONTROLS * MAX_CONTROLLERS_PER_PORT +
CONTROLLER_CONTROLS * controller + control;
}
template<unsigned components, class cfield>
void decode(unsigned port, fieldsplitter& line, short* controls) throw(std::bad_alloc, std::runtime_error)
{
static cfield p;
for(unsigned j = 0; j < components; j++) {
std::string tmp = line;
p.set_field(tmp);
for(unsigned i = 0; i < p.indices(); i++)
controls[MAX_SYSTEM_CONTROLS + port * CONTROLLER_CONTROLS * MAX_CONTROLLERS_PER_PORT +
CONTROLLER_CONTROLS * j + i] = p[i];
}
}
}
unsigned ccindex2(unsigned port, unsigned controller, unsigned control) throw(std::logic_error)
{
return ccindex(port, controller, control);
}
void cdecode::none(unsigned port, fieldsplitter& line, short* controls) throw(std::bad_alloc, std::runtime_error)
{
decode<0, controlfield_gamepad>(port, line, controls);
}
void cdecode::gamepad(unsigned port, fieldsplitter& line, short* controls) throw(std::bad_alloc, std::runtime_error)
{
decode<1, controlfield_gamepad>(port, line, controls);
}
void cdecode::multitap(unsigned port, fieldsplitter& line, short* controls) throw(std::bad_alloc, std::runtime_error)
{
decode<4, controlfield_gamepad>(port, line, controls);
}
void cdecode::mouse(unsigned port, fieldsplitter& line, short* controls) throw(std::bad_alloc, std::runtime_error)
{
decode<1, controlfield_mousejustifier>(port, line, controls);
}
void cdecode::superscope(unsigned port, fieldsplitter& line, short* controls) throw(std::bad_alloc, std::runtime_error)
{
decode<1, controlfield_superscope>(port, line, controls);
}
void cdecode::justifier(unsigned port, fieldsplitter& line, short* controls) throw(std::bad_alloc, std::runtime_error)
{
decode<1, controlfield_mousejustifier>(port, line, controls);
}
void cdecode::justifiers(unsigned port, fieldsplitter& line, short* controls) throw(std::bad_alloc, std::runtime_error)
{
decode<2, controlfield_mousejustifier>(port, line, controls);
}
std::string cencode::system(const short* controls) throw(std::bad_alloc)
{
std::string x = "..";
if(controls[0])
x[0] = 'F';
if(controls[1])
x[1] = 'R';
if(controls[2] || controls[3]) {
std::ostringstream out;
out << x << " " << controls[2] << " " << controls[3];
x = out.str();
}
return x;
}
std::string cencode::none(unsigned port, const short* controls) throw(std::bad_alloc)
{
return "";
}
std::string cencode::gamepad(unsigned port, const short* controls) throw(std::bad_alloc)
{
const char chars[] = "BYsSudlrAXLR";
std::string x = "|............";
for(unsigned i = 0; i < 12; i++)
if(controls[ccindex(port, 0, i)])
x[i + 1] = chars[i];
return x;
}
std::string cencode::multitap(unsigned port, const short* controls) throw(std::bad_alloc)
{
const char chars[] = "BYsSudlrAXLR";
std::string x = "|............|............|............|............";
for(unsigned j = 0; j < 4; j++)
for(unsigned i = 0; i < 12; i++)
if(controls[ccindex(port, j, i)])
x[13 * j + i + 1] = chars[i];
return x;
}
std::string cencode::mouse(unsigned port, const short* controls) throw(std::bad_alloc)
{
std::ostringstream s;
s << "|";
s << (controls[ccindex(port, 0, 2)] ? 'L' : '.');
s << (controls[ccindex(port, 0, 3)] ? 'R' : '.');
s << " " << controls[ccindex(port, 0, 0)] << " " << controls[ccindex(port, 0, 1)];
return s.str();
}
std::string cencode::superscope(unsigned port, const short* controls) throw(std::bad_alloc)
{
std::ostringstream s;
s << "|";
s << (controls[ccindex(port, 0, 2)] ? 'T' : '.');
s << (controls[ccindex(port, 0, 3)] ? 'C' : '.');
s << (controls[ccindex(port, 0, 4)] ? 'U' : '.');
s << (controls[ccindex(port, 0, 5)] ? 'P' : '.');
s << " " << controls[ccindex(port, 0, 0)] << " " << controls[ccindex(port, 0, 1)];
return s.str();
}
std::string cencode::justifier(unsigned port, const short* controls) throw(std::bad_alloc)
{
std::ostringstream s;
s << "|";
s << (controls[ccindex(port, 0, 2)] ? 'T' : '.');
s << (controls[ccindex(port, 0, 3)] ? 'S' : '.');
s << " " << controls[ccindex(port, 0, 0)] << " " << controls[ccindex(port, 0, 1)];
return s.str();
}
std::string cencode::justifiers(unsigned port, const short* controls) throw(std::bad_alloc)
{
std::ostringstream s;
s << "|";
s << (controls[ccindex(port, 0, 2)] ? 'T' : '.');
s << (controls[ccindex(port, 0, 3)] ? 'S' : '.');
s << " " << controls[ccindex(port, 0, 0)] << " " << controls[ccindex(port, 0, 1)];
s << "|";
s << (controls[ccindex(port, 1, 2)] ? 'T' : '.');
s << (controls[ccindex(port, 1, 3)] ? 'S' : '.');
s << " " << controls[ccindex(port, 1, 0)] << " " << controls[ccindex(port, 1, 1)];
return s.str();
}
controls_t controls_t::operator^(controls_t other) throw()
{
controls_t x;
for(size_t i = 0; i < TOTAL_CONTROLS; i++)
x.controls[i] = controls[i] ^ ((i < MAX_SYSTEM_CONTROLS) ? 0 : other.controls[i]);
return x;
}
controls_t::controls_t(bool sync) throw()
{
memset(controls, 0, sizeof(controls));
if(sync)
controls[CONTROL_FRAME_SYNC] = 1;
}
const short& controls_t::operator()(unsigned port, unsigned controller, unsigned control) const throw(std::logic_error)
{
return controls[ccindex(port, controller, control)];
}
const short& controls_t::operator()(unsigned control) const throw(std::logic_error)
{
if(control >= TOTAL_CONTROLS)
throw std::logic_error("controls_t::operator(): Invalid control index");
return controls[control];
}
short& controls_t::operator()(unsigned port, unsigned controller, unsigned control) throw(std::logic_error)
{
return controls[ccindex(port, controller, control)];
}
short& controls_t::operator()(unsigned control) throw(std::logic_error)
{
if(control >= TOTAL_CONTROLS)
throw std::logic_error("controls_t::operator(): Invalid control index");
return controls[control];
}
controls_t::controls_t(const std::string& line, const std::vector<cdecode::fn_t>& decoders, unsigned version)
throw(std::bad_alloc, std::runtime_error)
{
memset(controls, 0, sizeof(controls));
fieldsplitter _line(line);
cdecode::system(_line, controls, version);
for(unsigned i = 0; i < decoders.size(); i++)
decoders[i](i, _line, controls);
}
std::string controls_t::tostring(const std::vector<cencode::fn_t>& encoders) const throw(std::bad_alloc)
{
std::string x;
x = cencode::system(controls);
for(unsigned i = 0; i < encoders.size(); i++)
x = x + encoders[i](i, controls);
return x;
}
bool controls_t::operator==(const controls_t& c) const throw()
{
for(size_t i = 0; i < TOTAL_CONTROLS; i++)
if(controls[i] != c.controls[i])
return false;
return true;
}
controlfield::controlfield(std::vector<control_subfield> _subfields) throw(std::bad_alloc)
{
subfields = _subfields;
size_t needed = 0;
for(size_t i = 0; i < subfields.size(); i++)
if(needed <= subfields[i].index)
needed = subfields[i].index + 1;
values.resize(needed);
for(size_t i = 0; i < needed; i++)
values[i] = 0;
}
short controlfield::operator[](unsigned index) throw(std::logic_error)
{
if(index >= values.size())
throw std::logic_error("controlfield::operator[]: Bad subfield index");
return values[index];
}
unsigned controlfield::indices() throw()
{
return values.size();
}
void controlfield::set_field(const std::string& str) throw(std::bad_alloc)
{
size_t pos = 0;
for(unsigned i = 0; i < subfields.size(); i++)
//Buttons always come first.
if(subfields[i].type == control_subfield::BUTTON) {
values[subfields[i].index] = (pos < str.length() && str[pos] != '.' && str[pos] != ' ' &&
str[pos] != '\t') ? 1 : 0;
pos++;
}
for(unsigned i = 0; i < subfields.size(); i++)
//Then the axis.
if(subfields[i].type == control_subfield::AXIS) {
short value = 0;
//Skip whitespace before subfield.
while(pos < str.length() && (str[pos] == ' ' || str[pos] == '\t'))
pos++;
if(pos < str.length())
value = (short)atoi(str.c_str() + pos);
values[subfields[i].index] = value;
}
}
namespace
{
std::vector<struct control_subfield> gamepad() throw(std::bad_alloc)
{
static std::vector<struct control_subfield> g;
static bool init = false;
if(!init) {
for(unsigned i = 0; i < 12; i++)
g.push_back(control_subfield(i, control_subfield::BUTTON));
init = true;
}
return g;
}
std::vector<struct control_subfield> mousejustifier() throw(std::bad_alloc)
{
static std::vector<struct control_subfield> g;
static bool init = false;
if(!init) {
g.push_back(control_subfield(0, control_subfield::AXIS));
g.push_back(control_subfield(1, control_subfield::AXIS));
g.push_back(control_subfield(2, control_subfield::BUTTON));
g.push_back(control_subfield(3, control_subfield::BUTTON));
init = true;
}
return g;
}
std::vector<struct control_subfield> superscope() throw(std::bad_alloc)
{
static std::vector<struct control_subfield> g;
static bool init = false;
if(!init) {
g.push_back(control_subfield(0, control_subfield::AXIS));
g.push_back(control_subfield(1, control_subfield::AXIS));
g.push_back(control_subfield(2, control_subfield::BUTTON));
g.push_back(control_subfield(3, control_subfield::BUTTON));
g.push_back(control_subfield(4, control_subfield::BUTTON));
g.push_back(control_subfield(5, control_subfield::BUTTON));
init = true;
}
return g;
}
std::vector<struct control_subfield> csystem(unsigned version) throw(std::bad_alloc, std::runtime_error)
{
static std::vector<struct control_subfield> g0;
static bool init0 = false;
if(version == 0) {
if(!init0) {
g0.push_back(control_subfield(0, control_subfield::BUTTON));
g0.push_back(control_subfield(1, control_subfield::BUTTON));
g0.push_back(control_subfield(2, control_subfield::AXIS));
g0.push_back(control_subfield(3, control_subfield::AXIS));
init0 = true;
}
return g0;
} else
throw std::runtime_error("csystem: Unknown record version");
}
}
controlfield_system::controlfield_system(unsigned version) throw(std::bad_alloc, std::runtime_error)
: controlfield(csystem(version))
{
}
controlfield_gamepad::controlfield_gamepad() throw(std::bad_alloc)
: controlfield(gamepad())
{
}
controlfield_mousejustifier::controlfield_mousejustifier() throw(std::bad_alloc)
: controlfield(mousejustifier())
{
}
controlfield_superscope::controlfield_superscope() throw(std::bad_alloc)
: controlfield(superscope())
{
}
control_subfield::control_subfield(unsigned _index, enum control_subfield::control_subfield_type _type) throw()
{
index = _index;
type = _type;
}
const port_type& port_type::lookup(const std::string& name, bool port2) throw(std::bad_alloc,
std::runtime_error)
{
for(unsigned i = 0; i <= PT_LAST_CTYPE; i++) {
if(name != port_types[i].name)
continue;
if(!port2 && !port_types[i].valid_port1)
throw std::runtime_error("Can't connect " + name + " to port #1");
return port_types[i];
}
throw std::runtime_error("Unknown port type '" + name + "'");
}
port_type port_types[] = {
{ "none", cdecode::none, cencode::none, PT_NONE, 0, DT_NONE, true },
{ "gamepad", cdecode::gamepad, cencode::gamepad, PT_GAMEPAD, 1, DT_GAMEPAD, true },
{ "multitap", cdecode::multitap, cencode::multitap, PT_MULTITAP, 4, DT_GAMEPAD, true },
{ "mouse", cdecode::mouse, cencode::mouse, PT_MOUSE, 1, DT_MOUSE, true },
{ "superscope", cdecode::superscope, cencode::superscope, PT_SUPERSCOPE, 1, DT_SUPERSCOPE, false },
{ "justifier", cdecode::justifier, cencode::justifier, PT_JUSTIFIER, 1, DT_JUSTIFIER, false },
{ "justifiers", cdecode::justifiers, cencode::justifiers, PT_JUSTIFIERS, 2, DT_JUSTIFIER, false }
};

689
controllerdata.hpp Normal file
View file

@ -0,0 +1,689 @@
#ifndef _controllerdata__hpp__included__
#define _controllerdata__hpp__included__
#include "fieldsplit.hpp"
#include <vector>
#include <stdexcept>
/**
* \brief What version to write as control version?
*/
#define WRITE_CONTROL_VERSION 0
/**
* \brief System control: Frame sync flag
*/
#define CONTROL_FRAME_SYNC 0
/**
* \brief System control: System reset button
*/
#define CONTROL_SYSTEM_RESET 1
/**
* \brief High part of cycle count for system reset (multiplier 10000).
*/
#define CONTROL_SYSTEM_RESET_CYCLES_HI 2
/**
* \brief Low part of cycle count for system reset (multiplier 1).
*/
#define CONTROL_SYSTEM_RESET_CYCLES_LO 3
/**
* \brief Number of system controls.
*/
#define MAX_SYSTEM_CONTROLS 4
/**
* \brief SNES has 2 controller ports.
*/
#define MAX_PORTS 2
/**
* \brief Multitap can connect 4 controllers to a single port.
*/
#define MAX_CONTROLLERS_PER_PORT 4
/**
* \brief Ordinary gamepad has 12 buttons/axis total (more than anything else supported).
*/
#define CONTROLLER_CONTROLS 12
/**
* \brief The total number of controls (currently 100).
*/
#define TOTAL_CONTROLS (MAX_SYSTEM_CONTROLS + MAX_PORTS * CONTROLLER_CONTROLS * MAX_CONTROLLERS_PER_PORT)
struct controls_t;
/**
* \brief Subfield information
*
* Information about single subfield in control field. Subfields can be either single buttons or single axes.
*/
struct control_subfield
{
/**
* \brief Index used for subfield in control field
*
* This is the index this subfield is accessed as in operator[] method of class controlfield.
*/
unsigned index;
/**
* \brief Type of subfield
*
* This type gives the type of the subfield, if it is a single button or single axis.
*/
enum control_subfield_type {
/**
* \brief The subfield is a button.
*/
BUTTON,
/**
* \brief The subfield is an axis.
*/
AXIS
};
/**
* \brief Type of subfield
*
* This gives the type of the subfield.
*/
enum control_subfield_type type;
/**
* \brief Make subfield structure
*
* Make subfield structure having specified index and type.
*
* \param _index The index to give to the subfield.
* \param _type The type to to give to the subfield.
*/
control_subfield(unsigned _index, enum control_subfield_type _type) throw();
};
/**
* \brief Field parser
*
* This class handles parsing fields consisting of subfields.
*/
class controlfield
{
public:
/**
* \brief Create new field parser
*
* This constructor creates new field parser parsing field consisting of specified subfields.
*
* \param _subfields The subfields forming the field to parse.
* \throws std::bad_alloc Not enough memory to complete the operation.
*/
controlfield(std::vector<control_subfield> _subfields) throw(std::bad_alloc);
/**
* \brief Update values of subfields
*
* Read in string representation of a field and update values of all subfields.
*
* \param str The string representation
* \throws std::bad_alloc Not enough memory to complete the operation.
*/
void set_field(const std::string& str) throw(std::bad_alloc);
/**
* \brief Read data in specified subfield.
*
* This method takes in a subfield index and gives the current data for said subfield.
*
* \param index The subfield index to read (0-based indexing).
* \throws std::logic_error Trying to access subfield index out of range
* \return The current value for that subfield
* \note If subfield index is in range but not mapped to any subfield, then 0 is returned.
*/
short operator[](unsigned index) throw(std::logic_error);
/**
* \brief Get number of valid subfield indices
*
* Obtain number of valid subfield indices. The indices are [0, N), where N is the return value of this method.
*
* \return The amount of accessable subfield indices.
*/
unsigned indices() throw();
private:
std::vector<control_subfield> subfields;
std::vector<short> values;
};
/**
* \brief System control field parser
*
* Subtype of controlfield (field parser) with fixed subfields of system field.
*/
class controlfield_system : public controlfield
{
public:
/**
* \brief Create new field parser
*
* This creates new parser for system fields.
*
* \param version The version to parse, currently must be 0.
* \throws std::bad_alloc Not enough memory.
* \throws std::runtime_error Unknown version.
*/
controlfield_system(unsigned version) throw(std::bad_alloc, std::runtime_error);
};
/**
* \brief Gamepad control field parser
*
* Subtype of controlfield (field parser) with fixed subfields of gamepad controller (12 buttons).
*/
class controlfield_gamepad : public controlfield
{
public:
/**
* \brief Create new field parser
*
* This creates new parser for gamepad fields.
*
* \throws std::bad_alloc Not enough memory.
*/
controlfield_gamepad() throw(std::bad_alloc);
};
/**
* \brief Mouse/Justifier control field parser
*
* Subtype of controlfield (field parser) with fixed subfields of mouse or justfier controller (2 axes, 2 buttons).
*/
class controlfield_mousejustifier : public controlfield
{
public:
/**
* \brief Create new field parser
*
* This creates new parser for mouse or justifier fields.
*
* \throws std::bad_alloc Not enough memory.
*/
controlfield_mousejustifier() throw(std::bad_alloc);
};
/**
* \brief Superscope control field parser
*
* Subtype of controlfield (field parser) with fixed subfields of superscope controller (2 axes, 4 buttons).
*/
class controlfield_superscope : public controlfield
{
public:
/**
* \brief Create new field parser
*
* This creates new parser for superscope fields.
*
* \throws std::bad_alloc Not enough memory.
*/
controlfield_superscope() throw(std::bad_alloc);
};
/**
* \brief Decoders
*/
class cdecode
{
public:
/**
* \brief Port decoder type
*
* This is type of functions that perform decoding of port fields.
*
* \param port The number of port to decode.
* \param line Field splitter, positioned so the first field of that port is read first.
* \param controls Buffer to place the read controls to.
* \throws std::bad_alloc Not enough memory.
* \throws std::runtime_error Bad input.
* \note This method needs to read all fields associated with the port.
*/
typedef void (*fn_t)(unsigned port, fieldsplitter& line, short* controls);
/**
* \brief System field decoder
*
* This is a decoder for the system field.
*
* \param line The line to decode from. Assumed to be positioned on the system field.
* \param controls The buffer for controls read.
* \param version The version of control structure to read.
* \throws std::bad_alloc Not enough memory.
* \throws std::runtime_error Invalid field or invalid version.
* \note This can't be put into decoder_type as parameters are bit diffrent.
*/
static void system(fieldsplitter& line, short* controls, unsigned version) throw(std::bad_alloc,
std::runtime_error);
/**
* \brief Port decoder for type none.
*
* This is a port decoder for port type none.
*
* \param port The port number
* \param line The line to decode. Assumed to be positioned on start of port data.
* \param controls Buffer to decode the controls to.
* \throws std::bad_alloc Can't happen.
* \throws std::runtime_error Can't happen.
*/
static void none(unsigned port, fieldsplitter& line, short* controls) throw(std::bad_alloc,
std::runtime_error);
/**
* \brief Port decoder for type gamepad.
*
* This is a port decoder for port type gamepad.
*
* \param port The port number
* \param line The line to decode. Assumed to be positioned on start of port data.
* \param controls Buffer to decode the controls to.
* \throws std::bad_alloc Out of memory.
* \throws std::runtime_error Invalid controller data.
*/
static void gamepad(unsigned port, fieldsplitter& line, short* controls) throw(std::bad_alloc,
std::runtime_error);
/**
* \brief Port decoder for type multitap.
*
* This is a port decoder for port type multitap.
*
* \param port The port number
* \param line The line to decode. Assumed to be positioned on start of port data.
* \param controls Buffer to decode the controls to.
* \throws std::bad_alloc Out of memory.
* \throws std::runtime_error Invalid controller data.
*/
static void multitap(unsigned port, fieldsplitter& line, short* controls) throw(std::bad_alloc,
std::runtime_error);
/**
* \brief Port decoder for type mouse.
*
* This is a port decoder for port type mouse.
*
* \param port The port number
* \param line The line to decode. Assumed to be positioned on start of port data.
* \param controls Buffer to decode the controls to.
* \throws std::bad_alloc Out of memory.
* \throws std::runtime_error Invalid controller data.
*/
static void mouse(unsigned port, fieldsplitter& line, short* controls) throw(std::bad_alloc,
std::runtime_error);
/**
* \brief Port decoder for type superscope.
*
* This is a port decoder for port type superscope.
*
* \param port The port number
* \param line The line to decode. Assumed to be positioned on start of port data.
* \param controls Buffer to decode the controls to.
* \throws std::bad_alloc Out of memory.
* \throws std::runtime_error Invalid controller data.
*/
static void superscope(unsigned port, fieldsplitter& line, short* controls) throw(std::bad_alloc,
std::runtime_error);
/**
* \brief Port decoder for type justifier.
*
* This is a port decoder for port type justifier.
*
* \param port The port number
* \param line The line to decode. Assumed to be positioned on start of port data.
* \param controls Buffer to decode the controls to.
* \throws std::bad_alloc Out of memory.
* \throws std::runtime_error Invalid controller data.
*/
static void justifier(unsigned port, fieldsplitter& line, short* controls) throw(std::bad_alloc,
std::runtime_error);
/**
* \brief Port decoder for type justifiers.
*
* This is a port decoder for port type justifiers.
*
* \param port The port number
* \param line The line to decode. Assumed to be positioned on start of port data.
* \param controls Buffer to decode the controls to.
* \throws std::bad_alloc Out of memory.
* \throws std::runtime_error Invalid controller data.
*/
static void justifiers(unsigned port, fieldsplitter& line, short* controls) throw(std::bad_alloc,
std::runtime_error);
};
/**
* \brief Decoders
*/
class cencode
{
public:
/**
* \brief Port encoder type
*
* This is the type of functions that perform encoding of port fields.
*
* \param port To number of port to encode.
* \param controls Buffer to read the controls from.
* \throws std::bad_alloc Not enough memory.
*/
typedef std::string (*fn_t)(unsigned port, const short* controls);
/**
* \brief Encoder for system field
*
* This is encoder for the system field.
*
* \param controls The controls to encode.
* \throws std::bad_alloc Out of memory.
* \note This can't be put into encoder_type as parameters are bit diffrent.
*/
static std::string system(const short* controls) throw(std::bad_alloc);
/**
* \brief Port encoder for type none.
*
* This is a port encoder for port type none.
*
* \param port The port number
* \param controls The controls to encode.
* \return The encoded fields.
* \throws std::bad_alloc Out of memory.
*/
static std::string none(unsigned port, const short* controls) throw(std::bad_alloc);
/**
* \brief Port encoder for type gamepad.
*
* This is a port encoder for port type gamepad.
*
* \param port The port number
* \param controls The controls to encode.
* \return The encoded fields.
* \throws std::bad_alloc Out of memory.
*/
static std::string gamepad(unsigned port, const short* controls) throw(std::bad_alloc);
/**
* \brief Port encoder for type multitap.
*
* This is a port encoder for port type multitap.
*
* \param port The port number
* \param controls The controls to encode.
* \return The encoded fields.
* \throws std::bad_alloc Out of memory.
*/
static std::string multitap(unsigned port, const short* controls) throw(std::bad_alloc);
/**
* \brief Port encoder for type mouse.
*
* This is a port encoder for port type mouse.
*
* \param port The port number
* \param controls The controls to encode.
* \return The encoded fields.
* \throws std::bad_alloc Out of memory.
*/
static std::string mouse(unsigned port, const short* controls) throw(std::bad_alloc);
/**
* \brief Port encoder for type superscope.
*
* This is a port encoder for port type superscope.
*
* \param port The port number
* \param controls The controls to encode.
* \return The encoded fields.
* \throws std::bad_alloc Out of memory.
*/
static std::string superscope(unsigned port, const short* controls) throw(std::bad_alloc);
/**
* \brief Port encoder for type justifier.
*
* This is a port encoder for port type justifier.
*
* \param port The port number
* \param controls The controls to encode.
* \return The encoded fields.
* \throws std::bad_alloc Out of memory.
*/
static std::string justifier(unsigned port, const short* controls) throw(std::bad_alloc);
/**
* \brief Port encoder for type justifiers.
*
* This is a port encoder for port type justifiers.
*
* \param port The port number
* \param controls The controls to encode.
* \return The encoded fields.
* \throws std::bad_alloc Out of memory.
*/
static std::string justifiers(unsigned port, const short* controls) throw(std::bad_alloc);
};
/**
* \brief Controls for single (sub)frame
*
* This structure holds controls for single (sub)frame or instant of time.
*/
struct controls_t
{
/**
* \brief Create new controls structure
*
* Creates new controls structure. All buttons are released and all axes are 0 (neutral).
*
* \param sync If true, write 1 (pressed) to frame sync subfield, else write 0 (released). Default false.
*/
controls_t(bool sync = false) throw();
/**
* \brief Read in a line using specified decoders
*
* This constructor takes in a line of input, port decoders and system field version and decodes the controls.
*
* \param line The line to decode.
* \param decoders The decoders for each port.
* \param version Version for the system field.
* \throws std::bad_alloc Not enough memory.
* \throws std::runtime_error Invalid input line.
*/
controls_t(const std::string& line, const std::vector<cdecode::fn_t>& decoders, unsigned version)
throw(std::bad_alloc, std::runtime_error);
/**
* \brief Encode line
*
* This method takes in port encoders and encodes the controls.
*
* \param encoders The encoders for each port.
* \throws std::bad_alloc Not enough memory.
*/
std::string tostring(const std::vector<cencode::fn_t>& encoders) const throw(std::bad_alloc);
/**
* \brief Read control value by (port, controller, control) tuple.
*
* This method takes in controller (port, controller, control) tuple and returns reference to the value of that
* control.
*
* \param port The port number
* \param controller The controller number within that port.
* \param control The control number within that controller.
* \return Reference to control value.
* \throws std::logic_error port, controller or control is invalid.
*/
const short& operator()(unsigned port, unsigned controller, unsigned control) const throw(std::logic_error);
/**
* \brief Read control value by system index.
*
* This method takes in system index and returns reference to the value of that control.
*
* \param control The system index of control.
* \return Reference to control value.
* \throws std::logic_error Invalid control index.
*/
const short& operator()(unsigned control) const throw(std::logic_error);
/**
* \brief Read control value by (port, controller, control) tuple.
*
* This method takes in controller (port, controller, control) tuple and returns reference to the value of that
* control.
*
* \param port The port number
* \param controller The controller number within that port.
* \param control The control number within that controller.
* \return Reference to control value.
* \throws std::logic_error port, controller or control is invalid.
*/
short& operator()(unsigned port, unsigned controller, unsigned control) throw(std::logic_error);
/**
* \brief Read control value by system index.
*
* This method takes in system index and returns reference to the value of that control.
*
* \param control The system index of control.
* \return Reference to control value.
* \throws std::logic_error Invalid control index.
*/
short& operator()(unsigned control) throw(std::logic_error);
controls_t operator^(controls_t other) throw();
/**
* \brief Raw controller data
*
* This field contains the raw controller data. Avoid manipulating directly.
*/
short controls[TOTAL_CONTROLS];
/**
* \brief Equality
*/
bool operator==(const controls_t& c) const throw();
};
/**
* \brief Type of port
*
* This enumeration gives the type of port.
*/
enum porttype_t
{
/**
* \brief No device
*/
PT_NONE = 0, //Nothing connected to port.
/**
* \brief Gamepad
*/
PT_GAMEPAD = 1,
/**
* \brief Multitap (with 4 gamepads connected)
*/
PT_MULTITAP = 2,
/**
* \brief Mouse
*/
PT_MOUSE = 3,
/**
* \brief Superscope (only allowed for port 2).
*/
PT_SUPERSCOPE = 4,
/**
* \brief Justifier (only allowed for port 2).
*/
PT_JUSTIFIER = 5,
/**
* \brief 2 Justifiers (only allowed for port 2).
*/
PT_JUSTIFIERS = 6,
/**
* \brief Number of controller types.
*/
PT_LAST_CTYPE = 6,
/**
* \brief Invalid controller type.
*/
PT_INVALID = 7
};
/**
* \brief Type of port
*
* This enumeration gives the type of device.
*/
enum devicetype_t
{
/**
* \brief No device
*/
DT_NONE = 0,
/**
* \brief Gamepad
*/
DT_GAMEPAD = 1,
/**
* \brief Mouse
*/
DT_MOUSE = 3,
/**
* \brief Superscope
*/
DT_SUPERSCOPE = 4,
/**
* \brief Justifier.
*/
DT_JUSTIFIER = 5
};
/**
* \brief Information about port type.
*/
struct port_type
{
const char* name;
cdecode::fn_t decoder;
cencode::fn_t encoder;
porttype_t ptype;
unsigned devices;
devicetype_t dtype;
bool valid_port1;
static const port_type& lookup(const std::string& name, bool port2 = true) throw(std::bad_alloc,
std::runtime_error);
};
/**
* \brief Information about port types.
*/
extern port_type port_types[];
/**
* \brief Translate controller index to system index.
*
* This method takes in controller (port, controller, control) tuple and returns the system index corresponding to
* that control.
*
* \param port The port number
* \param controller The controller number within that port.
* \param control The control number within that controller.
* \return System control index.
* \throws std::logic_error port, controller or control is invalid.
*/
unsigned ccindex2(unsigned port, unsigned controller, unsigned control) throw(std::logic_error);
#endif

58
fieldsplit.cpp Normal file
View file

@ -0,0 +1,58 @@
#include "lsnes.hpp"
#include "fieldsplit.hpp"
#include <iostream>
fieldsplitter::fieldsplitter(const std::string& _line) throw(std::bad_alloc)
{
line = _line;
position = 0;
}
fieldsplitter::operator bool() throw()
{
return (position < line.length());
}
fieldsplitter::operator std::string() throw(std::bad_alloc)
{
size_t nextp, oldp = position;
nextp = line.find_first_of("|", position);
if(nextp > line.length()) {
position = line.length();
return line.substr(oldp);
} else {
position = nextp + 1;
return line.substr(oldp, nextp - oldp);
}
}
tokensplitter::tokensplitter(const std::string& _line) throw(std::bad_alloc)
{
line = _line;
position = 0;
}
tokensplitter::operator bool() throw()
{
return (position < line.length());
}
tokensplitter::operator std::string() throw(std::bad_alloc)
{
size_t nextp, oldp = position;
nextp = line.find_first_of(" \t", position);
if(nextp > line.length()) {
position = line.length();
return line.substr(oldp);
} else {
position = nextp;
while(position < line.length() && (line[position] == ' ' || line[position] == '\t'))
position++;
return line.substr(oldp, nextp - oldp);
}
}
std::string tokensplitter::tail() throw(std::bad_alloc)
{
return line.substr(position);
}

63
fieldsplit.hpp Normal file
View file

@ -0,0 +1,63 @@
#ifndef _fieldsplit_hpp__included__
#define _fieldsplit_hpp__included__
#include <string>
#include <stdexcept>
/**
* \brief Class for splitting string to fields.
*
* Splits string to fields on |
*/
class fieldsplitter
{
public:
/**
* \brief Create new string splitter
*
* Creates a new string splitter to split specified string.
* \param _line The line to split.
* \throws std::bad_alloc Out of memory.
*/
fieldsplitter(const std::string& _line) throw(std::bad_alloc);
/**
* \brief More fields coming
*
* Checks if more fields are coming.
*
* \return True if more fields are coming, otherwise false.
*/
operator bool() throw();
/**
* \brief Read next field
*
* Reads the next field and returns it. If field doesn't exist, it reads as empty string.
*
* \return The read field.
* \throws std::bad_alloc
*/
operator std::string() throw(std::bad_alloc);
private:
std::string line;
size_t position;
};
/**
* \brief Class for splitting string to fields.
*
* Splits string to fields on ' ' and '\t', with multiple whitespace collapsed into one.
*/
class tokensplitter
{
public:
tokensplitter(const std::string& _line) throw(std::bad_alloc);
operator bool() throw();
operator std::string() throw(std::bad_alloc);
std::string tail() throw(std::bad_alloc);
private:
std::string line;
size_t position;
};
#endif

1
fonts/80ff.dat Normal file
View file

@ -0,0 +1 @@
€亗儎厗噲墛媽崕彁憭摂晼棙櫄洔潪煚、¥ウЖ┆<EFBFBD><EFBFBD><EFBFBD>辈炒刀犯购患骄坷谅媚牌侨墒颂臀闲岩釉罩棕仝圮蒉哙徕沅彐玷殛腱眍镳耱篝貊鼬<EFBFBD><EFBFBD><EFBFBD><EFBFBD>

12
fonts/ACKNOWLEDGEMENTS Normal file
View file

@ -0,0 +1,12 @@
unifontfull-5.1.20080820.hex is by Roman Czyborra.
http://unifoundry.com/unifont.html
Roman released his font and software as freeware: "All of my works you find here are freeware. You may freely copy, use, quote, modify or redistribute them as long as you properly attribute my contribution and have given a quick thought about whether Roman might perhaps be interested to read what you did with his stuff. Horizontal rules don't apply."
cp437.hex is conversion from CP437.F16 from font comes from ftp://ftp.simtel.net/pub/simtelnet/msdos/screen/fntcol16.zip
* The package is (c) by Joseph Gil
* The individual fonts are public domain.

256
fonts/cp437.hex Normal file
View file

@ -0,0 +1,256 @@
0000:00000000000000000000000000000000
0001:00007e81a58181bd9981817e00000000
0002:00007effdbffffc3e7ffff7e00000000
0003:000000006cfefefefe7c381000000000
0004:0000000010387cfe7c38100000000000
0005:000000183c3ce7e7e718183c00000000
0006:000000183c7effff7e18183c00000000
0007:000000000000183c3c18000000000000
0008:ffffffffffffe7c3c3e7ffffffffffff
0009:00000000003c664242663c0000000000
000a:ffffffffffc399bdbd99c3ffffffffff
000b:00001e0e1a3278cccccccc7800000000
000c:00003c666666663c187e181800000000
000d:00003f333f3030303070f0e000000000
000e:00007f637f6363636367e7e6c0000000
000f:0000001818db3ce73cdb181800000000
0010:0080c0e0f0f8fef8f0e0c08000000000
0011:0002060e1e3efe3e1e0e060200000000
0012:0000183c7e1818187e3c180000000000
0013:00006666666666666600666600000000
0014:00007fdbdbdb7b1b1b1b1b1b00000000
0015:007cc660386cc6c66c380cc67c000000
0016:0000000000000000fefefefe00000000
0017:0000183c7e1818187e3c187e00000000
0018:0000183c7e1818181818181800000000
0019:0000181818181818187e3c1800000000
001a:0000000000180cfe0c18000000000000
001b:00000000003060fe6030000000000000
001c:000000000000c0c0c0fe000000000000
001d:0000000000286cfe6c28000000000000
001e:000000001038387c7cfefe0000000000
001f:00000000fefe7c7c3838100000000000
0020:00000000000000000000000000000000
0021:0000183c3c3c18181800181800000000
0022:00666666240000000000000000000000
0023:0000006c6cfe6c6c6cfe6c6c00000000
0024:18187cc6c2c07c060686c67c18180000
0025:00000000c2c60c183060c68600000000
0026:0000386c6c3876dccccccc7600000000
0027:00303030600000000000000000000000
0028:00000c18303030303030180c00000000
0029:000030180c0c0c0c0c0c183000000000
002a:0000000000663cff3c66000000000000
002b:000000000018187e1818000000000000
002c:00000000000000000018181830000000
002d:00000000000000fe0000000000000000
002e:00000000000000000000181800000000
002f:0000000002060c183060c08000000000
0030:0000386cc6c6d6d6c6c66c3800000000
0031:00001838781818181818187e00000000
0032:00007cc6060c183060c0c6fe00000000
0033:00007cc606063c060606c67c00000000
0034:00000c1c3c6cccfe0c0c0c1e00000000
0035:0000fec0c0c0fc060606c67c00000000
0036:00003860c0c0fcc6c6c6c67c00000000
0037:0000fec606060c183030303000000000
0038:00007cc6c6c67cc6c6c6c67c00000000
0039:00007cc6c6c67e0606060c7800000000
003a:00000000181800000018180000000000
003b:00000000181800000018183000000000
003c:000000060c18306030180c0600000000
003d:00000000007e00007e00000000000000
003e:0000006030180c060c18306000000000
003f:00007cc6c60c18181800181800000000
0040:0000007cc6c6dedededcc07c00000000
0041:000010386cc6c6fec6c6c6c600000000
0042:0000fc6666667c66666666fc00000000
0043:00003c66c2c0c0c0c0c2663c00000000
0044:0000f86c6666666666666cf800000000
0045:0000fe6662687868606266fe00000000
0046:0000fe6662687868606060f000000000
0047:00003c66c2c0c0dec6c6663a00000000
0048:0000c6c6c6c6fec6c6c6c6c600000000
0049:00003c18181818181818183c00000000
004a:00001e0c0c0c0c0ccccccc7800000000
004b:0000e666666c78786c6666e600000000
004c:0000f06060606060606266fe00000000
004d:0000c6eefefed6c6c6c6c6c600000000
004e:0000c6e6f6fedecec6c6c6c600000000
004f:00007cc6c6c6c6c6c6c6c67c00000000
0050:0000fc6666667c60606060f000000000
0051:00007cc6c6c6c6c6c6d6de7c0c0e0000
0052:0000fc6666667c6c666666e600000000
0053:00007cc6c660380c06c6c67c00000000
0054:00007e7e5a1818181818183c00000000
0055:0000c6c6c6c6c6c6c6c6c67c00000000
0056:0000c6c6c6c6c6c6c66c381000000000
0057:0000c6c6c6c6d6d6d6feee6c00000000
0058:0000c6c66c7c38387c6cc6c600000000
0059:0000666666663c181818183c00000000
005a:0000fec6860c183060c2c6fe00000000
005b:00003c30303030303030303c00000000
005c:00000080c0e070381c0e060200000000
005d:00003c0c0c0c0c0c0c0c0c3c00000000
005e:10386cc6000000000000000000000000
005f:00000000000000000000000000ff0000
0060:0030180c000000000000000000000000
0061:0000000000780c7ccccccc7600000000
0062:0000e06060786c666666667c00000000
0063:00000000007cc6c0c0c0c67c00000000
0064:00001c0c0c3c6ccccccccc7600000000
0065:00000000007cc6fec0c0c67c00000000
0066:00001c36323078303030307800000000
0067:000000000076cccccccccc7c0ccc7800
0068:0000e060606c7666666666e600000000
0069:00001818003818181818183c00000000
006a:00000606000e06060606060666663c00
006b:0000e06060666c78786c66e600000000
006c:00003818181818181818183c00000000
006d:0000000000ecfed6d6d6d6c600000000
006e:0000000000dc66666666666600000000
006f:00000000007cc6c6c6c6c67c00000000
0070:0000000000dc66666666667c6060f000
0071:000000000076cccccccccc7c0c0c1e00
0072:0000000000dc7666606060f000000000
0073:00000000007cc660380cc67c00000000
0074:0000103030fc30303030361c00000000
0075:0000000000cccccccccccc7600000000
0076:0000000000c6c6c6c6c66c3800000000
0077:0000000000c6c6d6d6d6fe6c00000000
0078:0000000000c66c3838386cc600000000
0079:0000000000c6c6c6c6c6c67e060cf800
007a:0000000000fecc183060c6fe00000000
007b:00000e18181870181818180e00000000
007c:00001818181818181818181800000000
007d:0000701818180e181818187000000000
007e:0076dc00000000000000000000000000
007f:0000000010386cc6c6c6fe0000000000
00c7:00003c66c2c0c0c0c0c2663c18700000
00fc:0000cc0000cccccccccccc7600000000
00e9:000c1830007cc6fec0c0c67c00000000
00e2:0010386c00780c7ccccccc7600000000
00e4:0000cc0000780c7ccccccc7600000000
00e0:0060301800780c7ccccccc7600000000
00e5:00386c3800780c7ccccccc7600000000
00e7:00000000007cc6c0c0c0c67c18700000
00ea:0010386c007cc6fec0c0c67c00000000
00eb:0000c600007cc6fec0c0c67c00000000
00e8:00603018007cc6fec0c0c67c00000000
00ef:00006600003818181818183c00000000
00ee:00183c66003818181818183c00000000
00ec:00603018003818181818183c00000000
00c4:00c60010386cc6c6fec6c6c600000000
00c5:386c3810386cc6fec6c6c6c600000000
00c9:0c1800fe66626878686266fe00000000
00e6:0000000000ec36367ed8d86e00000000
00c6:00003e6cccccfeccccccccce00000000
00f4:0010386c007cc6c6c6c6c67c00000000
00f6:0000c600007cc6c6c6c6c67c00000000
00f2:00603018007cc6c6c6c6c67c00000000
00fb:003078cc00cccccccccccc7600000000
00f9:0060301800cccccccccccc7600000000
00ff:0000c60000c6c6c6c6c6c67e060c7800
00d6:00c6007cc6c6c6c6c6c6c67c00000000
00dc:00c600c6c6c6c6c6c6c6c67c00000000
00a2:0018187cc6c0c0c0c67c181800000000
00a3:00386c6460f060606060e6fc00000000
00a5:000066663c187e187e18181800000000
20a7:00f8ccccf8c4ccdeccccccc600000000
0192:000e1b1818187e181818d87000000000
00e1:0018306000780c7ccccccc7600000000
00ed:000c1830003818181818183c00000000
00f3:00183060007cc6c6c6c6c67c00000000
00fa:0018306000cccccccccccc7600000000
00f1:000076dc00dc66666666666600000000
00d1:76dc00c6e6f6fedecec6c6c600000000
00aa:00003c6c6c3e007e0000000000000000
00ba:0000386c6c38007c0000000000000000
00bf:0000303000303060c0c6c67c00000000
2310:000000000000fec0c0c0c00000000000
00ac:000000000000fe060606060000000000
00bd:0060e062666c183060dc860c183e0000
00bc:0060e062666c183066ce9a3f06060000
00a1:00001818001818183c3c3c1800000000
00ab:0000000000366cd86c36000000000000
00bb:0000000000d86c366cd8000000000000
2591:11441144114411441144114411441144
2592:55aa55aa55aa55aa55aa55aa55aa55aa
2593:dd77dd77dd77dd77dd77dd77dd77dd77
2502:18181818181818181818181818181818
2524:18181818181818f81818181818181818
2561:1818181818f818f81818181818181818
2562:36363636363636f63636363636363636
2556:00000000000000fe3636363636363636
2555:0000000000f818f81818181818181818
2563:3636363636f606f63636363636363636
2551:36363636363636363636363636363636
2557:0000000000fe06f63636363636363636
255d:3636363636f606fe0000000000000000
255c:36363636363636fe0000000000000000
255b:1818181818f818f80000000000000000
2510:00000000000000f81818181818181818
2514:181818181818181f0000000000000000
2534:18181818181818ff0000000000000000
252c:00000000000000ff1818181818181818
251c:181818181818181f1818181818181818
2500:00000000000000ff0000000000000000
253c:18181818181818ff1818181818181818
255e:18181818181f181f1818181818181818
255f:36363636363636373636363636363636
255a:363636363637303f0000000000000000
2554:00000000003f30373636363636363636
2569:3636363636f700ff0000000000000000
2566:0000000000ff00f73636363636363636
2560:36363636363730373636363636363636
2550:0000000000ff00ff0000000000000000
256c:3636363636f700f73636363636363636
2567:1818181818ff00ff0000000000000000
2568:36363636363636ff0000000000000000
2564:0000000000ff00ff1818181818181818
2565:00000000000000ff3636363636363636
2559:363636363636363f0000000000000000
2558:18181818181f181f0000000000000000
2552:00000000001f181f1818181818181818
2553:000000000000003f3636363636363636
256b:36363636363636ff3636363636363636
256a:1818181818ff18ff1818181818181818
2518:18181818181818f80000000000000000
250c:000000000000001f1818181818181818
2588:ffffffffffffffffffffffffffffffff
2584:00000000000000ffffffffffffffffff
258c:f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0
2590:0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f
2580:ffffffffffffff000000000000000000
03b1:000000000076dcd8d8d8dc7600000000
00df:000078ccccccd8ccc6c6c6cc00000000
0393:0000fec6c6c0c0c0c0c0c0c000000000
03c0:0000000000fe6c6c6c6c6c6c00000000
03a3:0000fec6603018183060c6fe00000000
03c3:00000000007ed8d8d8d8d87000000000
00b5:00000000006666666666667c6060c000
03c4:0000000076dc18181818181800000000
03a6:00007e183c666666663c187e00000000
0398:0000386cc6c6fec6c6c66c3800000000
03a9:0000386cc6c6c66c6c6c6cee00000000
03b4:00001e30180c3e666666663c00000000
221e:00000000007edbdbdb7e000000000000
03c6:00000003067edbdbf37e60c000000000
03b5:00001c3060607c606060301c00000000
2229:0000007cc6c6c6c6c6c6c6c600000000
2261:00000000fe0000fe0000fe0000000000
00b1:0000000018187e181800007e00000000
2265:00000030180c060c1830007e00000000
2264:0000000c18306030180c007e00000000
2320:00000e1b1b1818181818181818181818
2321:181818181818181818d8d8d870000000
00f7:000000000018007e0018000000000000
2248:000000000076dc0076dc000000000000
00b0:00386c6c380000000000000000000000
2219:00000000000000181800000000000000
00b7:00000000000000180000000000000000
221a:000f0c0c0c0c0cec6c6c3c1c00000000
207f:006c3636363636000000000000000000
00b2:003c660c18327e000000000000000000
25a0:000000007e7e7e7e7e7e7e0000000000
00a0:00000000000000000000000000000000

5
fonts/gen80ff.lua Normal file
View file

@ -0,0 +1,5 @@
s = "";
for i = 128,255 do
s = s .. string.char(i);
end
io.stdout:write(s);

259
fonts/parsehexfont.cpp Normal file
View file

@ -0,0 +1,259 @@
#include <map>
#include <string>
#include <iostream>
#include <cstdint>
#include <cmath>
#include <vector>
#include <cstdlib>
#include <ctime>
#include <iomanip>
std::map<uint32_t, uint32_t> glyph_index;
std::map<std::string, uint32_t> glyph_offsets;
std::map<uint32_t, std::string> glyphs;
uint32_t next_offset = 1;
void add_glyph(uint32_t codepoint, const std::string& appearence)
{
if(((codepoint >> 16) > 10) || ((codepoint & 0x1FF800) == 0xD800)) {
std::cerr << "Illegal codepoint " << std::hex << codepoint << "." << std::endl;
exit(1);
}
if(!glyph_offsets.count(appearence)) {
if(appearence.find_first_not_of("0123456789ABCDEFabcdef") < appearence.length()) {
std::cerr << "Invalid font representation (invalid hex char)."
<< std::endl;
std::cerr << "Faulty representation: '" << appearence << "'." << std::endl;
exit(1);
}
glyph_offsets[appearence] = next_offset;
glyph_index[next_offset] = codepoint;
if(appearence.length() == 32)
next_offset += 5;
else if(appearence.length() == 64)
next_offset += 9;
else {
std::cerr << "Invalid font representation (length " << appearence.length() << ")."
<< std::endl;
std::cerr << "Faulty representation: '" << appearence << "'." << std::endl;
exit(1);
}
}
glyphs[codepoint] = appearence;
}
uint32_t bucket_size(uint32_t items)
{
if(items <= 4)
return items;
return static_cast<uint32_t>(pow(items, 1.5));
}
//This is Jenkin's MIX function.
uint32_t keyhash(uint32_t key, uint32_t item, uint32_t mod)
{
uint32_t a = key;
uint32_t b = 0;
uint32_t c = item;
a=a-b; a=a-c; a=a^(c >> 13);
b=b-c; b=b-a; b=b^(a << 8);
c=c-a; c=c-b; c=c^(b >> 13);
a=a-b; a=a-c; a=a^(c >> 12);
b=b-c; b=b-a; b=b^(a << 16);
c=c-a; c=c-b; c=c^(b >> 5);
a=a-b; a=a-c; a=a^(c >> 3);
b=b-c; b=b-a; b=b^(a << 10);
c=c-a; c=c-b; c=c^(b >> 15);
return c % mod;
}
uint32_t wrong_key(uint32_t key, uint32_t hash, uint32_t mod)
{
uint32_t i = 0;
if(mod <= 1)
return 0;
while(keyhash(key, i, mod) == hash)
i++;
return i;
}
std::pair<uint32_t, uint32_t> make_subdirectory(std::vector<uint32_t>& items)
{
if(items.size() < 2)
return std::make_pair(0, items.size());
std::vector<uint32_t> memory;
memory.resize(bucket_size(items.size()));
uint32_t seed = 1;
unsigned tries = 0;
while(true) {
//Safety hatch: If unsuccessful too many times, increase the hash size.
if(tries == 100) {
memory.resize(memory.size() + 1);
tries = 0;
}
bool success = true;
seed = rand();
for(uint32_t i = 0; i < memory.size(); i++)
memory[i] = 0xFFFFFFFFUL;
for(uint32_t i = 0; i < items.size(); i++) {
uint32_t j = keyhash(seed, items[i], memory.size());
if(memory[j] != 0xFFFFFFFFUL) {
success = false;
break;
}
memory[j] = items[i];
}
if(success)
break;
tries++;
}
return std::make_pair(seed, memory.size());
}
void write_subdirectory(std::vector<uint32_t>& items, std::pair<uint32_t, uint32_t> p,
uint32_t badkey)
{
if(!p.second)
return;
std::vector<uint32_t> memory;
std::cout << "," << p.first << "UL," << p.second << "UL";
memory.resize(p.second);
for(uint32_t i = 0; i < memory.size(); i++)
memory[i] = badkey;
for(uint32_t i = 0; i < items.size(); i++)
memory[keyhash(p.first, items[i], memory.size())] = items[i];
for(uint32_t i = 0; i < memory.size(); i++) {
if(memory[i] != badkey)
std::cout << "," << memory[i] << "UL," << glyph_offsets[glyphs[memory[i]]] << "UL";
else
std::cout << "," << badkey << "UL,0UL";
}
std::cout << std::endl;
}
uint32_t glyph_part_string(const std::string& str)
{
return strtoul(str.c_str(), NULL, 16);
}
void write_glyphs()
{
for(std::map<uint32_t, uint32_t>::iterator i = glyph_index.begin(); i != glyph_index.end(); ++i) {
std::string glyph = glyphs[i->second];
if(glyph.length() == 32) {
std::cout << ",0";
for(unsigned i = 0; i < 32; i += 8)
std::cout << "," << glyph_part_string(glyph.substr(i, 8)) << "UL";
} else if(glyph.length() == 64) {
std::cout << ",1";
for(unsigned i = 0; i < 64; i += 8)
std::cout << "," << glyph_part_string(glyph.substr(i, 8)) << "UL";
} else {
std::cerr << "INTERNAL ERROR: Invalid glyph data" << std::endl;
exit(1);
}
}
std::cout << std::endl;
}
void write_main_directory(uint32_t seed, std::vector<std::vector<uint32_t> >& main_directory,
std::vector<std::pair<uint32_t, uint32_t> >& subdirectories)
{
std::cout << "," << seed << "UL";
std::cout << "," << main_directory.size() << "UL";
uint32_t subdir_offset = next_offset + 4 + main_directory.size();
for(size_t i = 0; i < main_directory.size(); i++) {
if(subdirectories[i].second) {
std::cout << "," << subdir_offset << "UL";
subdir_offset = subdir_offset + 2 + 2 * subdirectories[i].second;
} else
std::cout << "," << next_offset << "UL";
}
std::cout << std::endl;
}
int main()
{
std::string line;
srand(time(NULL));
while(std::getline(std::cin, line)) {
if(line[line.length() - 1] == '\r')
line = line.substr(0, line.length() - 1);
size_t split = line.find_first_of("#:");
if(split > line.length() || line[split] == '#')
continue;
std::string codepoint = line.substr(0, split);
std::string appearence = line.substr(split + 1);
uint32_t cp = 0;
char* end;
cp = strtoul(codepoint.c_str(), &end, 16);
if(*end) {
std::cerr << "Invalid codepoint number '" << codepoint << "." << std::endl;
exit(1);
}
add_glyph(cp, appearence);
}
std::cerr << "Loaded " << glyphs.size() << " glyphs (" << glyph_offsets.size() << " distinct)." << std::endl;
if(!glyphs.size()) {
std::cerr << "Need at least 1 glyph." << std::endl;
return 1;
}
uint32_t best_seed = 0;
uint32_t best_score = 0xFFFFFFFFUL;
uint32_t main_directory_size = glyphs.size();
for(unsigned i = 0; i < 1000; i++) {
if(i % 10 == 0)
std::cerr << "." << std::flush;
uint32_t seed = rand();
std::vector<uint32_t> bucket_items;
bucket_items.resize(main_directory_size);
for(uint32_t i = 0; i < main_directory_size; i++)
bucket_items[i] = 0;
for(std::map<uint32_t, std::string>::iterator i = glyphs.begin(); i != glyphs.end(); ++i)
bucket_items[keyhash(seed, i->first, main_directory_size)]++;
uint32_t score = 0;
for(uint32_t i = 0; i < main_directory_size; i++)
if(bucket_items[i])
score = score + 2 * bucket_size(bucket_items[i]) + 3;
else
score = score + 1;
if(score < best_score) {
best_seed = seed;
best_score = score;
}
}
std::cerr << std::endl;
std::cerr << "Best estimated score: " << best_score << " for seed " << best_seed << "." << std::endl;
std::vector<std::vector<uint32_t> > main_directory;
std::vector<std::pair<uint32_t, uint32_t> > subdirectories;
main_directory.resize(main_directory_size);
subdirectories.resize(main_directory_size);
for(std::map<uint32_t, std::string>::iterator i = glyphs.begin(); i != glyphs.end(); ++i)
main_directory[keyhash(best_seed, i->first, main_directory_size)].push_back(i->first);
uint32_t update_interval = main_directory_size / 10;
unsigned real_score = 0;
for(size_t i = 0; i < main_directory.size(); i++) {
if((i % update_interval) == 0)
std::cerr << "." << std::flush;
subdirectories[i] = make_subdirectory(main_directory[i]);
if(subdirectories[i].second)
real_score = real_score + 2 * subdirectories[i].second + 3;
else
real_score = real_score + 1;
}
std::cerr << std::endl;
std::cerr << "Final score: " << real_score << " (" << next_offset + real_score + 4 << " elements)."
<< std::endl;
std::cout << "#include <cstdint>" << std::endl;
std::cout << "uint32_t fontdata_size = " << next_offset + real_score + 4 << ";" << std::endl;
std::cout << "uint32_t fontdata[] = {";
std::cout << next_offset + 2 << "UL";
write_glyphs();
std::cout << ",0,0" << std::endl;
write_main_directory(best_seed, main_directory, subdirectories);
for(size_t i = 0; i < main_directory.size(); i++)
write_subdirectory(main_directory[i], subdirectories[i], wrong_key(best_seed, i, main_directory_size));
std::cout << "};" << std::endl;
return 0;
}

File diff suppressed because it is too large Load diff

116
fonts/verifyhexfont.cpp Normal file
View file

@ -0,0 +1,116 @@
#include <cstdint>
#include <cstdlib>
#include <iostream>
#include <set>
extern uint32_t fontdata_size;
extern uint32_t fontdata[];
uint32_t mdir_use = 0;
uint32_t mdir_nuse = 0;
uint32_t sdir_gkey = 0;
uint32_t sdir_bkey = 0;
uint32_t ftype0 = 0;
uint32_t ftype1 = 0;
std::set<uint32_t> seen;
//This is Jenkin's MIX function.
uint32_t keyhash(uint32_t key, uint32_t item, uint32_t mod)
{
uint32_t a = key;
uint32_t b = 0;
uint32_t c = item;
a=a-b; a=a-c; a=a^(c >> 13);
b=b-c; b=b-a; b=b^(a << 8);
c=c-a; c=c-b; c=c^(b >> 13);
a=a-b; a=a-c; a=a^(c >> 12);
b=b-c; b=b-a; b=b^(a << 16);
c=c-a; c=c-b; c=c^(b >> 5);
a=a-b; a=a-c; a=a^(c >> 3);
b=b-c; b=b-a; b=b^(a << 10);
c=c-a; c=c-b; c=c^(b >> 15);
return c % mod;
}
void verify_font(uint32_t cp, uint32_t offset)
{
if(offset >= fontdata_size) {
std::cerr << "Font verify error: Bad offset for codepoint " << cp << "." << std::endl;
}
if(seen.count(offset))
return;
if(fontdata[offset] > 1) {
std::cerr << "Font verify error: Bad glyph type for codepoint " << cp << "." << std::endl;
exit(1);
}
if(fontdata[offset] == 0)
ftype0++;
else if(fontdata[offset] == 1)
ftype1++;
seen.insert(offset);
}
void verify_subdirectory(uint32_t offset, uint32_t mseed, uint32_t msize, uint32_t snum)
{
if(offset + 2 > fontdata_size) {
std::cerr << "Font verify error: Subdirectory offset out of range: " << offset << "." << std::endl;
exit(1);
}
uint32_t sseed = fontdata[offset++];
uint32_t ssize = fontdata[offset++];
if(offset + 2 * ssize > fontdata_size) {
std::cerr << "Font verify error: Subdirectory offset out of range: " << offset - 2 << "." << std::endl;
exit(1);
}
if(ssize)
mdir_use++;
else
mdir_nuse++;
for(uint32_t i = 0; i < ssize; i++) {
uint32_t cp = fontdata[offset++];
uint32_t foffset = fontdata[offset++];
if(keyhash(mseed, cp, msize) == snum && keyhash(sseed, cp, ssize) == i) {
verify_font(cp, foffset);
sdir_gkey++;
} else if(foffset != 0) {
std::cerr << "Font verify error: Bad codepoint with nonzero offset: " << i << " of "
<< snum << "." << std::endl;
exit(1);
} else
sdir_bkey++;
}
}
void verify_main_directory(uint32_t offset)
{
if(offset + 2 > fontdata_size) {
std::cerr << "Font verify error: Main directory offset out of range: " << offset << "." << std::endl;
exit(1);
}
uint32_t mseed = fontdata[offset++];
uint32_t msize = fontdata[offset++];
if(offset + msize > fontdata_size) {
std::cerr << "Font verify error: Main directory offset out of range: " << offset - 2 << "."
<< std::endl;
exit(1);
}
for(uint32_t i = 0; i < msize; i++)
verify_subdirectory(fontdata[offset++], mseed, msize, i);
}
int main()
{
if(fontdata_size == 0) {
std::cerr << "Font verify error: Empty fontdata." << std::endl;
exit(1);
}
verify_main_directory(fontdata[0]);
std::cerr << "Font verify successful!" << std::endl;
std::cerr << "Main directory entries: " << (mdir_use + mdir_nuse) << " (" << mdir_use << " used + "
<< mdir_nuse << " not_used)." << std::endl;
std::cerr << "Subdirectory entries: " << (sdir_gkey + sdir_bkey) << " (" << sdir_gkey << " good + "
<< sdir_bkey << " bad)." << std::endl;
std::cerr << "Distinct glyphs: " << (ftype0 + ftype1) << " (" << ftype0 << " narrow + "
<< ftype1 << " wide)." << std::endl;
return 0;
}

54
fonts/writecp437font.cpp Normal file
View file

@ -0,0 +1,54 @@
#include <cstdint>
#include <iostream>
#include <iomanip>
#include <string>
uint8_t data[4096];
uint16_t hidxtab[128] = {
0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, 0x00e7,
0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5,
0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9,
0x00ff, 0x00d6, 0x00dc, 0x00a2, 0x00a3, 0x00a5, 0x20a7, 0x0192,
0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x00aa, 0x00ba,
0x00bf, 0x2310, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb,
0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510,
0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f,
0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567,
0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b,
0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580,
0x03b1, 0x00df, 0x0393, 0x03c0, 0x03a3, 0x03c3, 0x00b5, 0x03c4,
0x03a6, 0x0398, 0x03a9, 0x03b4, 0x221e, 0x03c6, 0x03b5, 0x2229,
0x2261, 0x00b1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248,
0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0, 0x00a0
};
uint16_t idx(uint8_t ch)
{
if(ch < 128)
return ch;
else
return hidxtab[ch - 128];
}
void write_glyph(uint16_t cp, uint8_t* d)
{
std::cout << std::hex << std::setw(4) << std::setfill('0') << cp << ":";
for(unsigned i = 0; i < 16; i++)
std::cout << std::hex << std::setw(2) << std::setfill('0')
<< static_cast<int>(static_cast<unsigned char>(d[i]));
std::cout << std::endl;
}
void write_glyphs()
{
for(unsigned i = 0; i < 256; i++)
write_glyph(idx(i), data + 16 * i);
}
int main()
{
std::cin.read(reinterpret_cast<char*>(data), 4096);
write_glyphs();
}

110
framerate.cpp Normal file
View file

@ -0,0 +1,110 @@
#include "framerate.hpp"
#include "settings.hpp"
#include <string>
#include <sstream>
#include <iostream>
#include <stdexcept>
namespace
{
double nominal_rate = 60;
double fps_value = 0;
const double exp_factor = 0.97;
uint64_t last_frame_msec = 0;
bool last_frame_msec_valid = false;
bool target_nominal = true;
double target_fps = 60;
bool target_infinite = false;
uint64_t wait_duration = 0;
struct setting_targetfps : public setting
{
setting_targetfps() throw(std::bad_alloc)
: setting("targetfps")
{
}
void blank() throw(std::bad_alloc, std::runtime_error)
{
target_nominal = true;
target_infinite = false;
target_fps = nominal_rate;
}
bool is_set() throw()
{
return !target_nominal;
}
virtual void set(const std::string& value) throw(std::bad_alloc, std::runtime_error)
{
double tmp;
const char* s;
char* e;
if(value == "infinite") {
target_infinite = true;
target_nominal = false;
return;
}
s = value.c_str();
tmp = strtod(s, &e);
if(*e)
throw std::runtime_error("Invalid frame rate");
if(tmp < 0.001)
throw std::runtime_error("Target frame rate must be at least 0.001fps");
target_fps = tmp;
target_infinite = false;
target_nominal = false;
}
virtual std::string get() throw(std::bad_alloc)
{
if(target_nominal)
return "";
else {
std::ostringstream o;
o << target_fps;
return o.str();
}
}
} targetfps;
}
void set_nominal_framerate(double fps) throw()
{
nominal_rate = fps;
if(target_nominal) {
target_fps = nominal_rate;
target_infinite = false;
}
}
double get_framerate() throw()
{
return fps_value;
}
void ack_frame_tick(uint64_t msec) throw()
{
if(!last_frame_msec_valid) {
last_frame_msec = msec;
last_frame_msec_valid = true;
return;
}
uint64_t frame_msec = msec - last_frame_msec;
fps_value = exp_factor * fps_value + (1 - exp_factor) * (1000.0 / frame_msec);
last_frame_msec = msec;
}
uint64_t to_wait_frame(uint64_t msec) throw()
{
//Very simple algorithm. TODO: Make better one.
if(!last_frame_msec_valid || target_infinite)
return 0;
if(get_framerate() < target_fps && wait_duration > 0)
wait_duration--;
else if(get_framerate() > target_fps)
wait_duration++;
return wait_duration;
}

51
framerate.hpp Normal file
View file

@ -0,0 +1,51 @@
#ifndef _framerate__hpp__included__
#define _framerate__hpp__included__
#include <cstdint>
/**
* \brief Nominal framerate of NTSC SNES.
*/
#define FRAMERATE_SNES_NTSC (10738636.0/178683.0)
/**
* \brief Nominal framerate of PAL SNES.
*/
#define FRAMERATE_SNES_PAL (322445.0/6448.0)
/**
* \brief Set the nominal framerate
*
* Sets the nominal frame rate. Framerate limiting tries to maintain the nominal framerate when there is no other
* explict framerate to maintain.
*/
void set_nominal_framerate(double fps) throw();
/**
* \brief Get the current realized framerate.
*
* Returns the current realized framerate.
*
* \return The framerate the system is currently archiving.
*/
double get_framerate() throw();
/**
* \brief ACK frame start.
*
* Acknowledge frame start for timing purposes.
*
* \param msec Current time.
*/
void ack_frame_tick(uint64_t msec) throw();
/**
* \brief Obtain how long to wait for next frame.
*
* Computes the number of milliseconds to wait for next frame.
*
* \param msec Current time.
* \return Number of more milliseconds to wait.
*/
uint64_t to_wait_frame(uint64_t msec) throw();
#endif

305
keymapper.cpp Normal file
View file

@ -0,0 +1,305 @@
#include "keymapper.hpp"
#include <stdexcept>
#include "lua.hpp"
#include <list>
#include <map>
#include <set>
#include "misc.hpp"
#include "zip.hpp"
#include "videodumper2.hpp"
#include "settings.hpp"
#include "memorymanip.hpp"
#include "fieldsplit.hpp"
namespace
{
std::map<std::string, std::list<std::string>> aliases;
void handle_alias(std::string cmd, window* win)
{
std::string syntax = "Syntax: alias-command <alias> <command>";
tokensplitter t(cmd);
std::string dummy = t;
std::string aliasname = t;
std::string command = t.tail();
if(command == "")
throw std::runtime_error(syntax);
aliases[aliasname].push_back(command);
out(win) << "Command '" << aliasname << "' aliased to '" << command << "'" << std::endl;
}
void handle_unalias(std::string cmd, window* win) throw(std::bad_alloc, std::runtime_error)
{
std::string syntax = "Syntax: unalias-command <alias>";
tokensplitter t(cmd);
std::string dummy = t;
std::string aliasname = t;
if(aliasname == "" || t.tail() != "")
throw std::runtime_error(syntax);
aliases.erase(aliasname);
out(win) << "Removed alias '" << aliasname << "'" << std::endl;
}
void handle_aliases(window* win) throw(std::bad_alloc, std::runtime_error)
{
for(auto i = aliases.begin(); i != aliases.end(); i++)
for(auto j = i->second.begin(); j != i->second.end(); j++)
out(win) << "alias " << i->first << " " << *j << std::endl;
}
void handle_run(std::string cmd, window* win, aliasexpand_commandhandler& cmdh,
std::set<std::string>& recursive_commands) throw(std::bad_alloc, std::runtime_error)
{
std::string syntax = "Syntax: run-script <file>";
tokensplitter t(cmd);
std::string dummy = t;
std::string file = t.tail();
if(file == "")
throw std::runtime_error(syntax);
std::istream* o = NULL;
try {
o = &open_file_relative(file, "");
out(win) << "Running '" << file << "'" << std::endl;
std::string line;
while(std::getline(*o, line))
cmdh.docommand(line, win, recursive_commands);
delete o;
} catch(std::bad_alloc& e) {
OOM_panic(win);
} catch(std::exception& e) {
out(win) << "Error running script: " << e.what() << std::endl;
if(o)
delete o;
}
}
void handle_set(std::string cmd, window* win) throw(std::bad_alloc, std::runtime_error)
{
std::string syntax = "Syntax: set-setting <setting> [<value>]";
tokensplitter t(cmd);
std::string dummy = t;
std::string settingname = t;
std::string settingvalue = t.tail();
if(settingname == "")
throw std::runtime_error(syntax);
setting_set(settingname, settingvalue);
out(win) << "Setting '" << settingname << "' set to '" << settingvalue << "'" << std::endl;
}
void handle_unset(std::string cmd, window* win) throw(std::bad_alloc, std::runtime_error)
{
std::string syntax = "Syntax: unset-setting <setting>";
tokensplitter t(cmd);
std::string dummy = t;
std::string settingname = t;
if(settingname == "" || t.tail() != "")
throw std::runtime_error(syntax);
setting_blank(settingname);
out(win) << "Setting '" << settingname << "' blanked" << std::endl;
}
void handle_get(std::string cmd, window* win) throw(std::bad_alloc, std::runtime_error)
{
std::string syntax = "Syntax: get-setting <setting>";
tokensplitter t(cmd);
std::string dummy = t;
std::string settingname = t;
if(settingname == "" || t.tail() != "")
throw std::runtime_error(syntax);
if(!setting_isblank(settingname))
out(win) << "Setting '" << settingname << "' has value '" << setting_get(settingname)
<< "'" << std::endl;
else
out(win) << "Setting '" << settingname << "' unset" << std::endl;
}
struct binding_request
{
binding_request(std::string& cmd) throw(std::bad_alloc, std::runtime_error);
bool polarity;
std::string mod;
std::string modmask;
std::string keyname;
std::string command;
};
struct set_raii
{
set_raii(std::set<std::string>& _set, const std::string& _foo) throw(std::bad_alloc)
: set(_set), foo(_foo)
{
set.insert(foo);
}
~set_raii() throw()
{
set.erase(foo);
}
std::set<std::string>& set;
std::string foo;
};
}
aliasexpand_commandhandler::aliasexpand_commandhandler() throw()
{
}
aliasexpand_commandhandler::~aliasexpand_commandhandler() throw()
{
}
void aliasexpand_commandhandler::docommand(std::string& cmd, window* win) throw(std::bad_alloc)
{
std::set<std::string> recursive_commands;
docommand(cmd, win, recursive_commands);
}
void aliasexpand_commandhandler::docommand(std::string& cmd, window* win, std::set<std::string>& recursive_commands)
throw(std::bad_alloc)
{
std::string tmp = cmd;
bool noalias = false;
if(cmd.length() > 0 && cmd[0] == '*') {
tmp = cmd.substr(1);
noalias = true;
}
if(recursive_commands.count(tmp)) {
out(win) << "Not executing '" << tmp << "' recursively" << std::endl;
return;
}
set_raii rce(recursive_commands, tmp);
try {
if(is_cmd_prefix(tmp, "set-setting")) {
handle_set(tmp, win);
return;
}
if(is_cmd_prefix(tmp, "get-setting")) {
handle_get(tmp, win);
return;
}
if(is_cmd_prefix(tmp, "print-settings")) {
setting_print_all(win);
return;
}
if(is_cmd_prefix(tmp, "unset-setting")) {
handle_unset(tmp, win);
return;
}
if(is_cmd_prefix(tmp, "print-keybindings")) {
if(win)
win->dumpbindings();
else
throw std::runtime_error("Can't list bindings without graphics context");
return;
}
if(is_cmd_prefix(tmp, "print-aliases")) {
handle_aliases(win);
return;
}
if(is_cmd_prefix(tmp, "alias-command")) {
handle_alias(tmp, win);
return;
}
if(is_cmd_prefix(tmp, "unalias-command")) {
handle_unalias(tmp, win);
return;
}
if(is_cmd_prefix(tmp, "run-script")) {
handle_run(tmp, win, *this, recursive_commands);
return;
}
if(is_cmd_prefix(tmp, "bind-key")) {
binding_request req(tmp);
if(win)
win->bind(req.mod, req.modmask, req.keyname, req.command);
else
throw std::runtime_error("Can't bind keys without graphics context");
return;
}
if(is_cmd_prefix(tmp, "unbind-key")) {
binding_request req(tmp);
if(win)
win->unbind(req.mod, req.modmask, req.keyname);
else
throw std::runtime_error("Can't unbind keys without graphics context");
return;
}
if(win && win->exec_command(tmp))
return;
if(vid_dumper_command(tmp, win))
return;
if(memorymanip_command(tmp, win))
return;
if(lua_command(tmp, win))
return;
} catch(std::bad_alloc& e) {
OOM_panic(win);
} catch(std::exception& e) {
out(win) << "Error: " << e.what() << std::endl;
return;
}
if(!noalias && aliases.count(tmp)) {
auto& x = aliases[tmp];
for(auto i = x.begin(); i != x.end(); i++) {
std::string y = *i;
docommand(y, win, recursive_commands);
}
} else {
docommand2(tmp, win);
}
}
std::string fixup_command_polarity(std::string cmd, bool polarity) throw(std::bad_alloc)
{
if(cmd == "")
return "";
if(cmd[0] != '+' && polarity)
return "";
if(cmd[0] == '+' && !polarity)
cmd[0] = '-';
return cmd;
}
binding_request::binding_request(std::string& cmd) throw(std::bad_alloc, std::runtime_error)
{
if(is_cmd_prefix(cmd, "bind-key")) {
std::string syntax = "Syntax: bind-key [<mod>/<modmask>] <key> <command>";
tokensplitter t(cmd);
std::string dummy = t;
std::string mod_or_key = t;
if(mod_or_key.find_first_of("/") < mod_or_key.length()) {
//Mod field.
size_t split = mod_or_key.find_first_of("/");
mod = mod_or_key.substr(0, split);
modmask = mod_or_key.substr(split + 1);
mod_or_key = static_cast<std::string>(t);
}
if(mod_or_key == "")
throw std::runtime_error(syntax);
keyname = mod_or_key;
command = t.tail();
if(command == "")
throw std::runtime_error(syntax);
polarity = true;
} else if(is_cmd_prefix(cmd, "unbind-key")) {
std::string syntax = "Syntax: unbind-key [<mod>/<modmask>] <key>";
tokensplitter t(cmd);
std::string dummy = t;
std::string mod_or_key = t;
if(mod_or_key.find_first_of("/") < mod_or_key.length()) {
//Mod field.
size_t split = mod_or_key.find_first_of("/");
mod = mod_or_key.substr(0, split);
modmask = mod_or_key.substr(split + 1);
mod_or_key = static_cast<std::string>(t);
}
if(mod_or_key == "")
throw std::runtime_error(syntax);
keyname = mod_or_key;
if(t.tail() != "")
throw std::runtime_error(syntax);
polarity = false;
} else
throw std::runtime_error("Not a valid binding request");
}

218
keymapper.hpp Normal file
View file

@ -0,0 +1,218 @@
#ifndef _keymapper__hpp__included__
#define _keymapper__hpp__included__
#include <string>
#include <sstream>
#include <stdexcept>
#include <list>
#include <set>
#include <iostream>
#include "window.hpp"
#include "misc.hpp"
/**
* \brief Fixup command according to key polarity.
*
* Takes in a raw command and returns the command that should be actually executed given the key polarity.
*
* \param cmd Raw command.
* \param polarity Polarity (True => Being pressed, False => Being released).
* \return The fixed command, "" if no command should be executed.
* \throws std::bad_alloc Not enough memory.
*/
std::string fixup_command_polarity(std::string cmd, bool polarity) throw(std::bad_alloc);
/**
* \brief Alias-expanding command handler.
*/
struct aliasexpand_commandhandler : public commandhandler
{
public:
/**
* \brief Constructor.
*/
aliasexpand_commandhandler() throw();
/**
* \brief Destructor.
*/
~aliasexpand_commandhandler() throw();
/**
* \brief Run specified command, expanding aliases.
*
* Runs the specified command, expanding any possible aliases, including running scripts and internally recognizing
* many special command classes (bind commands, alias commands, dumper commands, settings and graphics platform
* commands).
*
* This won't recursively expand aliases nor scripts.
*
* \param cmd The command
* \param win The context to graphics platform.
* \throws std::bad_alloc Not enough memory.
*/
void docommand(std::string& cmd, window* win) throw(std::bad_alloc);
/**
* \brief Run specified command, expanding aliases.
*
* Runs the specified command, expanding any possible aliases, including running scripts and internally recognizing
* many special command classes (bind commands, alias commands, dumper commands, settings and graphics platform
* commands).
*
* This won't recursively expand aliases nor scripts. The diffrence from the other version is that explicit list of
* commands that have been recursed through is given.
*
* \param cmd The command
* \param win The context to graphics platform.
* \param recursive_commands The set of commands already recursively executed.
* \throws std::bad_alloc Not enough memory.
*/
void docommand(std::string& cmd, window* win, std::set<std::string>& recursive_commands) throw(std::bad_alloc);
/**
* \brief Run specified command, no alias expansion.
*
* Runs the specified command (is not of those internally handled classes) without alias / script expansion.
*
* \param cmd The command
* \param win The context to graphics platform.
* \throws std::bad_alloc Not enough memory.
* \throws std::runtime_error Error running command.
*/
virtual void docommand2(std::string& cmd, window* win) throw(std::bad_alloc, std::runtime_error) = 0;
private:
aliasexpand_commandhandler(const aliasexpand_commandhandler&);
aliasexpand_commandhandler& operator=(const aliasexpand_commandhandler&);
};
/**
* \brief Keyboard mapper.
*
* This class handles internals of mapping events from keyboard buttons and pseudo-buttons. The helper class T has
* to have the following:
*
* unsigned T::mod_str(const std::string& mod): Translate modifiers set mod into modifier mask.
* typedef T::internal_keysymbol: Key symbol to match against. Needs to have == operator available.
* T::internal_keysymbol key_str(const std::string& keyname): Translate key name to key to match against.
* typedef T::keysymbol: Key symbol from keyboard (or pseudo-button). Carries modifiers too.
* unsigned mod_key(T::keysymbol key): Get modifier mask for keyboard key.
* T::internal_keysymbol key_key(T::keysymbol key): Get key symbol to match against for given keyboard key.
* std::string T::name_key(unsigned mod, unsigned modmask, T::internal_keysymbol key): Print name of key with mods.
*/
template<class T>
class keymapper
{
public:
/**
* \brief Bind a key.
*
* Binds a key, erroring out if binding would conflict with existing one.
*
* \param mod Modifier set to require to be pressed.
* \param modmask Modifier set to take into account.
* \param keyname Key to bind the action to.
* \param command The command to bind.
* \throws std::bad_alloc Not enough memory.
* \throws std::runtime_error The binding would conflict with existing one.
*/
void bind(std::string mod, std::string modmask, std::string keyname, std::string command) throw(std::bad_alloc,
std::runtime_error)
{
unsigned _mod = T::mod_str(mod);
unsigned _modmask = T::mod_str(modmask);
if(_mod & ~_modmask)
throw std::runtime_error("Mod must be subset of modmask");
typename T::internal_keysymbol _keyname = T::key_str(keyname);
/* Check for collisions. */
for(auto i = bindings.begin(); i != bindings.end(); i++) {
if(!(_keyname == i->symbol))
continue;
if((_mod & _modmask & i->modmask) != (i->mod & _modmask & i->modmask))
continue;
throw std::runtime_error("Would conflict with " + T::name_key(i->mod, i->modmask, i->symbol));
}
struct kdata k;
k.mod = _mod;
k.modmask = _modmask;
k.symbol = _keyname;
k.command = command;
bindings.push_back(k);
}
/**
* \brief Unbind a key.
*
* Unbinds a key, erroring out if binding does not exist..
*
* \param mod Modifier set to require to be pressed.
* \param modmask Modifier set to take into account.
* \param keyname Key to bind the action to.
* \throws std::bad_alloc Not enough memory.
* \throws std::runtime_error The binding does not exist.
*/
void unbind(std::string mod, std::string modmask, std::string keyname) throw(std::bad_alloc,
std::runtime_error)
{
unsigned _mod = T::mod_str(mod);
unsigned _modmask = T::mod_str(modmask);
typename T::internal_keysymbol _keyname = T::key_str(keyname);
for(auto i = bindings.begin(); i != bindings.end(); i++) {
if(!(_keyname == i->symbol) || _mod != i->mod || _modmask != i->modmask)
continue;
bindings.erase(i);
return;
}
throw std::runtime_error("No such binding");
}
/**
* \brief Map key symbol from keyboard + polarity into a command.
*
* Takes in symbol from keyboard and polarity. Outputs command to run.
*
* \param sym Symbol from keyboard (with its mods).
* \param polarity True if key is being pressed, false if being released.
* \return The command to run. "" if none.
* \throws std::bad_alloc Not enough memory.
*/
std::string map(typename T::keysymbol sym, bool polarity) throw(std::bad_alloc)
{
unsigned _mod = T::mod_key(sym);
typename T::internal_keysymbol _keyname = T::key_key(sym);
for(auto i = bindings.begin(); i != bindings.end(); i++) {
if((!(_keyname == i->symbol)) || ((_mod & i->modmask) != (i->mod & i->modmask)))
continue;
std::string x = fixup_command_polarity(i->command, polarity);
if(x == "")
continue;
return x;
}
return "";
}
/**
* \brief Dump list of bindigns as messages to specified graphics handle.
*
* \param win The graphics system handle.
* \throws std::bad_alloc Not enough memory.
*/
void dumpbindings(window* win) throw(std::bad_alloc)
{
for(auto i = bindings.begin(); i != bindings.end(); i++)
out(win) << "bind " << T::name_key(i->mod, i->modmask, i->symbol) << " " << i->command
<< std::endl;
}
private:
struct kdata
{
unsigned mod;
unsigned modmask;
typename T::internal_keysymbol symbol;
std::string command;
};
std::list<kdata> bindings;
};
#endif

151
lsnes.cpp Normal file
View file

@ -0,0 +1,151 @@
#include <sstream>
#include "mainloop.hpp"
#include "lua.hpp"
#include "rrdata.hpp"
#include "lsnes.hpp"
#include "rom.hpp"
#include "keymapper.hpp"
#include "misc.hpp"
#include <sys/time.h>
#include <snes/snes.hpp>
#include <ui-libsnes/libsnes.hpp>
#include "framerate.hpp"
#if defined(_WIN32) || defined(_WIN64)
#include "SDL_main.h"
#endif
class my_interfaced : public SNES::Interface
{
string path(SNES::Cartridge::Slot slot, const string &hint)
{
return "./";
}
};
class mycommandhandlerd : public aliasexpand_commandhandler
{
public:
void docommand2(std::string& cmd, window* win) throw(std::bad_alloc, std::runtime_error)
{
if(cmd != "")
win->message("Unrecognized command: " + cmd);
}
};
struct moviefile generate_movie_template(std::vector<std::string> cmdline, loaded_rom& r, window* win)
{
struct moviefile movie;
movie.gametype = gtype::togametype(r.rtype, r.region);
movie.rom_sha256 = r.rom.sha256;
movie.romxml_sha256 = r.rom_xml.sha256;
movie.slota_sha256 = r.slota.sha256;
movie.slotaxml_sha256 = r.slota_xml.sha256;
movie.slotb_sha256 = r.slotb.sha256;
movie.slotbxml_sha256 = r.slotb_xml.sha256;
movie.movie_sram = load_sram_commandline(cmdline);
for(auto i = cmdline.begin(); i != cmdline.end(); i++) {
std::string o = *i;
if(o.length() >= 8 && o.substr(0, 8) == "--port1=")
movie.port1 = port_type::lookup(o.substr(8), false).ptype;
if(o.length() >= 8 && o.substr(0, 8) == "--port2=")
movie.port2 = port_type::lookup(o.substr(8), true).ptype;
if(o.length() >= 11 && o.substr(0, 11) == "--gamename=")
movie.gamename = o.substr(11);
if(o.length() >= 9 && o.substr(0, 9) == "--author=") {
std::string line = o.substr(9);
fieldsplitter f(line);
std::string full = f;
std::string nick = f;
if(full == "" && nick == "")
throw std::runtime_error("Bad author name, one of full or nickname must be present");
movie.authors.push_back(std::make_pair(full, nick));
}
}
return movie;
}
#if defined(_WIN32) || defined(_WIN64)
int SDL_main(int argc, char** argv)
#else
int main(int argc, char** argv)
#endif
{
std::vector<std::string> cmdline;
for(int i = 1; i < argc; i++)
cmdline.push_back(argv[i]);
mycommandhandlerd handler;
my_interfaced intrf;
SNES::system.interface = &intrf;
set_random_seed();
{
std::ostringstream x;
x << snes_library_id() << " (" << SNES::Info::Profile << " core)";
bsnes_core_version = x.str();
}
window win;
init_lua(&win);
lua_set_commandhandler(handler);
win.out() << "BSNES version: " << bsnes_core_version << std::endl;
win.out() << "lsnes version: lsnes rr" << lsnes_version << std::endl;
win.out() << "Command line is: ";
for(auto k = cmdline.begin(); k != cmdline.end(); k++)
win.out() << "\"" << *k << "\" ";
win.out() << std::endl;
std::string cfgpath = get_config_path(&win);
create_lsnesrc(&win);
out(&win) << "Saving per-user data to: " << get_config_path(&win) << std::endl;
out(&win) << "--- Running lsnesrc --- " << std::endl;
std::string rc = "run-script " + cfgpath + "/lsnes.rc";
handler.docommand(rc, &win);
out(&win) << "--- End running lsnesrc --- " << std::endl;
out(&win) << "--- Loading ROM ---" << std::endl;
struct loaded_rom r;
try {
r = load_rom_from_commandline(cmdline, &win);
r.load();
} catch(std::bad_alloc& e) {
OOM_panic(&win);
} catch(std::exception& e) {
win.out() << "FATAL: Can't load ROM: " << e.what() << std::endl;
win.fatal_error();
exit(1);
}
win.out() << "Detected region: " << gtype::tostring(r.rtype, r.region) << std::endl;
if(r.region == REGION_PAL)
set_nominal_framerate(322445.0/6448.0);
else if(r.region == REGION_NTSC)
set_nominal_framerate(10738636.0/178683.0);
out(&win) << "--- Internal memory mappings ---" << std::endl;
dump_region_map(&win);
out(&win) << "--- End of Startup --- " << std::endl;
try {
moviefile movie;
bool loaded = false;
for(auto i = cmdline.begin(); i != cmdline.end(); i++)
if(i->length() > 0 && (*i)[0] != '-') {
movie = moviefile(*i);
loaded = true;
}
if(!loaded)
movie = generate_movie_template(cmdline, r, &win);
main_loop(&win, r, movie);
} catch(std::bad_alloc& e) {
OOM_panic(&win);
} catch(std::exception& e) {
win.message(std::string("Fatal: ") + e.what());
win.fatal_error();
return 1;
}
rrdata::close();
return 0;
}

19
lsnes.hpp Normal file
View file

@ -0,0 +1,19 @@
#ifndef lsnes__hpp__included__
#define lsnes__hpp__included__
#include <iostream>
#define STILL_HERE do { std::cerr << "Still here at file " << __FILE__ << " line " << __LINE__ << "." << std::endl; } \
while(0)
#define DEBUGGER
#ifdef BSNES_IS_COMPAT
#define PROFILE_COMPATIBILITY
#else
#define PROFILE_ACCURACY
#endif
extern std::string bsnes_core_version;
extern std::string lsnes_version;
#endif

65
lsnes.rc Normal file
View file

@ -0,0 +1,65 @@
bind-key /rctrl q quit-emulator
bind-key rctrl/rctrl q quit-emulator /y
bind-key pause pause-emulator
alias-command +speed set-setting targetfps infinite
alias-command -speed unset-setting targetfps
bind-key scrollock +speed
bind-key pageup scroll-up
bind-key pagedown scroll-down
bind-key home scroll-fullup
bind-key end scroll-fulldown
bind-key key121 toggle-console
bind-key left +controller1left
bind-key up +controller1up
bind-key right +controller1right
bind-key down +controller1down
bind-key rctrl +controller1A
bind-key ralt +controller1B
bind-key rshift +controller1X
bind-key l +controller1Y
bind-key o +controller1L
bind-key p +controller1R
bind-key return +controller1start
bind-key 0 +controller1select
bind-key a +controller2left
bind-key w +controller2up
bind-key d +controller2right
bind-key s +controller2down
bind-key lctrl +controller2A
bind-key lalt +controller2B
bind-key lshift +controller2X
bind-key z +controller2Y
bind-key 1 +controller2L
bind-key 3 +controller2R
bind-key tab +controller2start
bind-key x +controller2select
bind-key backspace +advance-frame
bind-key /shift f1 save-state movieslot1.lsmv
bind-key /shift f2 save-state movieslot2.lsmv
bind-key /shift f3 save-state movieslot3.lsmv
bind-key /shift f4 save-state movieslot4.lsmv
bind-key /shift f5 save-state movieslot5.lsmv
bind-key /shift f6 save-state movieslot6.lsmv
bind-key /shift f7 save-state movieslot7.lsmv
bind-key /shift f8 save-state movieslot8.lsmv
bind-key /shift f9 save-state movieslot9.lsmv
bind-key /shift f10 save-state movieslota.lsmv
bind-key /shift f11 save-state movieslotb.lsmv
bind-key /shift f12 save-state movieslotc.lsmv
bind-key shift/shift f1 load-state movieslot1.lsmv
bind-key shift/shift f2 load-state movieslot2.lsmv
bind-key shift/shift f3 load-state movieslot3.lsmv
bind-key shift/shift f4 load-state movieslot4.lsmv
bind-key shift/shift f5 load-state movieslot5.lsmv
bind-key shift/shift f6 load-state movieslot6.lsmv
bind-key shift/shift f7 load-state movieslot7.lsmv
bind-key shift/shift f8 load-state movieslot8.lsmv
bind-key shift/shift f9 load-state movieslot9.lsmv
bind-key shift/shift f10 load-state movieslota.lsmv
bind-key shift/shift f11 load-state movieslotb.lsmv
bind-key shift/shift f12 load-state movieslotc.lsmv

37
lsnesrc Normal file
View file

@ -0,0 +1,37 @@
bind /rctrl q quit
bind rctrl/rctrl q quit /y
bind pause pause
alias +speed set targetfps infinite
alias -speed unset targetfps
bind f12 +speed
bind pageup scrollup
bind pagedown scrolldown
bind home scrollfullup
bind end scrollfulldown
bind key121 toggleconsole
bind left +controller1left
bind up +controller1up
bind right +controller1right
bind down +controller1down
bind rctrl +controller1A
bind ralt +controller1B
bind rshift +controller1X
bind l +controller1Y
bind o +controller1L
bind p +controller1R
bind return +controller1start
bind backspace +controller1select
bind a +controller2left
bind w +controller2up
bind d +controller2right
bind s +controller2down
bind lctrl +controller2A
bind lalt +controller2B
bind lshift +controller2X
bind z +controller2Y
bind 1 +controller2L
bind 3 +controller2R
bind tab +controller2start
bind x +controller2select

60
lsnesrc.cpp Normal file
View file

@ -0,0 +1,60 @@
const char* lsnesrc_file =
"bind-key /rctrl q quit-emulator\n"
"bind-key rctrl/rctrl q quit-emulator /y\n"
"bind-key pause pause-emulator\n"
"alias-command +speed set-setting targetfps infinite\n"
"alias-command -speed unset-setting targetfps\n"
"bind-key scrollock +speed\n"
"bind-key pageup scroll-up\n"
"bind-key pagedown scroll-down\n"
"bind-key home scroll-fullup\n"
"bind-key end scroll-fulldown\n"
"bind-key left +controller1left\n"
"bind-key up +controller1up\n"
"bind-key right +controller1right\n"
"bind-key down +controller1down\n"
"bind-key rctrl +controller1A\n"
"bind-key ralt +controller1B\n"
"bind-key rshift +controller1X\n"
"bind-key l +controller1Y\n"
"bind-key o +controller1L\n"
"bind-key p +controller1R\n"
"bind-key return +controller1start\n"
"bind-key 0 +controller1select\n"
"bind-key a +controller2left\n"
"bind-key w +controller2up\n"
"bind-key d +controller2right\n"
"bind-key s +controller2down\n"
"bind-key lctrl +controller2A\n"
"bind-key lalt +controller2B\n"
"bind-key lshift +controller2X\n"
"bind-key z +controller2Y\n"
"bind-key 1 +controller2L\n"
"bind-key 3 +controller2R\n"
"bind-key tab +controller2start\n"
"bind-key x +controller2select\n"
"bind-key backspace +advance-frame\n"
"bind-key /shift f1 save-state movieslot1.lsmv\n"
"bind-key /shift f2 save-state movieslot2.lsmv\n"
"bind-key /shift f3 save-state movieslot3.lsmv\n"
"bind-key /shift f4 save-state movieslot4.lsmv\n"
"bind-key /shift f5 save-state movieslot5.lsmv\n"
"bind-key /shift f6 save-state movieslot6.lsmv\n"
"bind-key /shift f7 save-state movieslot7.lsmv\n"
"bind-key /shift f8 save-state movieslot8.lsmv\n"
"bind-key /shift f9 save-state movieslot9.lsmv\n"
"bind-key /shift f10 save-state movieslota.lsmv\n"
"bind-key /shift f11 save-state movieslotb.lsmv\n"
"bind-key /shift f12 save-state movieslotc.lsmv\n"
"bind-key shift/shift f1 load-state movieslot1.lsmv\n"
"bind-key shift/shift f2 load-state movieslot2.lsmv\n"
"bind-key shift/shift f3 load-state movieslot3.lsmv\n"
"bind-key shift/shift f4 load-state movieslot4.lsmv\n"
"bind-key shift/shift f5 load-state movieslot5.lsmv\n"
"bind-key shift/shift f6 load-state movieslot6.lsmv\n"
"bind-key shift/shift f7 load-state movieslot7.lsmv\n"
"bind-key shift/shift f8 load-state movieslot8.lsmv\n"
"bind-key shift/shift f9 load-state movieslot9.lsmv\n"
"bind-key shift/shift f10 load-state movieslota.lsmv\n"
"bind-key shift/shift f11 load-state movieslotb.lsmv\n"
"bind-key shift/shift f12 load-state movieslotc.lsmv\n";

69
lua-dummy.cpp Normal file
View file

@ -0,0 +1,69 @@
#include "lua.hpp"
void lua_callback_do_paint(struct lua_render_context* ctx, window* win) throw()
{
}
void lua_callback_do_video(struct lua_render_context* ctx, window* win) throw()
{
}
void lua_callback_do_input(controls_t& data, bool subframe, window* win) throw()
{
}
void lua_callback_do_reset(window* win) throw()
{
}
void lua_callback_do_readwrite(window* win) throw()
{
}
void lua_callback_startup(window* win) throw()
{
}
void lua_callback_pre_load(const std::string& name, window* win) throw()
{
}
void lua_callback_err_load(const std::string& name, window* win) throw()
{
}
void lua_callback_post_load(const std::string& name, bool was_state, window* win) throw()
{
}
void lua_callback_pre_save(const std::string& name, bool is_state, window* win) throw()
{
}
void lua_callback_err_save(const std::string& name, window* win) throw()
{
}
void lua_callback_post_save(const std::string& name, bool is_state, window* win) throw()
{
}
bool lua_command(const std::string& cmd, window* win) throw(std::bad_alloc)
{
return false;
}
void lua_callback_quit(window* win) throw()
{
}
void lua_set_commandhandler(commandhandler& cmdh) throw()
{
}
void init_lua(window* win) throw()
{
}
bool lua_requests_repaint = false;
bool lua_requests_subframe_paint = false;

859
lua.cpp Normal file
View file

@ -0,0 +1,859 @@
#include "lua.hpp"
#include "misc.hpp"
#include "memorymanip.hpp"
#include "mainloop.hpp"
extern "C" {
#include <lua.h>
#include <lualib.h>
}
#include <iostream>
#include "fieldsplit.hpp"
#define BITWISE_BITS 48
#define BITWISE_MASK ((1ULL << (BITWISE_BITS)) - 1)
#define SETFIELDFUN(LSS, idx, name, fun) do { lua_pushcfunction(LSS, fun); lua_setfield(LSS, (idx) - 1, name); \
} while(0)
namespace
{
window* tmp_win;
lua_State* L;
lua_render_context* rctx = NULL;
bool recursive_flag = false;
commandhandler* cmdhnd;
const char* luareader_fragment = NULL;
controls_t* controllerdata = NULL;
const char* read_lua_fragment(lua_State* LS, void* dummy, size_t* size)
{
if(luareader_fragment) {
const char* ret = luareader_fragment;
*size = strlen(luareader_fragment);
luareader_fragment = NULL;
return ret;
} else {
*size = 0;
return NULL;
}
}
void* alloc(void* user, void* old, size_t olds, size_t news)
{
if(news)
return realloc(old, news);
else
free(old);
return NULL;
}
bool callback_exists(const char* name)
{
if(recursive_flag)
return false;
lua_getglobal(L, name);
int t = lua_type(L, -1);
if(t != LUA_TFUNCTION)
lua_pop(L, 1);
return (t == LUA_TFUNCTION);
}
void push_string(const std::string& s)
{
lua_pushlstring(L, s.c_str(), s.length());
}
void push_boolean(bool b)
{
lua_pushboolean(L, b ? 1 : 0);
}
#define TEMPORARY "LUAINTERP_INTERNAL_COMMAND_TEMPORARY"
const char* eval_lua_lua = "loadstring(" TEMPORARY ")();";
const char* run_lua_lua = "dofile(" TEMPORARY ");";
void run_lua_fragment(window* win) throw(std::bad_alloc)
{
if(recursive_flag)
return;
int t = lua_load(L, read_lua_fragment, NULL, "run_lua_fragment");
if(t == LUA_ERRSYNTAX) {
out(win) << "Can't run Lua: Internal syntax error: " << lua_tostring(L, -1) << std::endl;
lua_pop(L, 1);
return;
}
if(t == LUA_ERRMEM) {
out(win) << "Can't run Lua: Out of memory" << std::endl;
lua_pop(L, 1);
return;
}
recursive_flag = true;
tmp_win = win;
int r = lua_pcall(L, 0, 0, 0);
recursive_flag = false;
if(r == LUA_ERRRUN) {
out(win) << "Error running Lua hunk: " << lua_tostring(L, -1) << std::endl;
lua_pop(L, 1);
}
if(r == LUA_ERRMEM) {
out(win) << "Error running Lua hunk: Out of memory" << std::endl;
lua_pop(L, 1);
}
if(r == LUA_ERRERR) {
out(win) << "Error running Lua hunk: Double Fault???" << std::endl;
lua_pop(L, 1);
}
if(lua_requests_repaint && cmdhnd) {
std::string c = "repaint";
lua_requests_repaint = false;
cmdhnd->docommand(c, win);
}
}
template<typename T>
T get_numeric_argument(lua_State* LS, unsigned argindex, const char* fname)
{
if(lua_isnone(LS, argindex) || !lua_isnumber(LS, argindex)) {
lua_pushfstring(L, "argument #%i to %s must be numeric", argindex, fname);
lua_error(LS);
}
return static_cast<T>(lua_tonumber(LS, argindex));
}
std::string get_string_argument(lua_State* LS, unsigned argindex, const char* fname)
{
if(lua_isnone(LS, argindex)) {
lua_pushfstring(L, "argument #%i to %s must be string", argindex, fname);
lua_error(LS);
}
size_t len;
const char* f = lua_tolstring(LS, argindex, &len);
if(!f) {
lua_pushfstring(L, "argument #%i to %s must be string", argindex, fname);
lua_error(LS);
}
return std::string(f, f + len);
}
bool get_boolean_argument(lua_State* LS, unsigned argindex, const char* fname)
{
if(lua_isnone(LS, argindex) || !lua_isboolean(LS, argindex)) {
lua_pushfstring(L, "argument #%i to %s must be boolean", argindex, fname);
lua_error(LS);
}
return (lua_toboolean(LS, argindex) != 0);
}
template<typename T>
void get_numeric_argument(lua_State* LS, unsigned argindex, T& value, const char* fname)
{
if(lua_isnoneornil(LS, argindex))
return;
if(lua_isnone(LS, argindex) || !lua_isnumber(LS, argindex)) {
lua_pushfstring(L, "argument #%i to %s must be numeric if present", argindex, fname);
lua_error(LS);
}
value = static_cast<T>(lua_tonumber(LS, argindex));
}
void do_eval_lua(const std::string& c, window* win) throw(std::bad_alloc)
{
push_string(c);
lua_setglobal(L, TEMPORARY);
luareader_fragment = eval_lua_lua;
run_lua_fragment(win);
}
void do_run_lua(const std::string& c, window* win) throw(std::bad_alloc)
{
push_string(c);
lua_setglobal(L, TEMPORARY);
luareader_fragment = run_lua_lua;
run_lua_fragment(win);
}
void run_lua_cb(int args, window* win) throw()
{
recursive_flag = true;
tmp_win = win;
int r = lua_pcall(L, args, 0, 0);
recursive_flag = false;
if(r == LUA_ERRRUN) {
out(win) << "Error running Lua callback: " << lua_tostring(L, -1) << std::endl;
lua_pop(L, 1);
}
if(r == LUA_ERRMEM) {
out(win) << "Error running Lua callback: Out of memory" << std::endl;
lua_pop(L, 1);
}
if(r == LUA_ERRERR) {
out(win) << "Error running Lua callback: Double Fault???" << std::endl;
lua_pop(L, 1);
}
if(lua_requests_repaint && cmdhnd) {
std::string c = "repaint";
lua_requests_repaint = false;
cmdhnd->docommand(c, win);
}
}
int lua_symmetric_bitwise(lua_State* LS, uint64_t (*combine)(uint64_t chain, uint64_t arg), uint64_t init)
{
int stacksize = 0;
while(!lua_isnone(LS, stacksize + 1))
stacksize++;
uint64_t ret = init;
for(int i = 0; i < stacksize; i++)
ret = combine(ret, get_numeric_argument<uint64_t>(LS, i + 1, "<bitwise function>"));
lua_pushnumber(LS, ret);
return 1;
}
int lua_shifter(lua_State* LS, uint64_t (*shift)(uint64_t base, uint64_t amount, uint64_t bits))
{
uint64_t base;
uint64_t amount = 1;
uint64_t bits = BITWISE_BITS;
base = get_numeric_argument<uint64_t>(LS, 1, "<shift function>");
get_numeric_argument(LS, 2, amount, "<shift function>");
get_numeric_argument(LS, 3, bits, "<shift function>");
lua_pushnumber(LS, shift(base, amount, bits));
return 1;
}
uint64_t combine_none(uint64_t chain, uint64_t arg)
{
return (chain & ~arg) & BITWISE_MASK;
}
uint64_t combine_any(uint64_t chain, uint64_t arg)
{
return (chain | arg) & BITWISE_MASK;
}
uint64_t combine_all(uint64_t chain, uint64_t arg)
{
return (chain & arg) & BITWISE_MASK;
}
uint64_t combine_parity(uint64_t chain, uint64_t arg)
{
return (chain ^ arg) & BITWISE_MASK;
}
uint64_t shift_lrotate(uint64_t base, uint64_t amount, uint64_t bits)
{
uint64_t mask = ((1ULL << bits) - 1);
base &= mask;
base = (base << amount) | (base >> (bits - amount));
return base & mask & BITWISE_MASK;
}
uint64_t shift_rrotate(uint64_t base, uint64_t amount, uint64_t bits)
{
uint64_t mask = ((1ULL << bits) - 1);
base &= mask;
base = (base >> amount) | (base << (bits - amount));
return base & mask & BITWISE_MASK;
}
uint64_t shift_lshift(uint64_t base, uint64_t amount, uint64_t bits)
{
uint64_t mask = ((1ULL << bits) - 1);
base <<= amount;
return base & mask & BITWISE_MASK;
}
uint64_t shift_lrshift(uint64_t base, uint64_t amount, uint64_t bits)
{
uint64_t mask = ((1ULL << bits) - 1);
base &= mask;
base >>= amount;
return base & BITWISE_MASK;
}
uint64_t shift_arshift(uint64_t base, uint64_t amount, uint64_t bits)
{
uint64_t mask = ((1ULL << bits) - 1);
base &= mask;
bool negative = ((base >> (bits - 1)) != 0);
base >>= amount;
base |= ((negative ? BITWISE_MASK : 0) << (bits - amount));
return base & mask & BITWISE_MASK;
}
int lua_bit_none(lua_State* LS)
{
return lua_symmetric_bitwise(LS, combine_none, BITWISE_MASK);
}
int lua_bit_any(lua_State* LS)
{
return lua_symmetric_bitwise(LS, combine_any, 0);
}
int lua_bit_all(lua_State* LS)
{
return lua_symmetric_bitwise(LS, combine_all, BITWISE_MASK);
}
int lua_bit_parity(lua_State* LS)
{
return lua_symmetric_bitwise(LS, combine_parity, 0);
}
int lua_bit_lrotate(lua_State* LS)
{
return lua_shifter(LS, shift_lrotate);
}
int lua_bit_rrotate(lua_State* LS)
{
return lua_shifter(LS, shift_rrotate);
}
int lua_bit_lshift(lua_State* LS)
{
return lua_shifter(LS, shift_lshift);
}
int lua_bit_arshift(lua_State* LS)
{
return lua_shifter(LS, shift_arshift);
}
int lua_bit_lrshift(lua_State* LS)
{
return lua_shifter(LS, shift_lrshift);
}
int lua_print(lua_State* LS)
{
int stacksize = 0;
while(!lua_isnone(LS, stacksize + 1))
stacksize++;
std::string toprint;
bool first = true;
for(int i = 0; i < stacksize; i++) {
size_t len;
const char* tmp = NULL;
if(lua_isnil(LS, i + 1)) {
tmp = "nil";
len = 3;
} else if(lua_isboolean(LS, i + 1) && lua_toboolean(LS, i + 1)) {
tmp = "true";
len = 4;
} else if(lua_isboolean(LS, i + 1) && !lua_toboolean(LS, i + 1)) {
tmp = "false";
len = 5;
} else {
tmp = lua_tolstring(LS, i + 1, &len);
if(!tmp) {
tmp = "(unprintable)";
len = 13;
}
}
std::string localmsg(tmp, tmp + len);
if(first)
toprint = localmsg;
else
toprint = toprint + "\t" + localmsg;
first = false;
}
tmp_win->message(toprint);
}
int lua_gui_resolution(lua_State* LS)
{
if(!rctx)
return 0;
lua_pushnumber(LS, rctx->width);
lua_pushnumber(LS, rctx->height);
lua_pushnumber(LS, rctx->rshift);
lua_pushnumber(LS, rctx->gshift);
lua_pushnumber(LS, rctx->bshift);
return 5;
}
int lua_gui_set_gap(lua_State* LS, uint32_t lua_render_context::*gap)
{
if(!rctx)
return 0;
uint32_t g = get_numeric_argument<uint32_t>(LS, 1, "gui.<direction>_gap");
if(g > 8192)
return 0; //Ignore ridiculous gap.
rctx->*gap = g;
}
int lua_gui_set_left_gap(lua_State* LS)
{
lua_gui_set_gap(LS, &lua_render_context::left_gap);
}
int lua_gui_set_right_gap(lua_State* LS)
{
lua_gui_set_gap(LS, &lua_render_context::right_gap);
}
int lua_gui_set_top_gap(lua_State* LS)
{
lua_gui_set_gap(LS, &lua_render_context::top_gap);
}
int lua_gui_set_bottom_gap(lua_State* LS)
{
lua_gui_set_gap(LS, &lua_render_context::bottom_gap);
}
int lua_gui_text(lua_State* LS)
{
if(!rctx)
return 0;
uint32_t x255 = 255;
uint32_t fgc = (x255 << rctx->rshift) | (x255 << rctx->gshift) | (x255 << rctx->bshift);
uint32_t bgc = 0;
uint16_t fga = 256;
uint16_t bga = 0;
int32_t _x = get_numeric_argument<int32_t>(LS, 1, "gui.text");
int32_t _y = get_numeric_argument<int32_t>(LS, 2, "gui.text");
get_numeric_argument<uint32_t>(LS, 4, fgc, "gui.text");
get_numeric_argument<uint16_t>(LS, 5, fga, "gui.text");
get_numeric_argument<uint32_t>(LS, 6, bgc, "gui.text");
get_numeric_argument<uint16_t>(LS, 7, bga, "gui.text");
std::string text = get_string_argument(LS, 3, "gui.text");
rctx->queue->add(*new render_object_text(_x, _y, text, fgc, fga, bgc, bga));
return 0;
}
int lua_gui_request_repaint(lua_State* LS)
{
lua_requests_repaint = true;
return 0;
}
int lua_gui_update_subframe(lua_State* LS)
{
lua_requests_subframe_paint = get_boolean_argument(LS, 1, "gui.subframe_update");
return 0;
}
int lua_exec(lua_State* LS)
{
std::string text = get_string_argument(LS, 1, "exec");
cmdhnd->docommand(text, tmp_win);
return 0;
}
template<typename T, typename U>
int lua_read_memory(lua_State* LS, U (*rfun)(uint32_t addr))
{
uint32_t addr = get_numeric_argument<uint32_t>(LS, 1, "memory.read<type>");
lua_pushnumber(LS, static_cast<T>(rfun(addr)));
return 1;
}
template<typename T>
int lua_write_memory(lua_State* LS, bool (*wfun)(uint32_t addr, T value))
{
uint32_t addr = get_numeric_argument<uint32_t>(LS, 1, "memory.write<type>");
T value = get_numeric_argument<T>(LS, 2, "memory.write<type>");
wfun(addr, value);
return 0;
}
int lua_memory_readbyte(lua_State* LS)
{
return lua_read_memory<uint8_t, uint8_t>(LS, memory_read_byte);
}
int lua_memory_readsbyte(lua_State* LS)
{
return lua_read_memory<int8_t, uint8_t>(LS, memory_read_byte);
}
int lua_memory_readword(lua_State* LS)
{
return lua_read_memory<uint16_t, uint16_t>(LS, memory_read_word);
}
int lua_memory_readsword(lua_State* LS)
{
return lua_read_memory<int16_t, uint16_t>(LS, memory_read_word);
}
int lua_memory_readdword(lua_State* LS)
{
return lua_read_memory<uint32_t, uint32_t>(LS, memory_read_dword);
}
int lua_memory_readsdword(lua_State* LS)
{
return lua_read_memory<int32_t, uint32_t>(LS, memory_read_dword);
}
int lua_memory_readqword(lua_State* LS)
{
return lua_read_memory<uint64_t, uint64_t>(LS, memory_read_qword);
}
int lua_memory_readsqword(lua_State* LS)
{
return lua_read_memory<int64_t, uint64_t>(LS, memory_read_qword);
}
int lua_memory_writebyte(lua_State* LS)
{
return lua_write_memory(LS, memory_write_byte);
}
int lua_memory_writeword(lua_State* LS)
{
return lua_write_memory(LS, memory_write_word);
}
int lua_memory_writedword(lua_State* LS)
{
return lua_write_memory(LS, memory_write_dword);
}
int lua_memory_writeqword(lua_State* LS)
{
return lua_write_memory(LS, memory_write_qword);
}
int lua_input_set(lua_State* LS)
{
if(!controllerdata)
return 0;
unsigned controller = get_numeric_argument<unsigned>(LS, 1, "input.set");
unsigned index = get_numeric_argument<unsigned>(LS, 2, "input.set");
short value = get_numeric_argument<short>(LS, 3, "input.set");
if(controller > 7 || index > 11)
return 0;
(*controllerdata)(controller >> 2, controller & 3, index) = value;
return 0;
}
int lua_input_get(lua_State* LS)
{
if(!controllerdata)
return 0;
unsigned controller = get_numeric_argument<unsigned>(LS, 1, "input.set");
unsigned index = get_numeric_argument<unsigned>(LS, 2, "input.set");
if(controller > 7 || index > 11)
return 0;
lua_pushnumber(LS, (*controllerdata)(controller >> 2, controller & 3, index));
return 1;
}
int lua_input_reset(lua_State* LS)
{
if(!controllerdata)
return 0;
long cycles = 0;
get_numeric_argument(LS, 1, cycles, "input.reset");
if(cycles < 0)
return 0;
short lo = cycles % 10000;
short hi = cycles / 10000;
(*controllerdata)(CONTROL_SYSTEM_RESET) = 1;
(*controllerdata)(CONTROL_SYSTEM_RESET_CYCLES_HI) = hi;
(*controllerdata)(CONTROL_SYSTEM_RESET_CYCLES_LO) = lo;
return 0;
}
int lua_hostmemory_read(lua_State* LS)
{
size_t address = get_numeric_argument<size_t>(LS, 1, "hostmemory.read");
auto& h = get_host_memory();
if(address >= h.size()) {
lua_pushboolean(LS, 0);
return 1;
}
lua_pushnumber(LS, static_cast<uint8_t>(h[address]));
return 1;
}
int lua_hostmemory_write(lua_State* LS)
{
size_t address = get_numeric_argument<size_t>(LS, 1, "hostmemory.write");
uint8_t value = get_numeric_argument<uint8_t>(LS, 2, "hostmemory.write");
auto& h = get_host_memory();
if(address >= h.size())
h.resize(address + 1);
h[address] = value;
return 0;
}
int lua_movie_currentframe(lua_State* LS)
{
auto& m = get_movie();
lua_pushnumber(LS, m.get_current_frame());
return 1;
}
int lua_movie_framecount(lua_State* LS)
{
auto& m = get_movie();
lua_pushnumber(LS, m.get_frame_count());
return 1;
}
int lua_movie_readonly(lua_State* LS)
{
auto& m = get_movie();
lua_pushboolean(LS, m.readonly_mode() ? 1 : 0);
return 1;
}
int lua_movie_set_readwrite(lua_State* LS)
{
auto& m = get_movie();
m.readonly_mode(false);
return 0;
}
int lua_movie_frame_subframes(lua_State* LS)
{
uint64_t frame = get_numeric_argument<uint64_t>(LS, 1, "movie.frame_subframes");
auto& m = get_movie();
lua_pushnumber(LS, m.frame_subframes(frame));
return 1;
}
int lua_movie_read_subframe(lua_State* LS)
{
uint64_t frame = get_numeric_argument<uint64_t>(LS, 1, "movie.frame_subframes");
uint64_t subframe = get_numeric_argument<uint64_t>(LS, 2, "movie.frame_subframes");
auto& m = get_movie();
controls_t r = m.read_subframe(frame, subframe);
lua_newtable(LS);
for(size_t i = 0; i < TOTAL_CONTROLS; i++) {
lua_pushnumber(LS, i);
lua_pushnumber(LS, r(i));
lua_settable(L, -3);
}
return 1;
}
}
void lua_callback_do_paint(struct lua_render_context* ctx, window* win) throw()
{
if(!callback_exists("on_paint"))
return;
rctx = ctx;
run_lua_cb(0, win);
rctx = NULL;
}
void lua_callback_do_video(struct lua_render_context* ctx, window* win) throw()
{
if(!callback_exists("on_video"))
return;
rctx = ctx;
run_lua_cb(0, win);
rctx = NULL;
}
void lua_callback_do_reset(window* win) throw()
{
if(!callback_exists("on_reset"))
return;
run_lua_cb(0, win);
}
void lua_callback_do_readwrite(window* win) throw()
{
if(!callback_exists("on_readwrite"))
return;
run_lua_cb(0, win);
}
void lua_callback_startup(window* win) throw()
{
if(!callback_exists("on_startup"))
return;
run_lua_cb(0, win);
}
void lua_callback_pre_load(const std::string& name, window* win) throw()
{
if(!callback_exists("on_pre_load"))
return;
push_string(name);
run_lua_cb(1, win);
}
void lua_callback_err_load(const std::string& name, window* win) throw()
{
if(!callback_exists("on_err_load"))
return;
push_string(name);
run_lua_cb(1, win);
}
void lua_callback_post_load(const std::string& name, bool was_state, window* win) throw()
{
if(!callback_exists("on_post_load"))
return;
push_string(name);
push_boolean(was_state);
run_lua_cb(2, win);
}
void lua_callback_pre_save(const std::string& name, bool is_state, window* win) throw()
{
if(!callback_exists("on_pre_save"))
return;
push_string(name);
push_boolean(is_state);
run_lua_cb(2, win);
}
void lua_callback_err_save(const std::string& name, window* win) throw()
{
if(!callback_exists("on_err_save"))
return;
push_string(name);
run_lua_cb(1, win);
}
void lua_callback_post_save(const std::string& name, bool is_state, window* win) throw()
{
if(!callback_exists("on_post_save"))
return;
push_string(name);
push_boolean(is_state);
run_lua_cb(2, win);
}
void lua_callback_do_input(controls_t& data, bool subframe, window* win) throw()
{
if(!callback_exists("on_input"))
return;
controllerdata = &data;
push_boolean(subframe);
run_lua_cb(1, win);
controllerdata = NULL;
}
bool lua_command(const std::string& cmd, window* win) throw(std::bad_alloc)
{
if(is_cmd_prefix(cmd, "eval-lua")) {
tokensplitter t(cmd);
std::string dummy = t;
do_eval_lua(t.tail(), win);
return true;
}
if(is_cmd_prefix(cmd, "run-lua")) {
tokensplitter t(cmd);
std::string dummy = t;
do_run_lua(t.tail(), win);
return true;
}
return false;
}
void lua_callback_quit(window* win) throw()
{
if(!callback_exists("on_quit"))
return;
run_lua_cb(0, win);
}
void lua_set_commandhandler(commandhandler& cmdh) throw()
{
cmdhnd = &cmdh;
}
void init_lua(window* win) throw()
{
L = lua_newstate(alloc, NULL);
if(!L) {
out(win) << "Can't initialize Lua." << std::endl;
fatal_error(win);
}
luaL_openlibs(L);
//Some globals
lua_pushcfunction(L, lua_print);
lua_setglobal(L, "print");
lua_pushcfunction(L, lua_exec);
lua_setglobal(L, "exec");
//Bit table.
lua_newtable(L);
SETFIELDFUN(L, -1, "none", lua_bit_none);
SETFIELDFUN(L, -1, "bnot", lua_bit_none);
SETFIELDFUN(L, -1, "any", lua_bit_any);
SETFIELDFUN(L, -1, "bor", lua_bit_any);
SETFIELDFUN(L, -1, "all", lua_bit_all);
SETFIELDFUN(L, -1, "band", lua_bit_all);
SETFIELDFUN(L, -1, "parity", lua_bit_parity);
SETFIELDFUN(L, -1, "bxor", lua_bit_parity);
SETFIELDFUN(L, -1, "lrotate", lua_bit_lrotate);
SETFIELDFUN(L, -1, "rrotate", lua_bit_rrotate);
SETFIELDFUN(L, -1, "lshift", lua_bit_lshift);
SETFIELDFUN(L, -1, "arshift", lua_bit_arshift);
SETFIELDFUN(L, -1, "lrshift", lua_bit_lrshift);
lua_setglobal(L, "bit");
//Gui table.
lua_newtable(L);
SETFIELDFUN(L, -1, "resolution", lua_gui_resolution);
SETFIELDFUN(L, -1, "left_gap", lua_gui_set_left_gap);
SETFIELDFUN(L, -1, "right_gap", lua_gui_set_right_gap);
SETFIELDFUN(L, -1, "top_gap", lua_gui_set_top_gap);
SETFIELDFUN(L, -1, "bottom_gap", lua_gui_set_bottom_gap);
SETFIELDFUN(L, -1, "text", lua_gui_text);
SETFIELDFUN(L, -1, "repaint", lua_gui_request_repaint);
SETFIELDFUN(L, -1, "subframe_update", lua_gui_update_subframe);
lua_setglobal(L, "gui");
//Memory table.
lua_newtable(L);
SETFIELDFUN(L, -1, "readbyte", lua_memory_readbyte);
SETFIELDFUN(L, -1, "readsbyte", lua_memory_readsbyte);
SETFIELDFUN(L, -1, "writebyte", lua_memory_writebyte);
SETFIELDFUN(L, -1, "readword", lua_memory_readword);
SETFIELDFUN(L, -1, "readsword", lua_memory_readsword);
SETFIELDFUN(L, -1, "writeword", lua_memory_writeword);
SETFIELDFUN(L, -1, "readdword", lua_memory_readdword);
SETFIELDFUN(L, -1, "readsdword", lua_memory_readsdword);
SETFIELDFUN(L, -1, "writedword", lua_memory_writedword);
SETFIELDFUN(L, -1, "readqword", lua_memory_readqword);
SETFIELDFUN(L, -1, "readsqword", lua_memory_readsqword);
SETFIELDFUN(L, -1, "writeqword", lua_memory_writeqword);
lua_setglobal(L, "memory");
//Input table
lua_newtable(L);
SETFIELDFUN(L, -1, "get", lua_input_get);
SETFIELDFUN(L, -1, "set", lua_input_set);
SETFIELDFUN(L, -1, "reset", lua_input_reset);
lua_setglobal(L, "input");
//Hostmemory table.
lua_newtable(L);
SETFIELDFUN(L, -1, "read", lua_hostmemory_read);
SETFIELDFUN(L, -1, "write", lua_hostmemory_write);
lua_setglobal(L, "hostmemory");
//Movie table.
lua_newtable(L);
SETFIELDFUN(L, -1, "currentframe", lua_movie_currentframe);
SETFIELDFUN(L, -1, "frame_subframes", lua_movie_frame_subframes);
SETFIELDFUN(L, -1, "framecount", lua_movie_framecount);
SETFIELDFUN(L, -1, "read_subframe", lua_movie_read_subframe);
SETFIELDFUN(L, -1, "readonly", lua_movie_readonly);
SETFIELDFUN(L, -1, "set_readwrite", lua_movie_set_readwrite);
lua_setglobal(L, "movie");
//TODO: Add some functions into the Lua state.
}
bool lua_requests_repaint = false;
bool lua_requests_subframe_paint = false;

42
lua.hpp Normal file
View file

@ -0,0 +1,42 @@
#ifndef _lua__hpp__included__
#define _lua__hpp__included__
#include "render.hpp"
#include "window.hpp"
#include "controllerdata.hpp"
struct lua_render_context
{
uint32_t left_gap;
uint32_t right_gap;
uint32_t top_gap;
uint32_t bottom_gap;
struct render_queue* queue;
uint32_t width;
uint32_t height;
uint32_t rshift;
uint32_t gshift;
uint32_t bshift;
};
void init_lua(window* win) throw();
void lua_callback_do_paint(struct lua_render_context* ctx, window* win) throw();
void lua_callback_do_video(struct lua_render_context* ctx, window* win) throw();
void lua_callback_do_input(controls_t& data, bool subframe, window* win) throw();
void lua_callback_do_reset(window* win) throw();
void lua_callback_do_readwrite(window* win) throw();
void lua_callback_startup(window* win) throw();
void lua_callback_pre_load(const std::string& name, window* win) throw();
void lua_callback_err_load(const std::string& name, window* win) throw();
void lua_callback_post_load(const std::string& name, bool was_state, window* win) throw();
void lua_callback_pre_save(const std::string& name, bool is_state, window* win) throw();
void lua_callback_err_save(const std::string& name, window* win) throw();
void lua_callback_post_save(const std::string& name, bool is_state, window* win) throw();
void lua_callback_quit(window* win) throw();
bool lua_command(const std::string& cmd, window* win) throw(std::bad_alloc);
void lua_set_commandhandler(commandhandler& cmdh) throw();
extern bool lua_requests_repaint;
extern bool lua_requests_subframe_paint;
#endif

1303
mainloop.cpp Normal file

File diff suppressed because it is too large Load diff

18
mainloop.hpp Normal file
View file

@ -0,0 +1,18 @@
#ifndef _mainloop__hpp__included__
#define _mainloop__hpp__included__
#include "window.hpp"
#include "rom.hpp"
#include "moviefile.hpp"
#include "movie.hpp"
/**
* \brief Emulator main loop.
*/
void main_loop(window* _win, struct loaded_rom& rom, struct moviefile& settings) throw(std::bad_alloc,
std::runtime_error);
std::vector<char>& get_host_memory();
movie& get_movie();
#endif

2511
manual.lyx Normal file

File diff suppressed because it is too large Load diff

302
manual.text Normal file
View file

@ -0,0 +1,302 @@
Memory map:
===========
(all addresses in hex)
Address range Size SRAM id Word Description
00000000-0000FFFF 64K (volatile) 1 APURAM (Sound processor RAM).
00010000-0001FFFF 64K (volatile) 1 VRAM (Video RAM).
00020000-0002021F 544 (volatile) 1 OAM (Video Object Attribute Memory).
00021000-000211FF 512 (volatile) 1 CGRAM (Video Palette).
00022000-00022013 20 rtc 1 RTC (If present on cart).
00023000-00023FFF 4K nec 2 DSP RAM (if present on cart).
007E0000-007FFFFF 128K (volatile) 1 WRAM (Main system RAM).
10000000-1XXXXXXX (variable) srm 1 SRAM.
20000000-2XXXXXXX (variable) bss/slota.sts 1 BSX RAM / Sufami Turbo slot A SRAM.
30000000-3XXXXXXX (variable) bsp/slotb.sts 1 BSX PRAM / Sufami Turbo slot B SRAM.
80000000-8XXXXXXX (variable) (rom) 1 Main ROM.
90000000-9XXXXXXX (variable) (rom) 1 BSX Flash / Sufami Turbo slot A ROM / Gameboy ROM
A0000000-AXXXXXXX (variable) (rom) 1 Sufami Turbo slot B ROM (amount depends on cart).
F0000000-F000FFFF 64K (rom) 4 DSP program ROM (if present on cart).
F0010000-F0010FFF 4K (rom) 2 DSP data ROM (if present on cart).
Framerate:
==========
NTSC: 10738636/178683 fps (60.09881186234840471673fps)
PAL: 322445/6448 fps (50.00697890818858560794fps)
Savestate/Movie format (.lsmv):
===============================
Movies/Savestates are ZIP archives. All members must either be stored or deflated. Encryption nor patching is
not allowed. Max version to decompress can be at most 20 (PKZIP 2.0).
Member 'systemid' (required):
-----------------------------
One line containing "lsnes-rr"<rr_version> (e.g. "lsnes-rr1").
Member 'controlsversion' (required):
------------------------------------
One line containing controls version.
This version is "0". WARNING: Don't try to decode movies with unknown controls version, you can encounter who knows
what.
Member 'coreversion' (required):
--------------------------------
One line containing raw bsnes version string (e.g. "bsnes v079").
On movie/savestate load, this is checked against the core emulator is using. If these mismatch, warning is issued.
Member 'port1' (optional):
--------------------------
One line containg type of port #1.
The valid values are:
* "none"
* "joypad" (the default if member is absent)
* "multitap"
* "mouse"
Member 'port2' (optional):
--------------------------
One line containg type of port #2.
The valid values are:
* "none" (the default if member is absent)
* "joypad"
* "multitap"
* "mouse"
* "superscope"
* "justifier"
* "justifiers"
Member 'project' (required):
----------------------------
One line containing project ID. Opaque string.
Member 'rerecords' (required):
------------------------------
One line containing rerecord count.
The rerecord count is given as ASCII base-10 nonnegative integer.
Note: Leading zeroes are not allowed (but 0 rerecords is still stored as "0").
Member 'authors' (required):
----------------------------
Authors of the run, one per line (empty lines are skipped).
* If only full name is available, line is of form <full_name> (e.g. "John Doe").
* If only nickname is available, line is of form "|"<nickname> (e.g. "|Ilari")
* If both full name and nickname are available, line is of form <full_name>"|"<nickname> (e.g. "Jane Doe|jdoe").
Member 'gametype' (required):
-----------------------------
One line, giving ROM type.
Valid values are:
* "snes_ntsc" (NTSC SNES)
* "snes_pal" (PAL SNES)
* "bsxslotted" (BS-X slotted; NTSC)
* "bsx" (BS-X; NTSC)
* "sufamiturbo" (Sufami Turbo; NTSC)
* "sgb_ntsc" (NTSC SGB)
* "sgb_pal" (PAL SGB)
Member 'gamename' (optional):
-----------------------------
One line giving the game name.
Member 'rom.sha256' (optional):
-------------------------------
One line, giving SHA-256 of ROM as hex string (lowercase a-f).
If there is no ROM, this member is absent.
Member 'romxml.sha256' (optional):
----------------------------------
One line, giving SHA-256 of ROM XML as hex string (lowercase a-f).
If there is no ROM XML (use emulator defaults), this member is absent.
Member 'slota.sha256' (optional):
----------------------------------
One line, giving SHA-256 of SLOT A ROM as hex string (lowercase a-f).
If there is no SLOT A ROM, this member is absent.
SNES games do not have slot A, and thus shouldn't have this member.
Member 'slotaxml.sha256' (optional):
------------------------------------
One line, giving SHA-256 of SLOT A XML as hex string (lowercase a-f).
If there is no SLOT A XML (use emulator defaults), this member is absent.
SNES games do not have slot A, and thus shouldn't have this member.
Member 'slotb.sha256' (optional):
---------------------------------
One line, giving SHA-256 of SLOT B ROM as hex string (lowercase a-f).
If there is no SLOT B ROM, this member is absent.
SNES, BS-X (slotted or not) and SGB games do not have slot B, and thus shouldn't have this member.
Member 'slotbxml.sha256' (optional):
------------------------------------
One line, giving SHA-256 of SLOT B XML as hex string (lowercase a-f).
If there is no SLOT B XML (use emulator defaults), this member is absent.
SNES, BS-X (slotted or not) and SGB games do not have slot B, and thus shouldn't have this member.
Member 'sram.<id>' (optional, only present in savestates):
----------------------------------------------------------
Raw binary contents of SRAM with identifier '<id>'.
Current SRAM identifiers (based on bsnes v079 core):
* 'srm': Main cart SRAM.
* 'nec': DSP RAM on the cart (some DSP versions only).
* 'rtc': Cart Realtime Clock (if present on cart).
* 'bss': BS-X SRAM.
* 'bsp': BS-X PSRAM.
* 'slota.sts': Sufami Turbo Slot A Cart SRAM.
* 'slotb.sts': Sufami Turbo Slot B Cart SRAM.
Member 'moviesram.<id>' (required for SRAM start, otherwise must be absent):
----------------------------------------------------------------------------
Raw binary contents of SRAM with identifier '<id>'. To be loaded at start of movie.
Member 'savestate' (required for savestate, must be absent otherwise):
----------------------------------------------------------------------
Raw binary bsnes savestate.
Member 'moviestate' (required for savestate, must be absent otherwise):
-----------------------------------------------------------------------
Raw binary movie subsystem savestate.
Member 'hostmemory' (optional for savestate, must be absent otherwise):
-----------------------------------------------------------------------
Raw binary host memory contents.
Member 'input' (required):
--------------------------
Input track. One subframe per line (empty lines are skipped).
Each line consists of fields, split by '|' (no '|' at start of line unless first field is empty!).
The fields are in following order:
* Zero or more system fields
* Zero or more fields for port #1
* Zero or more fields for port #2
Inside each field, each button appears first in order. Then axis values appear, again in order. Each axis value must be
preceeded by at least one space/tab character. If reading button or axis value would go outside field, then the value
reads as released (for buttons) or as 0 (for axis).
For buttons, ' ', <tab> or '.' reads as released, anything else reads as pressed. Axis values are presented as signed
base-10 ASCII values (e.g. "-55" or "126"). If last encoded button/axis ends before field, the rest is ignored.
Note that axis values are after all button values regardless of ordering according to normal numeric order. E.g.
justifier, which has values 0 and 1 of type axis and values 2 and 3 of type button encodes the input in order 2, 3,
0, 1.
Fields for system:
------------------
One field:
* Button #0: Frame sync flag. Indicates that this is first subframe of frame. The length of movie in frames is the
number of subframes with frame sync flag set.
* Button #1: Reset flag. Ignored if frame sync flag is not set.
* Axis #2: Reset counter major: This value is multiplied by 10000 and added to reset counter.
* Axis #3: Reset counter minor: This value is added to reset counter.
If both reset and frame sync flags are set, then emulator starts a counter from reset counter value. While the
counter is nonzero and new frame hasn't started, CPU instruction is stepped and counter is decremented by 1. If
the counter reaches zero, RESET is issued.
Examples:
"..|" (subframe)
"F.|" (No reset, new frame)
"FR|" (reset instantly)
"FR 5 -1000|" (reset after 49000 cycles)
Fields for port of type 'none':
-------------------------------
No fields.
Example:
""
Fields for port of type 'gamepad':
----------------------------------
One field:
* Button #0: B
* Button #1: Y
* Button #2: Select
* Button #3: Start
* Button #4: Up
* Button #5: Down
* Button #6: Left
* Button #7: Right
* Button #8: A
* Button #9: X
* Button #10: L
* Button #11: R
Example:
"......lrA.LR|" (left+right, A, L+R).
Fields for port of type 'multitap':
-----------------------------------
Four fields, each field like gamepad field.
Example:
"........A|B|.........X|.Y|" (A on first controller, B on second, X on third and Y on fourth).
Fields for port of type 'mouse':
--------------------------------
One field:
* Axis #0: Relative X motion
* Axis #1: Relative Y motion
* Button #2: Left button
* Button #3: Right button
Example:
"L. -14|" (Mouse delta (-14,0), clicking left button).
Fields for port of type 'superscope':
-------------------------------------
One field:
* Axis #0: Absolute X position
* Axis #1: Absolute Y position
* Button #2: Trigger
* Button #3: Cursor
* Button #4: Turbo
* Button #5: Pause
Example:
"T... 32 54|" (firing at 32,54).
Fields for port of type 'justifier':
------------------------------------
One field:
* Axis #0: Absolute X position
* Axis #1: Absolute Y position
* Button #2: Trigger
* Button #3: Start
Example:
"T.. 64 125|" (firing at 64, 125).
Fields for port of type 'justfiers':
------------------------------------
Two fields of same type as in justifier.
Example:
"T. 64 125|.. 220 140" (firing at 64, 125 with first justifier and the second one is at 220, 140).

1043
memorymanip.cpp Normal file

File diff suppressed because it is too large Load diff

433
memorymanip.hpp Normal file
View file

@ -0,0 +1,433 @@
#ifndef _memorymanip__hpp__included__
#define _memorymanip__hpp__included__
#include "window.hpp"
#include <string>
#include <list>
#include <vector>
#include <cstdint>
#include <stdexcept>
/**
* \brief Information about region of memory
*
* This structure contains information about memory region.
*/
struct memory_region
{
/**
* \brief Name of region.
*
* This is name of region, mainly for debugging and showing to the user.
*/
std::string region_name;
/**
* \brief Base address of region.
*/
uint32_t baseaddr;
/**
* \brief Size of region in bytes.
*/
uint32_t size;
/**
* \brief Last valid address in this region.
*/
uint32_t lastaddr;
/**
* \brief True for ROM, false for RAM.
*/
bool readonly;
/**
* \brief Endianess of the region.
*
* If true, region uses host endian.
* If false, region uses SNES (big) endian.
*/
bool native_endian;
};
/**
* \brief Refresh cart memory mappings
*
* This function rereads cartridge memory map. Call after loading a new cartridge.
*
* \throws std::bad_alloc Not enough memory.
*/
void refresh_cart_mappings() throw(std::bad_alloc);
/**
* \brief Get listing of all regions
*
* This function returns a list of all known regions.
*
* \return All regions
* \throws std::bad_alloc Not enough memory.
*/
std::vector<struct memory_region> get_regions() throw(std::bad_alloc);
/**
* \brief Read byte from memory
*
* This function reads one byte from memory.
*
* \param addr The address to read.
* \return The byte read.
*/
uint8_t memory_read_byte(uint32_t addr) throw();
/**
* \brief Read word from memory
*
* This function reads two bytes from memory.
*
* \param addr The address to read.
* \return The word read.
*/
uint16_t memory_read_word(uint32_t addr) throw();
/**
* \brief Read dword from memory
*
* This function reads four bytes from memory.
*
* \param addr The address to read.
* \return The dword read.
*/
uint32_t memory_read_dword(uint32_t addr) throw();
/**
* \brief Read qword from memory
*
* This function reads eight bytes from memory.
*
* \param addr The address to read.
* \return The qword read.
*/
uint64_t memory_read_qword(uint32_t addr) throw();
/**
* \brief Write byte to memory
*
* This function writes one byte to memory.
*
* \param addr The address to write.
* \param data The value to write.
* \return true if the write succeeded.
*/
bool memory_write_byte(uint32_t addr, uint8_t data) throw();
/**
* \brief Write word to memory
*
* This function writes two bytes to memory.
*
* \param addr The address to write.
* \param data The value to write.
* \return true if the write succeeded.
*/
bool memory_write_word(uint32_t addr, uint16_t data) throw();
/**
* \brief Write dword to memory
*
* This function writes four bytes to memory.
*
* \param addr The address to write.
* \param data The value to write.
* \return true if the write succeeded.
*/
bool memory_write_dword(uint32_t addr, uint32_t data) throw();
/**
* \brief Write qword to memory
*
* This function writes eight bytes to memory.
*
* \param addr The address to write.
* \param data The value to write.
* \return true if the write succeeded.
*/
bool memory_write_qword(uint32_t addr, uint64_t data) throw();
/**
* \brief Memory search context
*
* Context for memory search. Each individual context is independent.
*/
class memorysearch
{
public:
/**
* \brief Create new memory search context.
*
* Creates a new memory search context with all addresses.
*
* \throws std::bad_alloc Not enough memory.
*/
memorysearch() throw(std::bad_alloc);
/**
* \brief Reset the context
*
* Reset the context so all addresses are candidates again.
*
* \throws std::bad_alloc Not enough memory.
*/
void reset() throw(std::bad_alloc);
/**
* \brief Search for address satisfying criteria
*
* This searches the memory space, leaving those addresses for which condition object returns true.
*
* \param obj The condition to search for.
*/
template<class T>
void search(const T& obj) throw();
/**
* \brief Search for byte with specified value
* \param value The value to search for
*/
void byte_value(uint8_t value) throw();
/**
* \brief Search for bytes that are signed less
*/
void byte_slt() throw();
/**
* \brief Search for bytes that are signed less or equal
*/
void byte_sle() throw();
/**
* \brief Search for bytes that are signed equal
*/
void byte_seq() throw();
/**
* \brief Search for bytes that are signed not equal
*/
void byte_sne() throw();
/**
* \brief Search for bytes that are signed greater or equal
*/
void byte_sge() throw();
/**
* \brief Search for bytes that are signed greater
*/
void byte_sgt() throw();
/**
* \brief Search for bytes that are unsigned less
*/
void byte_ult() throw();
/**
* \brief Search for bytes that are unsigned less or equal
*/
void byte_ule() throw();
/**
* \brief Search for bytes that are unsigned equal
*/
void byte_ueq() throw();
/**
* \brief Search for bytes that are unsigned not equal
*/
void byte_une() throw();
/**
* \brief Search for bytes that are unsigned greater or equal
*/
void byte_uge() throw();
/**
* \brief Search for bytes that are unsigned greater
*/
void byte_ugt() throw();
/**
* \brief Search for word with specified value
* \param value The value to search for
*/
void word_value(uint16_t value) throw();
/**
* \brief Search for words that are signed less
*/
void word_slt() throw();
/**
* \brief Search for words that are signed less or equal
*/
void word_sle() throw();
/**
* \brief Search for words that are signed equal
*/
void word_seq() throw();
/**
* \brief Search for words that are signed not equal
*/
void word_sne() throw();
/**
* \brief Search for words that are signed greater or equal
*/
void word_sge() throw();
/**
* \brief Search for words that are signed greater
*/
void word_sgt() throw();
/**
* \brief Search for words that are unsigned less
*/
void word_ult() throw();
/**
* \brief Search for words that are unsigned less or equal
*/
void word_ule() throw();
/**
* \brief Search for words that are unsigned equal
*/
void word_ueq() throw();
/**
* \brief Search for words that are unsigned not equal
*/
void word_une() throw();
/**
* \brief Search for words that are unsigned greater or equal
*/
void word_uge() throw();
/**
* \brief Search for words that are unsigned greater
*/
void word_ugt() throw();
/**
* \brief Search for dword with specified value
* \param value The value to search for
*/
void dword_value(uint32_t value) throw();
/**
* \brief Search for dwords that are signed less
*/
void dword_slt() throw();
/**
* \brief Search for dwords that are signed less or equal
*/
void dword_sle() throw();
/**
* \brief Search for dwords that are signed equal
*/
void dword_seq() throw();
/**
* \brief Search for dwords that are signed not equal
*/
void dword_sne() throw();
/**
* \brief Search for dwords that are signed greater or equal
*/
void dword_sge() throw();
/**
* \brief Search for dwords that are signed greater
*/
void dword_sgt() throw();
/**
* \brief Search for dwords that are unsigned less
*/
void dword_ult() throw();
/**
* \brief Search for dwords that are unsigned less or equal
*/
void dword_ule() throw();
/**
* \brief Search for dwords that are unsigned equal
*/
void dword_ueq() throw();
/**
* \brief Search for dwords that are unsigned not equal
*/
void dword_une() throw();
/**
* \brief Search for dwords that are unsigned greater or equal
*/
void dword_uge() throw();
/**
* \brief Search for dwords that are unsigned greater
*/
void dword_ugt() throw();
/**
* \brief Search for qword with specified value
* \param value The value to search for
*/
void qword_value(uint64_t value) throw();
/**
* \brief Search for qwords that are signed less
*/
void qword_slt() throw();
/**
* \brief Search for qwords that are signed less or equal
*/
void qword_sle() throw();
/**
* \brief Search for qwords that are signed equal
*/
void qword_seq() throw();
/**
* \brief Search for qwords that are signed not equal
*/
void qword_sne() throw();
/**
* \brief Search for qwords that are signed greater or equal
*/
void qword_sge() throw();
/**
* \brief Search for qwords that are signed greater
*/
void qword_sgt() throw();
/**
* \brief Search for qwords that are unsigned less
*/
void qword_ult() throw();
/**
* \brief Search for qwords that are unsigned less or equal
*/
void qword_ule() throw();
/**
* \brief Search for qwords that are unsigned equal
*/
void qword_ueq() throw();
/**
* \brief Search for qwords that are unsigned not equal
*/
void qword_une() throw();
/**
* \brief Search for qwords that are unsigned greater or equal
*/
void qword_uge() throw();
/**
* \brief Search for qwords that are unsigned greater
*/
void qword_ugt() throw();
/**
* \brief Get number of memory addresses that are still candidates
*
* This returns the number of memory addresses satisfying constraints so far.
*
* \return The number of candidates
*/
uint32_t get_candidate_count() throw();
/**
* \brief Get List of all candidate addresses
*
* Returns list of all candidates. This function isn't lazy, so be careful when calling with many candidates.
*
* \return Candidate address list
* \throws std::bad_alloc Not enough memory.
*/
std::list<uint32_t> get_candidates() throw(std::bad_alloc);
private:
std::vector<uint8_t> previous_content;
std::vector<uint64_t> still_in;
uint32_t candidates;
};
/**
* \brief Commands for memory manipulation
*/
bool memorymanip_command(const std::string& command, window* win) throw(std::bad_alloc, std::runtime_error);
#endif

220
memorywatch.cpp Normal file
View file

@ -0,0 +1,220 @@
#include "memorywatch.hpp"
#include "memorymanip.hpp"
#include <list>
#include <iomanip>
#include <stack>
#include <cmath>
#include <sstream>
std::string evaluate_watch(const std::string& expr) throw(std::bad_alloc)
{
std::stack<double> s;
size_t y;
std::string _expr = expr;
std::string t;
double a;
double b;
int d;
for(size_t i = 0; i < expr.length(); i++) {
switch(expr[i]) {
case 'C':
y = expr.find_first_of("z", i);
if(y > expr.length())
return "#syntax (noterm)";
t = _expr.substr(i + 1, y - i - 1);
if(t.length() > 2 && t.substr(0, 2) == "0x") {
char* end;
s.push(strtoull(t.c_str() + 2, &end, 16));
if(*end)
return "#syntax (badhex)";
} else {
char* end;
s.push(strtod(t.c_str(), &end));
if(*end)
return "#syntax (badnum)";
}
i = y;
break;
case 'R':
if(i + 1 == expr.length())
return "#syntax (noparam)";
d = expr[++i] - '0';
a = s.top();
s.pop();
b = pow(10, d);
s.push(floor(b * a + 0.5) / b);
break;
case 'a':
if(s.size() < 1)
return "#syntax (underflow)";
a = s.top();
s.pop();
s.push(atan(a));
break;
case 'A':
if(s.size() < 2)
return "#syntax (underflow)";
a = s.top();
s.pop();
b = s.top();
s.pop();
s.push(atan2(a, b));
break;
case 'c':
if(s.size() < 1)
return "#syntax (underflow)";
a = s.top();
s.pop();
s.push(cos(a));
break;
case 'r':
if(s.size() < 1)
return "#syntax (underflow)";
a = s.top();
s.pop();
s.push(sqrt(a));
break;
case 's':
if(s.size() < 1)
return "#syntax (underflow)";
a = s.top();
s.pop();
s.push(sin(a));
break;
case 't':
if(s.size() < 1)
return "#syntax (underflow)";
a = s.top();
s.pop();
s.push(tan(a));
break;
case 'u':
if(s.size() < 1)
return "#syntax (underflow)";
s.push(s.top());
break;
case 'p':
s.push(4 * atan(1));
break;
case '+':
if(s.size() < 2)
return "#syntax (underflow)";
a = s.top();
s.pop();
b = s.top();
s.pop();
s.push(a + b);
break;
case '-':
if(s.size() < 2)
return "#syntax (underflow)";
a = s.top();
s.pop();
b = s.top();
s.pop();
s.push(a - b);
break;
case '*':
if(s.size() < 2)
return "#syntax (underflow)";
a = s.top();
s.pop();
b = s.top();
s.pop();
s.push(a * b);
break;
case 'i':
if(s.size() < 2)
return "#syntax (underflow)";
a = s.top();
s.pop();
b = s.top();
s.pop();
s.push(a / b);
break;
case '/':
if(s.size() < 2)
return "#syntax (underflow)";
a = s.top();
s.pop();
b = s.top();
s.pop();
s.push(static_cast<int64_t>(a / b));
break;
case '%':
if(s.size() < 2)
return "#syntax (underflow)";
a = s.top();
s.pop();
b = s.top();
s.pop();
s.push(a - static_cast<int64_t>(a / b) * b);
break;
case 'b':
if(s.size() < 1)
return "#syntax (underflow)";
a = s.top();
s.pop();
s.push(static_cast<int8_t>(memory_read_byte(a)));
break;
case 'B':
if(s.size() < 1)
return "#syntax (underflow)";
a = s.top();
s.pop();
s.push(static_cast<uint8_t>(memory_read_byte(a)));
break;
case 'w':
if(s.size() < 1)
return "#syntax (underflow)";
a = s.top();
s.pop();
s.push(static_cast<int16_t>(memory_read_word(a)));
break;
case 'W':
if(s.size() < 1)
return "#syntax (underflow)";
a = s.top();
s.pop();
s.push(static_cast<uint16_t>(memory_read_word(a)));
break;
case 'd':
if(s.size() < 1)
return "#syntax (underflow)";
a = s.top();
s.pop();
s.push(static_cast<int32_t>(memory_read_dword(a)));
break;
case 'D':
if(s.size() < 1)
return "#syntax (underflow)";
a = s.top();
s.pop();
s.push(static_cast<uint32_t>(memory_read_dword(a)));
break;
case 'q':
if(s.size() < 1)
return "#syntax (underflow)";
a = s.top();
s.pop();
s.push(static_cast<int64_t>(memory_read_qword(a)));
break;
case 'Q':
if(s.size() < 1)
return "#syntax (underflow)";
a = s.top();
s.pop();
s.push(static_cast<uint64_t>(memory_read_qword(a)));
break;
default:
return "#syntax (illchar)";
}
}
if(s.empty())
return "#ERR";
else {
char buffer[512];
sprintf(buffer, "%f", s.top());
return buffer;
}
}

9
memorywatch.hpp Normal file
View file

@ -0,0 +1,9 @@
#ifndef _memorywatch__hpp__included__
#define _memorywatch__hpp__included__
#include <string>
#include <stdexcept>
std::string evaluate_watch(const std::string& expr) throw(std::bad_alloc);
#endif

304
misc.cpp Normal file
View file

@ -0,0 +1,304 @@
#include "lsnes.hpp"
#include "memorymanip.hpp"
#include "misc.hpp"
#include "rom.hpp"
#include <sstream>
#include <iostream>
#include <iomanip>
#include <fstream>
#include <string>
#include <vector>
#include <algorithm>
#include <ctime>
#include <cstdlib>
#include <cstring>
#include <boost/filesystem.hpp>
namespace
{
std::string rseed;
uint64_t rcounter = 0;
std::string get_random_hexstring_64(size_t index)
{
std::ostringstream str;
str << rseed << " " << time(NULL) << " " << (rcounter++) << " " << index;
std::string s = str.str();
std::vector<char> x;
x.resize(s.length());
std::copy(s.begin(), s.end(), x.begin());
return sha256::hash(reinterpret_cast<uint8_t*>(&x[0]), x.size());
}
std::string collect_identifying_information()
{
//TODO: Collect as much identifying information as possible.
std::ostringstream str;
time_t told = time(NULL);
time_t tnew;
uint64_t loops = 0;
uint64_t base = 0;
int cnt = 0;
while(cnt < 3) {
tnew = time(NULL);
if(tnew > told) {
told = tnew;
cnt++;
str << (loops - base) << " ";
base = loops;
}
loops++;
}
return str.str();
}
}
bool is_cmd_prefix(const std::string& haystack, const std::string& needle) throw()
{
size_t hlen = haystack.length();
size_t nlen = needle.length();
if(hlen < nlen)
return false;
for(size_t i = 0; i < nlen; i++)
if(haystack[i] != needle[i])
return false;
if(hlen == nlen)
return true;
return (haystack[nlen] == ' ' || haystack[nlen] == '\t');
}
std::string get_random_hexstring(size_t length) throw(std::bad_alloc)
{
std::string out;
for(size_t i = 0; i < length; i += 64)
out = out + get_random_hexstring_64(i);
return out.substr(0, length);
}
void set_random_seed(const std::string& seed) throw(std::bad_alloc)
{
std::ostringstream str;
str << seed.length() << " " << seed;
rseed = str.str();
}
void set_random_seed() throw(std::bad_alloc)
{
//Try /dev/urandom first.
{
std::ifstream r("/dev/urandom", std::ios::binary);
if(r.is_open()) {
char buf[64];
r.read(buf, 64);
std::string s(buf, 64);
set_random_seed(s);
return;
}
}
//Fall back to time.
std::ostringstream str;
str << collect_identifying_information() << " " << time(NULL);
set_random_seed(str.str());
}
//VERY dirty hack.
namespace foobar
{
#include <nall/sha256.hpp>
}
using foobar::nall::sha256_ctx;
using foobar::nall::sha256_init;
using foobar::nall::sha256_final;
using foobar::nall::sha256_hash;
using foobar::nall::sha256_chunk;
/**
* \brief Opaque internal state of SHA256
*/
struct sha256_opaque
{
/**
* \brief Opaque internal state structure of SHA256
*/
sha256_ctx shactx;
};
sha256::sha256() throw(std::bad_alloc)
{
opaque = new sha256_opaque();
finished = false;
sha256_init(&opaque->shactx);
}
sha256::~sha256() throw()
{
delete opaque;
}
void sha256::write(const uint8_t* data, size_t datalen) throw()
{
sha256_chunk(&opaque->shactx, data, datalen);
}
void sha256::read(uint8_t* hashout) throw()
{
if(!finished)
sha256_final(&opaque->shactx);
finished = true;
sha256_hash(&opaque->shactx, hashout);
}
std::string sha256::read() throw(std::bad_alloc)
{
uint8_t b[32];
read(b);
return sha256::tostring(b);
}
void sha256::hash(uint8_t* hashout, const uint8_t* data, size_t datalen) throw()
{
sha256 s;
s.write(data, datalen);
s.read(hashout);
}
std::string sha256::tostring(const uint8_t* hashout) throw(std::bad_alloc)
{
std::ostringstream str;
for(unsigned i = 0; i < 32; i++)
str << std::hex << std::setw(2) << std::setfill('0') << (unsigned)hashout[i];
return str.str();
}
struct loaded_rom load_rom_from_commandline(std::vector<std::string> cmdline, window* win) throw(std::bad_alloc,
std::runtime_error)
{
struct rom_files f;
try {
f = rom_files(cmdline, win);
f.resolve_relative();
} catch(std::bad_alloc& e) {
OOM_panic(win);
} catch(std::exception& e) {
throw std::runtime_error(std::string("Can't resolve ROM files: ") + e.what());
}
out(win) << "ROM type: " << gtype::tostring(f.rtype, f.region) << std::endl;
if(f.rom != "") out(win) << name_subrom(f.rtype, 0) << " file: '" << f.rom << "'" << std::endl;
if(f.rom_xml != "") out(win) << name_subrom(f.rtype, 1) << " file: '" << f.rom_xml << "'" << std::endl;
if(f.slota != "") out(win) << name_subrom(f.rtype, 2) << " file: '" << f.slota << "'" << std::endl;
if(f.slota_xml != "") out(win) << name_subrom(f.rtype, 3) << " file: '" << f.slota_xml << "'" << std::endl;
if(f.slotb != "") out(win) << name_subrom(f.rtype, 4) << " file: '" << f.slotb << "'" << std::endl;
if(f.slotb_xml != "") out(win) << name_subrom(f.rtype, 5) << " file: '" << f.slotb_xml << "'" << std::endl;
struct loaded_rom r;
try {
r = loaded_rom(f, win);
r.do_patch(cmdline, win);
} catch(std::bad_alloc& e) {
OOM_panic(win);
} catch(std::exception& e) {
throw std::runtime_error(std::string("Can't load ROM: ") + e.what());
}
std::string not_present = "N/A";
if(r.rom.valid) out(win) << name_subrom(f.rtype, 0) << " hash: " << r.rom.sha256 << std::endl;
if(r.rom_xml.valid) out(win) << name_subrom(f.rtype, 1) << " hash: " << r.rom_xml.sha256 << std::endl;
if(r.slota.valid) out(win) << name_subrom(f.rtype, 2) << " hash: " << r.slota.sha256 << std::endl;
if(r.slota_xml.valid) out(win) << name_subrom(f.rtype, 3) << " hash: " << r.slota_xml.sha256 << std::endl;
if(r.slotb.valid) out(win) << name_subrom(f.rtype, 4) << " hash: " << r.slotb.sha256 << std::endl;
if(r.slotb_xml.valid) out(win) << name_subrom(f.rtype, 5) << " hash: " << r.slotb_xml.sha256 << std::endl;
return r;
}
void dump_region_map(window* win) throw(std::bad_alloc)
{
std::vector<struct memory_region> regions = get_regions();
for(auto i = regions.begin(); i != regions.end(); ++i) {
char buf[256];
sprintf(buf, "Region: %08X-%08X %08X %s%c %s", i->baseaddr, i->lastaddr, i->size,
i->readonly ? "R-" : "RW", i->native_endian ? 'N' : 'B', i->region_name.c_str());
out(win) << buf << std::endl;
}
}
std::ostream& out(window* win) throw(std::bad_alloc)
{
if(!win)
return std::cout;
else
return win->out();
}
void fatal_error(window* win) throw()
{
if(win)
win->fatal_error();
std::cout << "PANIC: Fatal error, can't continue." << std::endl;
exit(1);
}
std::string get_config_path(window* win) throw(std::bad_alloc)
{
const char* tmp;
std::string basedir;
if((tmp = getenv("APPDATA"))) {
//If $APPDATA exists, it is the base directory
basedir = tmp;
} else if((tmp = getenv("XDG_CONFIG_HOME"))) {
//If $XDG_CONFIG_HOME exists, it is the base directory
basedir = tmp;
} else if((tmp = getenv("HOME"))) {
//If $HOME exists, the base directory is '.config' there.
basedir = std::string(tmp) + "/.config";
} else {
//Last chance: Return current directory.
return ".";
}
//Try to create 'lsnes'. If it exists (or is created) and is directory, great. Otherwise error out.
std::string lsnes_path = basedir + "/lsnes";
boost::filesystem::path p(lsnes_path);
if(!boost::filesystem::create_directories(p) && !boost::filesystem::is_directory(p)) {
out(win) << "FATAL: Can't create configuration directory '" << lsnes_path << "'" << std::endl;
fatal_error(win);
}
//Yes, this is racy, but portability is more important than being absolutely correct...
std::string tfile = lsnes_path + "/test";
remove(tfile.c_str());
FILE* x;
if(!(x = fopen(tfile.c_str(), "w+"))) {
out(win) << "FATAL: Configuration directory '" << lsnes_path << "' is not writable" << std::endl;
fatal_error(win);
}
fclose(x);
remove(tfile.c_str());
return lsnes_path;
}
extern const char* lsnesrc_file;
void create_lsnesrc(window* win)
{
std::string rcfile = get_config_path(win) + "/lsnes.rc";
std::ifstream x(rcfile.c_str());
if(x) {
x.close();
return;
}
std::ofstream y(rcfile.c_str());
if(!y) {
out(win) << "FATAL: lsnes.rc (" << rcfile << ") doesn't exist nor it can be created" << std::endl;
fatal_error(win);
}
y.write(lsnesrc_file, strlen(lsnesrc_file));
y.close();
}
void OOM_panic(window* win)
{
out(win) << "FATAL: Out of memory!" << std::endl;
fatal_error(win);
}
std::string bsnes_core_version;
std::string lsnes_version = "0-β0";

279
misc.hpp Normal file
View file

@ -0,0 +1,279 @@
#ifndef _misc__hpp__included__
#define _misc__hpp__included__
#include <string>
#include <vector>
#include "window.hpp"
#include <boost/lexical_cast.hpp>
/**
* \brief Recognize "foo", "foo (anything)" and "foo\t(anything)".
*
* \param haystack The string to search.
* \param needle The string to find.
* \return True if found, false if not.
*/
bool is_cmd_prefix(const std::string& haystack, const std::string& needle) throw();
/**
* \brief Get random hexes
*
* Get string of random hex characters of specified length.
*
* \param length The number of hex characters to return.
* \return The random hexadecimal string.
* \throws std::bad_alloc Not enough memory.
*/
std::string get_random_hexstring(size_t length) throw(std::bad_alloc);
/**
* \brief Set random seed
*
* This function sets the random seed to use.
*
* \param seed The value to use as seed.
* \throw std::bad_alloc Not enough memory.
*/
void set_random_seed(const std::string& seed) throw(std::bad_alloc);
/**
* \brief Set random seed to (hopefully) unique value
*
* This function sets the random seed to value that should only be used once. Note, the value is not necressarily
* crypto-secure, even if it is unique.
*
* \throw std::bad_alloc Not enough memory.
*/
void set_random_seed() throw(std::bad_alloc);
/**
* \brief Load a ROM.
*
* Given commandline arguments, load a ROM.
*
* \param cmdline The command line.
* \param win Handle to send the messages to.
* \return The loaded ROM set.
* \throws std::bad_alloc Not enough memory.
* \throws std::runtime_error Can't load the ROMset.
*/
struct loaded_rom load_rom_from_commandline(std::vector<std::string> cmdline, window* win) throw(std::bad_alloc,
std::runtime_error);
/**
* \brief Dump listing of regions to graphics system messages.
*
* \param win Handle to send the messages to.
* \throws std::bad_alloc Not enough memory.
*/
void dump_region_map(window* win) throw(std::bad_alloc);
/**
* \brief Return printing stream.
*
* \param win Handle to graphics system.
* \return Stream. If win is NULL, this is std::cout. Otherwise it is win->out().
* \throws std::bad_alloc Not enough memory.
*/
std::ostream& out(window* win) throw(std::bad_alloc);
/**
* \brief Fatal error.
*
* Fatal error. If win is non-NULL, it calls win->fatal_error(). Otherwise just immediately quits with error.
*/
void fatal_error(window* win) throw();
/**
* \brief Get path to config directory.
*
* \param win Graphics system handle.
* \return The config directory path.
* \throw std::bad_alloc Not enough memory.
*/
std::string get_config_path(window* win) throw(std::bad_alloc);
/**
* \brief Panic on OOM.
*/
void OOM_panic(window* win);
/**
* \brief Typeconvert string.
*/
template<typename T> inline T parse_value(const std::string& value) throw(std::bad_alloc, std::runtime_error)
{
try {
//Hack, since lexical_cast lets negative values slip through.
if(!std::numeric_limits<T>::is_signed && value.length() && value[0] == '-') {
throw std::runtime_error("Unsigned values can't be negative");
}
return boost::lexical_cast<T>(value);
} catch(std::exception& e) {
throw std::runtime_error("Can't parse value '" + value + "': " + e.what());
}
}
template<> inline std::string parse_value(const std::string& value) throw(std::bad_alloc, std::runtime_error)
{
return value;
}
void create_lsnesrc(window* win);
/**
* \brief Opaque internal state of SHA256
*/
struct sha256_opaque;
/**
* \brief SHA-256 function
*
* This class implements interface to SHA-256.
*/
class sha256
{
public:
/**
* \brief Create new SHA-256 context
*
* Creates new SHA-256 context, initially containing empty data.
*/
sha256() throw(std::bad_alloc);
/**
* \brief Destructor
*/
~sha256() throw();
/**
* \brief Append data to be hashed
*
* This function appends specified data to be hashed. Don't call after calling read().
*
* \param data The data to write.
* \param datalen The length of data written.
*/
void write(const uint8_t* data, size_t datalen) throw();
/**
* \brief Read the hash value
*
* Reads the hash of data written. Can be called multiple times, but after the first call, data can't be appended
* anymore.
*
* \param hashout 32-byte buffer to store the hash to.
*/
void read(uint8_t* hashout) throw();
/**
* \brief Read the hash value
*
* Similar to read(uint8_t*) but instead returns the hash as hexadecimal string.
*
* \return The hash in hex form.
* \throw std::bad_alloc Not enough memory.
*/
std::string read() throw(std::bad_alloc);
/**
* \brief Hash block of data.
*
* Hashes block of data.
*
* \param hashout 32-byte buffer to write the hash to.
* \param data The data to hash.
* \param datalen The length of data hashed.
*/
static void hash(uint8_t* hashout, const uint8_t* data, size_t datalen) throw();
/**
* \brief Hash block of data.
*
* Hashes block of data.
*
* \param hashout 32-byte buffer to write the hash to.
* \param data The data to hash.
*/
static void hash(uint8_t* hashout, const std::vector<uint8_t>& data) throw()
{
hash(hashout, &data[0], data.size());
}
/**
* \brief Hash block of data.
*
* Hashes block of data.
*
* \param hashout 32-byte buffer to write the hash to.
* \param data The data to hash.
*/
static void hash(uint8_t* hashout, const std::vector<char>& data) throw()
{
hash(hashout, reinterpret_cast<const uint8_t*>(&data[0]), data.size());
}
/**
* \brief Hash block of data.
*
* Hashes block of data.
*
* \param data The data to hash.
* \param datalen The length of data hashed.
* \return Hexadecimal hash of the data.
*/
static std::string hash(const uint8_t* data, size_t datalen) throw(std::bad_alloc)
{
uint8_t hashout[32];
hash(hashout, data, datalen);
return tostring(hashout);
}
/**
* \brief Hash block of data.
*
* Hashes block of data.
*
* \param data The data to hash.
* \return Hexadecimal hash of the data.
*/
static std::string hash(const std::vector<uint8_t>& data) throw(std::bad_alloc)
{
uint8_t hashout[32];
hash(hashout, &data[0], data.size());
return tostring(hashout);
}
/**
* \brief Hash block of data.
*
* Hashes block of data.
*
* \param data The data to hash.
* \return Hexadecimal hash of the data.
*/
static std::string hash(const std::vector<char>& data) throw(std::bad_alloc)
{
uint8_t hashout[32];
hash(hashout, reinterpret_cast<const uint8_t*>(&data[0]), data.size());
return tostring(hashout);
}
/**
* \brief Translate binary hash to hexadecimal hash
*
* Reads 32-byte binary hash from hashout and returns 64-hex hexadecimal hash.
*
* \param hashout The binary hash
* \return Hexadecimal hash
* \throws std::bad_alloc Not enough memory.
*/
static std::string tostring(const uint8_t* hashout) throw(std::bad_alloc);
private:
sha256(const sha256& x) throw();
sha256& operator=(const sha256& x) throw();
sha256_opaque* opaque;
bool finished;
};
#endif

693
movie.cpp Normal file
View file

@ -0,0 +1,693 @@
#include "lsnes.hpp"
#include "movie.hpp"
#include "rom.hpp"
#include "misc.hpp"
#include <stdexcept>
#include <cassert>
#include <cstring>
#include <fstream>
#define FLAG_SYNC CONTROL_FRAME_SYNC
std::string global_rerecord_count = "0";
//std::ofstream debuglog("movie-debugging-log", std::ios::out | std::ios::app);
namespace
{
void hash_string(uint8_t* res, const std::string& s) throw(std::bad_alloc)
{
std::vector<char> t;
t.resize(s.length());
std::copy(s.begin(), s.end(), t.begin());
sha256::hash(res, t);
}
uint8_t* enlarge(std::vector<uint8_t>& v, size_t amount) throw(std::bad_alloc)
{
size_t i = v.size();
v.resize(i + amount);
return &v[i];
}
inline void write64(uint8_t* buffer, uint64_t value) throw()
{
buffer[0] = value >> 56;
buffer[1] = value >> 48;
buffer[2] = value >> 40;
buffer[3] = value >> 32;
buffer[4] = value >> 24;
buffer[5] = value >> 16;
buffer[6] = value >> 8;
buffer[7] = value;
}
inline void write32(uint8_t* buffer, uint32_t value) throw()
{
buffer[0] = value >> 24;
buffer[1] = value >> 16;
buffer[2] = value >> 8;
buffer[3] = value;
}
inline uint32_t read32(const uint8_t* buffer) throw()
{
return (static_cast<uint32_t>(buffer[0]) << 24) |
(static_cast<uint32_t>(buffer[1]) << 16) |
(static_cast<uint32_t>(buffer[2]) << 8) |
(static_cast<uint32_t>(buffer[3]));
}
inline uint64_t read64(const uint8_t* buffer) throw()
{
return (static_cast<uint64_t>(buffer[0]) << 56) |
(static_cast<uint64_t>(buffer[1]) << 48) |
(static_cast<uint64_t>(buffer[2]) << 40) |
(static_cast<uint64_t>(buffer[3]) << 32) |
(static_cast<uint64_t>(buffer[4]) << 24) |
(static_cast<uint64_t>(buffer[5]) << 16) |
(static_cast<uint64_t>(buffer[6]) << 8) |
(static_cast<uint64_t>(buffer[7]));
}
inline void write16s(uint8_t* buffer, int16_t x) throw()
{
uint16_t y = static_cast<uint16_t>(x);
buffer[0] = y >> 8;
buffer[1] = y;
}
void hash_subframe(sha256& ctx, const controls_t& ctrl) throw()
{
uint8_t buf[2 * TOTAL_CONTROLS];
for(unsigned i = 0; i < TOTAL_CONTROLS; i++)
write16s(buf + 2 * i, ctrl(i));
ctx.write(buf, 2 * TOTAL_CONTROLS);
}
//Hashes frame and returns starting subframe of next frame.
uint64_t hash_frame(sha256& ctx, std::vector<controls_t>& input, uint64_t first_subframe,
uint32_t bound) throw()
{
if(!bound) {
//Ignore this frame completely.
if(first_subframe >= input.size())
return first_subframe;
first_subframe++;
while(first_subframe < input.size() && !input[first_subframe](CONTROL_FRAME_SYNC))
first_subframe++;
return first_subframe;
}
if(first_subframe >= input.size()) {
//Hash an empty frame.
hash_subframe(ctx, controls_t(true));
return first_subframe;
}
uint64_t subframes_to_hash = 1;
uint64_t last_differing = 1;
uint64_t next;
controls_t prev = input[first_subframe];
prev(CONTROL_FRAME_SYNC) = 0;
while(first_subframe + subframes_to_hash < input.size() && !input[first_subframe + subframes_to_hash]
(CONTROL_FRAME_SYNC)) {
if(!(input[first_subframe + subframes_to_hash] == prev))
last_differing = subframes_to_hash + 1;
prev = input[first_subframe + subframes_to_hash];
subframes_to_hash++;
}
next = first_subframe + subframes_to_hash;
subframes_to_hash = last_differing;
for(uint64_t i = 0; i < subframes_to_hash && i < bound; i++)
hash_subframe(ctx, input[first_subframe + i]);
return next;
}
void hash_movie(uint8_t* res, uint64_t current_frame, uint32_t* pollcounters,
std::vector<controls_t>& input) throw(std::bad_alloc)
{
sha256 ctx;
//If current_frame == 0, hash is empty.
if(!current_frame) {
ctx.read(res);
return;
}
//Hash past frames.
uint64_t current_subframe = 0;
for(uint64_t i = 1; i < current_frame; i++)
current_subframe = hash_frame(ctx, input, current_subframe, 0x7FFFFFFF);
//Current frame is special.
for(size_t i = 0; i < TOTAL_CONTROLS; i++) {
uint32_t polls = pollcounters[i] & 0x7FFFFFFF;
uint32_t last_seen = 0;
for(size_t j = 0; j < polls; j++) {
if(current_subframe + j < input.size() && !input[current_subframe + j]
(CONTROL_FRAME_SYNC))
last_seen = input[current_subframe + j](i);
uint8_t buf[2];
write16s(buf, last_seen);
ctx.write(buf, 2);
}
}
ctx.read(res);
}
}
void movie::set_all_DRDY() throw()
{
for(size_t i = 0; i < TOTAL_CONTROLS; i++)
pollcounters[i] |= 0x80000000UL;
}
std::string movie::rerecord_count() throw(std::bad_alloc)
{
return rerecords;
}
void movie::rerecord_count(const std::string& count) throw(std::bad_alloc)
{
rerecords = count;
}
std::string movie::project_id() throw(std::bad_alloc)
{
return _project_id;
}
void movie::project_id(const std::string& id) throw(std::bad_alloc)
{
_project_id = id;
}
bool movie::readonly_mode() throw()
{
return readonly;
}
short movie::next_input(unsigned port, unsigned controller, unsigned index) throw(std::bad_alloc, std::logic_error)
{
return next_input(ccindex2(port, controller, index));
}
void movie::set_controls(controls_t controls) throw()
{
current_controls = controls;
}
uint32_t movie::count_changes(uint64_t first_subframe)
{
if(first_subframe >= movie_data.size())
return 0;
uint32_t ret = 1;
while(first_subframe + ret < movie_data.size() && !movie_data[first_subframe + ret](CONTROL_FRAME_SYNC))
ret++;
return ret;
}
controls_t movie::get_controls() throw()
{
if(!readonly)
return current_controls;
controls_t c;
//Before the beginning? Somebody screwed up (but return released / neutral anyway)...
if(current_frame == 0)
return c;
//Otherwise find the last valid frame of input.
uint32_t changes = count_changes(current_frame_first_subframe);
if(!changes)
return c; //End of movie.
for(size_t i = 0; i < TOTAL_CONTROLS; i++) {
uint32_t polls = pollcounters[i] & 0x7FFFFFFF;
uint32_t index = (changes > polls) ? polls : changes - 1;
c(i) = movie_data[current_frame_first_subframe + index](i);
}
return c;
}
uint64_t movie::get_current_frame() throw()
{
return current_frame;
}
uint64_t movie::get_lag_frames() throw()
{
return lag_frames;
}
uint64_t movie::get_frame_count() throw()
{
return frames_in_movie;
}
void movie::next_frame() throw(std::bad_alloc)
{
//If all poll counters are zero for all real controls, this frame is lag.
bool this_frame_lag = true;
for(size_t i = MAX_SYSTEM_CONTROLS; i < TOTAL_CONTROLS; i++)
if(pollcounters[i] & 0x7FFFFFFF)
this_frame_lag = false;
//Hack: Reset frames must not be considered lagged, so we abuse pollcounter bit for reset to mark those.
if(pollcounters[CONTROL_SYSTEM_RESET] & 0x7FFFFFFF)
this_frame_lag = false;
//Oh, frame 0 must not be considered lag.
if(current_frame && this_frame_lag) {
lag_frames++;
//debuglog << "Frame " << current_frame << " is lag" << std::endl << std::flush;
if(!readonly) {
//If in read-write mode, write a dummy record for the frame. Force sync flag.
//As index should be movie_data.size(), it is correct afterwards.
controls_t c = current_controls;
c(CONTROL_FRAME_SYNC) = 1;
movie_data.push_back(c);
frames_in_movie++;
}
}
//Reset the poll counters and DRDY flags.
for(unsigned i = 0; i < TOTAL_CONTROLS; i++)
pollcounters[i] = 0;
//Increment the current frame counter and subframe counter. Note that first subframe is undefined for
//frame 0 and 0 for frame 1.
if(current_frame)
current_frame_first_subframe = current_frame_first_subframe +
count_changes(current_frame_first_subframe);
else
current_frame_first_subframe = 0;
current_frame++;
}
bool movie::get_DRDY(unsigned controlindex) throw(std::logic_error)
{
if(controlindex >= TOTAL_CONTROLS)
throw std::logic_error("movie::get_DRDY: Bad index");
return ((pollcounters[controlindex] & 0x80000000UL) != 0);
}
bool movie::get_DRDY(unsigned port, unsigned controller, unsigned index) throw(std::logic_error)
{
return get_DRDY(ccindex2(port, controller, index));
}
short movie::next_input(unsigned controlindex) throw(std::bad_alloc, std::logic_error)
{
//Check validity of index.
if(controlindex == FLAG_SYNC)
return 0;
if(controlindex >= TOTAL_CONTROLS)
throw std::logic_error("movie::next_input: Invalid control index");
//Clear the DRDY flag.
pollcounters[controlindex] &= 0x7FFFFFFF;
if(readonly) {
//In readonly mode...
//If at the end of the movie, return released / neutral (but also record the poll)...
if(current_frame_first_subframe >= movie_data.size()) {
pollcounters[controlindex]++;
return 0;
}
//Before the beginning? Somebody screwed up (but return released / neutral anyway)...
if(current_frame == 0)
return 0;
//Otherwise find the last valid frame of input.
uint32_t changes = count_changes(current_frame_first_subframe);
uint32_t polls = (pollcounters[controlindex]++) & 0x7FFFFFFF;
uint32_t index = (changes > polls) ? polls : changes - 1;
//debuglog << "Frame=" << current_frame << " Subframe=" << polls << " control=" << controlindex << " value=" << movie_data[current_frame_first_subframe + index](controlindex) << " fetchrow=" << current_frame_first_subframe + index << std::endl << std::flush;
return movie_data[current_frame_first_subframe + index](controlindex);
} else {
//Readwrite mode.
//Before the beginning? Somebody screwed up (but return released / neutral anyway)...
//Also, frame 0 must not be added to movie file.
if(current_frame == 0)
return 0;
//If at movie end, insert complete input with frame sync set (this is the first subframe).
if(current_frame_first_subframe >= movie_data.size()) {
controls_t c = current_controls;
c(CONTROL_FRAME_SYNC) = 1;
movie_data.push_back(c);
//current_frame_first_subframe should be movie_data.size(), so it is right.
pollcounters[controlindex]++;
frames_in_movie++;
assert(pollcounters[controlindex] == 1);
//debuglog << "Frame=" << current_frame << " Subframe=" << (pollcounters[controlindex] - 1) << " control=" << controlindex << " value=" << movie_data[current_frame_first_subframe](controlindex) << " fetchrow=" << current_frame_first_subframe << std::endl << std::flush;
return movie_data[current_frame_first_subframe](controlindex);
}
short new_value = current_controls(controlindex);
//Fortunately, we know this frame is the last one in movie_data.
uint32_t pollcounter = pollcounters[controlindex] & 0x7FFFFFFF;
uint64_t fetchrow = movie_data.size() - 1;
if(current_frame_first_subframe + pollcounter < movie_data.size()) {
//The index is within existing size. Change the value and propagate to all subsequent
//subframes.
for(uint64_t i = current_frame_first_subframe + pollcounter; i < movie_data.size(); i++)
movie_data[i](controlindex) = new_value;
fetchrow = current_frame_first_subframe + pollcounter;
} else if(new_value != movie_data[movie_data.size() - 1](controlindex)) {
//The index is not within existing size and value does not match. We need to create a new
//subframes(s), copying the last subframe.
while(current_frame_first_subframe + pollcounter >= movie_data.size()) {
controls_t c = movie_data[movie_data.size() - 1];
c(CONTROL_FRAME_SYNC) = 0;
movie_data.push_back(c);
}
fetchrow = current_frame_first_subframe + pollcounter;
movie_data[current_frame_first_subframe + pollcounter](controlindex) = new_value;
}
pollcounters[controlindex]++;
//debuglog << "Frame=" << current_frame << " Subframe=" << (pollcounters[controlindex] - 1) << " control=" << controlindex << " value=" << new_value << " fetchrow=" << fetchrow << std::endl << std::flush;
return new_value;
}
}
movie::movie() throw(std::bad_alloc)
{
readonly = false;
rerecords = "0";
_project_id = "";
current_frame = 0;
frames_in_movie = 0;
current_frame_first_subframe = 0;
for(unsigned i = 0; i < TOTAL_CONTROLS; i++) {
pollcounters[i] = 0;
current_controls(i) = 0;
}
lag_frames = 0;
clear_caches();
}
void movie::load(const std::string& rerecs, const std::string& project_id, const std::vector<controls_t>& input)
throw(std::bad_alloc, std::runtime_error)
{
if(input.size() > 0 && !input[0](CONTROL_FRAME_SYNC))
throw std::runtime_error("First subframe MUST have frame sync flag set");
clear_caches();
frames_in_movie = 0;
for(auto i = input.begin(); i != input.end(); i++)
if((*i)(CONTROL_FRAME_SYNC))
frames_in_movie++;
readonly = true;
rerecords = rerecs;
_project_id = project_id;
current_frame = 0;
current_frame_first_subframe = 0;
for(unsigned i = 0; i < TOTAL_CONTROLS; i++) {
pollcounters[i] = 0;
}
lag_frames = 0;
movie_data = input;
}
std::vector<controls_t> movie::save() throw(std::bad_alloc)
{
return movie_data;
}
void movie::commit_reset(long delay) throw(std::bad_alloc)
{
if(readonly || delay < 0)
return;
//If this frame is lagged, we need to write entry for it.
bool this_frame_lag = true;
for(size_t i = MAX_SYSTEM_CONTROLS; i < TOTAL_CONTROLS; i++)
if(pollcounters[i] & 0x7FFFFFFF)
this_frame_lag = false;
//Hack: Reset frames must not be considered lagged, so we abuse pollcounter bit for reset to mark those.
if(pollcounters[CONTROL_SYSTEM_RESET] & 0x7FFFFFFF)
this_frame_lag = false;
if(this_frame_lag) {
controls_t c = current_controls;
c(CONTROL_FRAME_SYNC) = 1;
movie_data.push_back(c);
frames_in_movie++;
//Current_frame_first_subframe is correct.
}
//Also set poll counters on reset cycles to avoid special cases elsewhere.
pollcounters[CONTROL_SYSTEM_RESET] = 1;
pollcounters[CONTROL_SYSTEM_RESET_CYCLES_HI] = 1;
pollcounters[CONTROL_SYSTEM_RESET_CYCLES_LO] = 1;
//Current frame is always last in rw mode.
movie_data[current_frame_first_subframe](CONTROL_SYSTEM_RESET) = 1;
movie_data[current_frame_first_subframe](CONTROL_SYSTEM_RESET_CYCLES_HI) = delay / 10000;
movie_data[current_frame_first_subframe](CONTROL_SYSTEM_RESET_CYCLES_LO) = delay % 10000;
}
unsigned movie::next_poll_number()
{
unsigned max = 0;
for(unsigned i = 0; i < TOTAL_CONTROLS; i++)
if(max < (pollcounters[i] & 0x7FFFFFFF))
max = (pollcounters[i] & 0x7FFFFFFF);
return max + 1;
}
void movie::readonly_mode(bool enable) throw(std::bad_alloc)
{
bool was_in_readonly = readonly;
readonly = enable;
if(was_in_readonly && !readonly) {
clear_caches();
//Transitioning to readwrite mode, we have to adjust the length of the movie data.
if(current_frame == 0) {
//WTF... At before first frame. Blank the entiere movie.
movie_data.clear();
return;
}
//Fun special case: Current frame is not in movie (current_frame_first_subframe >= movie_data.size()).
//In this case, we have to extend the movie data.
if(current_frame_first_subframe >= movie_data.size()) {
//Yes, this will insert one extra frame... But we will lose it later if it is not needed.
while(frames_in_movie < current_frame) {
controls_t c(true);
movie_data.push_back(c);
frames_in_movie++;
}
current_frame_first_subframe = movie_data.size() - 1;
}
//We have to take the part up to furthest currently readable subframe. Also, we need to propagate
//forward values with smaller poll counters.
uint64_t next_frame_first_subframe = current_frame_first_subframe +
count_changes(current_frame_first_subframe);
uint64_t max_readable_subframes = current_frame_first_subframe;
for(size_t i = 0; i < TOTAL_CONTROLS; i++) {
uint32_t polls = pollcounters[i] & 0x7FFFFFFF;
if(current_frame_first_subframe + polls >= next_frame_first_subframe)
max_readable_subframes = next_frame_first_subframe;
else if(current_frame_first_subframe + polls > max_readable_subframes)
max_readable_subframes = current_frame_first_subframe + polls;
}
movie_data.resize(max_readable_subframes);
next_frame_first_subframe = max_readable_subframes;
for(size_t i = 1; i < TOTAL_CONTROLS; i++) {
uint32_t polls = pollcounters[i] & 0x7FFFFFFF;
if(!polls)
polls = 1;
for(uint64_t j = current_frame_first_subframe + polls; j < next_frame_first_subframe; j++)
movie_data[j](i) = movie_data[current_frame_first_subframe + polls - 1](i);
}
frames_in_movie = current_frame - ((current_frame_first_subframe >= movie_data.size()) ? 1 : 0);
}
}
//Save state of movie code.
std::vector<uint8_t> movie::save_state() throw(std::bad_alloc)
{
//debuglog << "--------------------------------------------" << std::endl;
//debuglog << "SAVING STATE:" << std::endl;
std::vector<uint8_t> ret;
hash_string(enlarge(ret, 32), _project_id);
write64(enlarge(ret, 8), current_frame);
//debuglog << "Current frame is " << current_frame << std::endl;
//debuglog << "Poll counters: ";
for(unsigned i = 0; i < TOTAL_CONTROLS; i++) {
uint32_t v = pollcounters[i];
//debuglog << v;
if(v & 0x80000000UL) {
//debuglog << "R ";
} else
;//debuglog << " ";
write32(enlarge(ret, 4), v);
}
//debuglog << std::endl;
{
uint64_t v = lag_frames;
//debuglog << "Lag frame count: " << lag_frames << std::endl;
write64(enlarge(ret, 8), v);
}
hash_movie(enlarge(ret, 32), current_frame, pollcounters, movie_data);
uint8_t hash[32];
sha256::hash(hash, ret);
memcpy(enlarge(ret, 32), hash, 32);
//debuglog << "--------------------------------------------" << std::endl;
//debuglog.flush();
return ret;
}
//Restore state of movie code. Throws if state is invalid. Flag gives new state of readonly flag.
size_t movie::restore_state(const std::vector<uint8_t>& state, bool ro) throw(std::bad_alloc, std::runtime_error)
{
//Check the whole-data checksum.
size_t ptr = 0;
uint8_t tmp[32];
if(state.size() != 112+4*TOTAL_CONTROLS)
throw std::runtime_error("Movie save data corrupt: Wrong length");
sha256::hash(tmp, &state[0], state.size() - 32);
if(memcmp(tmp, &state[state.size() - 32], 32))
throw std::runtime_error("Movie save data corrupt: Checksum does not match");
//debuglog << "--------------------------------------------" << std::endl;
//debuglog << "RESTORING STATE:" << std::endl;
//Check project id.
hash_string(tmp, _project_id);
if(memcmp(tmp, &state[ptr], 32))
throw std::runtime_error("Save is not from this movie");
ptr += 32;
//Read current frame.
uint64_t tmp_curframe = read64(&state[ptr]);
uint64_t tmp_firstsubframe = 0;
for(uint64_t i = 1; i < tmp_curframe; i++)
tmp_firstsubframe = tmp_firstsubframe + count_changes(tmp_firstsubframe);
ptr += 8;
//Read poll counters and drdy flags.
uint32_t tmp_pollcount[TOTAL_CONTROLS];
for(unsigned i = 0; i < TOTAL_CONTROLS; i++) {
uint32_t v = read32(&state[ptr]);
ptr += 4;
tmp_pollcount[i] = v;
}
uint64_t tmp_lagframes = read64(&state[ptr]);
tmp_lagframes &= 0x7FFFFFFFFFFFFFFFULL;
ptr += 8;
hash_movie(tmp, tmp_curframe, tmp_pollcount, movie_data);
if(memcmp(tmp, &state[ptr], 32))
throw std::runtime_error("Save is not from this movie");
//Ok, all checks pass. Copy the state. Do this in readonly mode so we can use normal routine to switch
//to readwrite mode.
readonly = true;
current_frame = tmp_curframe;
current_frame_first_subframe = tmp_firstsubframe;
memcpy(pollcounters, tmp_pollcount, sizeof(tmp_pollcount));
lag_frames = tmp_lagframes;
//debuglog << "Current frame is " << current_frame << std::endl;
//debuglog << "Poll counters: ";
for(unsigned i = 0; i < TOTAL_CONTROLS; i++) {
uint32_t v = pollcounters[i];
//debuglog << v;
if(v & 0x80000000UL) {
//debuglog << "R ";
} else
;//debuglog << " ";
}
//debuglog << std::endl;
{
uint64_t v = lag_frames;
//debuglog << "Lag frame count: " << lag_frames << std::endl;
}
//debuglog << "--------------------------------------------" << std::endl;
//debuglog.flush();
//Move to readwrite mode if needed.
readonly_mode(ro);
return 0;
}
long movie::get_reset_status() throw()
{
if(current_frame == 0 || current_frame_first_subframe >= movie_data.size())
return -1; //No resets out of range.
if(!movie_data[current_frame_first_subframe](CONTROL_SYSTEM_RESET))
return -1; //Not a reset.
//Also set poll counters on reset cycles to avoid special cases elsewhere.
pollcounters[CONTROL_SYSTEM_RESET] = 1;
pollcounters[CONTROL_SYSTEM_RESET_CYCLES_HI] = 1;
pollcounters[CONTROL_SYSTEM_RESET_CYCLES_LO] = 1;
long hi = movie_data[current_frame_first_subframe](CONTROL_SYSTEM_RESET_CYCLES_HI);
long lo = movie_data[current_frame_first_subframe](CONTROL_SYSTEM_RESET_CYCLES_LO);
return hi * 10000 + lo;
}
uint64_t movie::frame_subframes(uint64_t frame)
{
if(frame < cached_frame)
clear_caches();
uint64_t p = cached_subframe;
for(uint64_t i = cached_frame; i < frame; i++)
p = p + count_changes(p);
cached_frame = frame;
cached_subframe = p;
return count_changes(p);
}
void movie::clear_caches()
{
cached_frame = 1;
cached_subframe = 0;
}
controls_t movie::read_subframe(uint64_t frame, uint64_t subframe)
{
if(frame < cached_frame)
clear_caches();
uint64_t p = cached_subframe;
for(uint64_t i = cached_frame; i < frame; i++)
p = p + count_changes(p);
cached_frame = frame;
cached_subframe = p;
uint64_t max = count_changes(p);
if(!max)
return controls_t(true);
if(max <= subframe)
subframe = max - 1;
return movie_data[p + subframe];
}
movie_logic::movie_logic(movie& m) throw()
: mov(m)
{
}
movie_logic::~movie_logic() throw()
{
}
movie& movie_logic::get_movie() throw()
{
return mov;
}
long movie_logic::new_frame_starting(bool dont_poll) throw(std::bad_alloc, std::runtime_error)
{
mov.next_frame();
controls_t c = update_controls(false);
if(!mov.readonly_mode()) {
mov.set_controls(c);
if(dont_poll)
mov.set_all_DRDY();
if(c(CONTROL_SYSTEM_RESET)) {
long hi = c(CONTROL_SYSTEM_RESET_CYCLES_HI);
long lo = c(CONTROL_SYSTEM_RESET_CYCLES_LO);
mov.commit_reset(hi * 10000 + lo);
}
}
return mov.get_reset_status();
}
short movie_logic::input_poll(bool port, unsigned dev, unsigned id) throw(std::bad_alloc, std::runtime_error)
{
if(dev >= MAX_CONTROLLERS_PER_PORT || id >= CONTROLLER_CONTROLS)
return 0;
if(!mov.get_DRDY(port ? 1 : 0, dev, id)) {
mov.set_controls(update_controls(true));
mov.set_all_DRDY();
}
int16_t in = mov.next_input(port ? 1 : 0, dev, id);
//debuglog << "BSNES asking for (" << port << "," << dev << "," << id << ") (frame " << mov.get_current_frame()
// << ") giving " << in << std::endl;
//debuglog.flush();
return in;
}

331
movie.hpp Normal file
View file

@ -0,0 +1,331 @@
#ifndef _movie__hpp__included__
#define _movie__hpp__included__
#include <string>
#include <cstdint>
#include <stdexcept>
#include "controllerdata.hpp"
/**
* \brief Movie being played back or recorded
*/
class movie
{
public:
/**
* \brief Construct new empty movie.
* \throws std::bad_alloc Not enough memory.
*/
movie() throw(std::bad_alloc);
/**
* \brief Is the movie in readonly mode?
*
* \return True if in read-only mode, false if in read-write mode.
*/
bool readonly_mode() throw();
/**
* \brief Switch between modes
*
* Switches movie to read-only or read-write mode. If switching to read-write mode, the movie is truncated.
*
* \param enable If true, switch to read-only mode, else to read-write mode.
* \throws std::bad_alloc Not enough memory.
*/
void readonly_mode(bool enable) throw(std::bad_alloc);
/**
* \brief Get movie rerecord count
*
* Returns the movie rerecord count (this is not the same thing as global rerecord count).
*
* \return The movie rerecord count
* \throws std::bad_alloc Not enough memory.
*/
std::string rerecord_count() throw(std::bad_alloc);
/**
* \brief Set movie rerecord count
*
* Sets the movie rerecord count (this is not the same thing as global rerecord count).
*
* \param count The new rerecord count
* \throws std::bad_alloc Not enough memory.
*/
void rerecord_count(const std::string& count) throw(std::bad_alloc);
/**
* \brief Read project ID
* \return The project ID
* \throws std::bad_alloc Not enough memory.
*/
std::string project_id() throw(std::bad_alloc);
/**
* \brief Set project ID
* \param id New project ID.
* \throws std::bad_alloc Not enough memory.
*/
void project_id(const std::string& id) throw(std::bad_alloc);
/**
* \brief Get number of frames in movie
* \return The number of frames.
*/
uint64_t get_frame_count() throw();
/**
* \brief Get number of currnet frame in movie
*
* The first frame in movie is 1. 0 is "before first frame" value.
*
* \return The number of frame
*/
uint64_t get_current_frame() throw();
/**
* \brief Get number of lag frames so far
* \return The number of lag frames.
*/
uint64_t get_lag_frames() throw();
/**
* \brief Advance to next frame.
*
* This function advances to next frame in movie, discarding subframes not used. If the frame is lag frame, it is
* counted as lag frame and subframe entry for it is made (if in readwrite mode).
*
* \throw std::bad_alloc Not enough memory.
*/
void next_frame() throw(std::bad_alloc);
/**
* \brief Get data ready flag for control index
*
* Reads the data ready flag. On new frame, all data ready flags are unset. On reading control, its data ready
* flag is unset.
*
* \param controlindex The index of control to read it for.
* \return The read value.
* \throws std::logic_error Invalid control index.
*/
bool get_DRDY(unsigned controlindex) throw(std::logic_error);
/**
* \brief Get data ready flag for given (port,controller,index) index
*
* Reads the data ready flag. On new frame, all data ready flags are unset. On reading control, its data ready
* flag is unset.
*
* This differs from get_DRDY(unsigned) in that this takes (port, controller,index) tuple.
*
* \param port The port controller is connected to (0 or 1)
* \param controller The controller number within port (0 to 3)
* \param index The index of control in controller (0 to 11)
* \return The read value.
* \throws std::logic_error Invalid control index.
*/
bool get_DRDY(unsigned port, unsigned controller, unsigned index) throw(std::logic_error);
/**
* \brief Set all data ready flags
*/
void set_all_DRDY() throw();
/**
* \brief Poll next value for given control index
*
* Poll a control. Note that index 0 (sync flag) always reads as released.
*
* \param controlindex The index
* \return The read value
* \throws std::bad_alloc Not enough memory.
* \throws std::logic_error Invalid control index or before movie start.
*/
short next_input(unsigned controlindex) throw(std::bad_alloc, std::logic_error);
/**
* \brief Poll next value for given (port, controller, index) index
*
* Poll a control.
*
* \param port The port controller is connected to (0 or 1)
* \param controller The controller number within port (0 to 3)
* \param index The index of control in controller (0 to 11)
* \return The read value
* \throws std::bad_alloc Not enough memory.
* \throws std::logic_error Invalid port, controller or index or before movie start.
*/
short next_input(unsigned port, unsigned controller, unsigned index) throw(std::bad_alloc, std::logic_error);
/**
* \brief Set values of current controls
*
* Set current control values. These are read in readwrite mode.
*
* \param controls The new controls.
*/
void set_controls(controls_t controls) throw();
/**
* \brief Get values of current controls
*
* Get current control values in effect.
*
* \return Controls
*/
controls_t get_controls() throw();
/**
* \brief Load a movie.
*
* Loads a movie plus some other parameters. The playback pointer is positioned to start of movie and readonly
* mode is enabled.
*
* \param rerecs Movie rerecord count.
* \param project_id Project ID of movie.
* \param input The input track.
* \throws std::bad_alloc Not enough memory.
* \throws std::runtime_error Bad movie data.
*/
void load(const std::string& rerecs, const std::string& project_id, const std::vector<controls_t>& input)
throw(std::bad_alloc, std::runtime_error);
/**
* \brief Save a movie.
*
* Saves the movie data.
*
* \return The movie data.
* \throws std::bad_alloc Not enough memory.
*/
std::vector<controls_t> save() throw(std::bad_alloc);
/**
* \brief Save the state of movie code
*
* This method serializes the state of movie code.
*
* \return The serialized state.
* \throws std::bad_alloc Not enough memory.
*/
std::vector<uint8_t> save_state() throw(std::bad_alloc);
/**
* \brief Restore the state of movie code
*
* Given previous serialized state from this movie, restore the state.
*
* \param state The state to restore.
* \param ro If true, restore in readonly mode, otherwise in readwrite mode.
* \throw std::bad_alloc Not enough memory.
* \throw std::runtime_error State is not from this movie or states is corrupt.
*/
size_t restore_state(const std::vector<uint8_t>& state, bool ro) throw(std::bad_alloc, std::runtime_error);
/**
* \brief Get reset status for current frame.
*
* \return -1 if current frame doesn't have a reset. Otherwise number of cycles to wait for delayed reset (0 is
* immediate reset).
*/
long get_reset_status() throw();
/**
* \brief Commit a reset.
*/
void commit_reset(long delay) throw(std::bad_alloc);
/**
* \brief Get how manyth poll in the frame next poll would be.
*
* \return Poll number.
*/
unsigned next_poll_number();
uint64_t frame_subframes(uint64_t frame);
controls_t read_subframe(uint64_t frame, uint64_t subframe);
private:
//TRUE if readonly mode is active.
bool readonly;
//Movie (not global!) rerecord count.
std::string rerecords;
//Project ID.
std::string _project_id;
//The actual controller data.
std::vector<controls_t> movie_data;
//Current frame + 1 (0 before next_frame() has been called.
uint64_t current_frame;
//First subframe in current frame (movie_data.size() if no subframes have been stored).
uint64_t current_frame_first_subframe;
//How many times has each control been polled (bit 31 is data ready bit)?
uint32_t pollcounters[TOTAL_CONTROLS];
//Current state of buttons.
controls_t current_controls;
//Number of known lag frames.
uint64_t lag_frames;
//Number of frames in movie.
uint64_t frames_in_movie;
//Cached subframes.
void clear_caches();
uint64_t cached_frame;
uint64_t cached_subframe;
//Count present subframes in frame starting from first_subframe (returns 0 if out of movie).
uint32_t count_changes(uint64_t first_subframe);
};
/**
* \brief Class encapsulating bridge logic between bsnes interface and movie code.
*/
class movie_logic
{
public:
/**
* \brief Create new bridge.
*
* \param m The movie to manipulate.
*/
movie_logic(movie& m) throw();
/**
* \brief Destructor.
*/
virtual ~movie_logic() throw();
/**
* \brief Get the movie instance associated.
*/
movie& get_movie() throw();
/**
* \brief Notify about new frame starting.
*
* \return Reset status for the new frame.
*/
long new_frame_starting(bool dont_poll) throw(std::bad_alloc, std::runtime_error);
/**
* \brief Poll for input.
*
* \return Value for polled input.
*/
short input_poll(bool port, unsigned dev, unsigned id) throw(std::bad_alloc, std::runtime_error);
/**
* \brief Called when movie code needs new controls snapshot.
*
* \param subframe True if this is for subframe update, false if for frame update.
*/
virtual controls_t update_controls(bool subframe) throw(std::bad_alloc, std::runtime_error) = 0;
private:
movie& mov;
};
/**
* \brief Global rerecord count.
*/
extern std::string global_rerecord_count;
#endif

401
moviefile.cpp Normal file
View file

@ -0,0 +1,401 @@
#include "lsnes.hpp"
#include <snes/snes.hpp>
#include <ui-libsnes/libsnes.hpp>
#include "moviefile.hpp"
#include "zip.hpp"
#include "misc.hpp"
#include "rrdata.hpp"
#include <sstream>
#include <boost/iostreams/copy.hpp>
#include <boost/iostreams/device/back_inserter.hpp>
void strip_CR(std::string& x) throw(std::bad_alloc)
{
if(x.length() > 0 && x[x.length() - 1] == '\r') {
if(x.length() > 1)
x = x.substr(0, x.length() - 1);
else
x = "";
}
}
void read_linefile(zip_reader& r, const std::string& member, std::string& out, bool conditional = false)
throw(std::bad_alloc, std::runtime_error)
{
if(conditional && !r.has_member(member))
return;
std::istream& m = r[member];
try {
std::getline(m, out);
strip_CR(out);
delete &m;
} catch(...) {
delete &m;
throw;
}
}
void write_linefile(zip_writer& w, const std::string& member, const std::string& value, bool conditional = false)
throw(std::bad_alloc, std::runtime_error)
{
if(conditional && value == "")
return;
std::ostream& m = w.create_file(member);
try {
m << value << std::endl;
w.close_file();
} catch(...) {
w.close_file();
throw;
}
}
void write_raw_file(zip_writer& w, const std::string& member, std::vector<char>& content) throw(std::bad_alloc,
std::runtime_error)
{
std::ostream& m = w.create_file(member);
try {
m.write(&content[0], content.size());
if(!m)
throw std::runtime_error("Can't write ZIP file member");
w.close_file();
} catch(...) {
w.close_file();
throw;
}
}
std::vector<char> read_raw_file(zip_reader& r, const std::string& member) throw(std::bad_alloc, std::runtime_error)
{
std::vector<char> out;
std::istream& m = r[member];
try {
boost::iostreams::back_insert_device<std::vector<char>> rd(out);
boost::iostreams::copy(m, rd);
delete &m;
} catch(...) {
delete &m;
throw;
}
return out;
}
void read_authors_file(zip_reader& r, std::vector<std::pair<std::string, std::string>>& authors) throw(std::bad_alloc,
std::runtime_error)
{
std::istream& m = r["authors"];
try {
std::string x;
while(std::getline(m, x)) {
strip_CR(x);
fieldsplitter fields(x);
std::string y = static_cast<std::string>(fields);
std::string z = static_cast<std::string>(fields);
authors.push_back(std::make_pair(y, z));
}
delete &m;
} catch(...) {
delete &m;
throw;
}
}
std::string read_rrdata(zip_reader& r, const std::string& projectid) throw(std::bad_alloc, std::runtime_error)
{
std::istream& m = r["rrdata"];
uint64_t count;
try {
rrdata::read_base(projectid);
count = rrdata::read(m);
delete &m;
} catch(...) {
delete &m;
throw;
}
std::ostringstream x;
x << count;
return x.str();
}
void write_rrdata(zip_writer& w) throw(std::bad_alloc, std::runtime_error)
{
std::ostream& m = w.create_file("rrdata");
uint64_t count;
try {
count = rrdata::write(m);
if(!m)
throw std::runtime_error("Can't write ZIP file member");
w.close_file();
} catch(...) {
w.close_file();
throw;
}
std::ostream& m2 = w.create_file("rerecords");
try {
m2 << count << std::endl;
if(!m2)
throw std::runtime_error("Can't write ZIP file member");
w.close_file();
} catch(...) {
w.close_file();
throw;
}
}
void write_authors_file(zip_writer& w, std::vector<std::pair<std::string, std::string>>& authors)
throw(std::bad_alloc, std::runtime_error)
{
std::ostream& m = w.create_file("authors");
try {
for(auto i = authors.begin(); i != authors.end(); ++i)
if(i->second == "")
m << i->first << std::endl;
else
m << i->first << "|" << i->second << std::endl;
if(!m)
throw std::runtime_error("Can't write ZIP file member");
w.close_file();
} catch(...) {
w.close_file();
throw;
}
}
void write_input(zip_writer& w, std::vector<controls_t>& input, porttype_t port1, porttype_t port2)
throw(std::bad_alloc, std::runtime_error)
{
std::vector<cencode::fn_t> encoders;
encoders.push_back(port_types[port1].encoder);
encoders.push_back(port_types[port2].encoder);
std::ostream& m = w.create_file("input");
try {
for(auto i = input.begin(); i != input.end(); ++i)
m << i->tostring(encoders) << std::endl;
if(!m)
throw std::runtime_error("Can't write ZIP file member");
w.close_file();
} catch(...) {
w.close_file();
throw;
}
}
void read_input(zip_reader& r, std::vector<controls_t>& input, porttype_t port1, porttype_t port2, unsigned version)
throw(std::bad_alloc, std::runtime_error)
{
std::vector<cdecode::fn_t> decoders;
decoders.push_back(port_types[port1].decoder);
decoders.push_back(port_types[port2].decoder);
std::istream& m = r["input"];
try {
std::string x;
while(std::getline(m, x)) {
strip_CR(x);
if(x != "") {
input.push_back(controls_t(x, decoders, version));
}
}
delete &m;
} catch(...) {
delete &m;
throw;
}
}
porttype_t parse_controller_type(const std::string& type, bool port) throw(std::bad_alloc, std::runtime_error)
{
porttype_t port1 = PT_INVALID;
for(unsigned i = 0; i <= PT_LAST_CTYPE; i++)
if(type == port_types[i].name && (port || port_types[i].valid_port1))
port1 = static_cast<porttype_t>(i);
if(port1 == PT_INVALID)
throw std::runtime_error(std::string("Illegal port") + (port ? "2" : "1") + " device '" + type + "'");
return port1;
}
moviefile::moviefile() throw(std::bad_alloc)
{
gametype = GT_INVALID;
port1 = PT_GAMEPAD;
port2 = PT_NONE;
coreversion = bsnes_core_version;
projectid = get_random_hexstring(40);
rerecords = "0";
is_savestate = false;
}
moviefile::moviefile(const std::string& movie) throw(std::bad_alloc, std::runtime_error)
{
is_savestate = false;
std::string tmp;
zip_reader r(movie);
read_linefile(r, "systemid", tmp);
if(tmp.substr(0, 8) != "lsnes-rr")
throw std::runtime_error("Not lsnes movie");
read_linefile(r, "controlsversion", tmp);
if(tmp != "0")
throw std::runtime_error("Can't decode movie data");
read_linefile(r, "gametype", tmp);
try {
gametype = gtype::togametype(tmp);
} catch(std::bad_alloc& e) {
throw;
} catch(std::exception& e) {
throw std::runtime_error("Illegal game type '" + tmp + "'");
}
tmp = port_types[PT_GAMEPAD].name;
read_linefile(r, "port1", tmp, true);
port1 = port_type::lookup(tmp, false).ptype;
tmp = port_types[PT_NONE].name;
read_linefile(r, "port2", tmp, true);
port2 = port_type::lookup(tmp, true).ptype;
read_linefile(r, "gamename", gamename, true);
read_linefile(r, "projectid", projectid);
rerecords = read_rrdata(r, projectid);
read_linefile(r, "coreversion", coreversion);
read_linefile(r, "rom.sha256", rom_sha256, true);
read_linefile(r, "romxml.sha256", romxml_sha256, true);
read_linefile(r, "slota.sha256", slota_sha256, true);
read_linefile(r, "slotaxml.sha256", slotaxml_sha256, true);
read_linefile(r, "slotb.sha256", slotb_sha256, true);
read_linefile(r, "slotbxml.sha256", slotbxml_sha256, true);
if(r.has_member("savestate")) {
is_savestate = true;
movie_state = read_raw_file(r, "moviestate");
if(r.has_member("hostmemory"))
host_memory = read_raw_file(r, "hostmemory");
savestate = read_raw_file(r, "savestate");
for(auto name = r.begin(); name != r.end(); ++name)
if((*name).length() >= 5 && (*name).substr(0, 5) == "sram.")
sram[(*name).substr(5)] = read_raw_file(r, *name);
screenshot = read_raw_file(r, "screenshot");
}
std::string name = r.find_first();
for(auto name = r.begin(); name != r.end(); ++name)
if((*name).length() >= 10 && (*name).substr(0, 10) == "moviesram.")
sram[(*name).substr(10)] = read_raw_file(r, *name);
read_authors_file(r, authors);
read_input(r, input, port1, port2, 0);
}
void moviefile::save(const std::string& movie, unsigned compression) throw(std::bad_alloc, std::runtime_error)
{
zip_writer w(movie, compression);
write_linefile(w, "gametype", gtype::tostring(gametype));
if(port1 != PT_GAMEPAD)
write_linefile(w, "port1", port_types[port1].name);
if(port2 != PT_NONE)
write_linefile(w, "port2", port_types[port2].name);
write_linefile(w, "gamename", gamename, true);
write_linefile(w, "systemid", "lsnes-rr1");
write_linefile(w, "controlsversion", "0");
coreversion = bsnes_core_version;
write_linefile(w, "coreversion", coreversion);
write_linefile(w, "projectid", projectid);
write_rrdata(w);
write_linefile(w, "rom.sha256", rom_sha256, true);
write_linefile(w, "romxml.sha256", romxml_sha256, true);
write_linefile(w, "slota.sha256", slota_sha256, true);
write_linefile(w, "slotaxml.sha256", slotaxml_sha256, true);
write_linefile(w, "slotb.sha256", slotb_sha256, true);
write_linefile(w, "slotbxml.sha256", slotbxml_sha256, true);
for(auto i = movie_sram.begin(); i != movie_sram.end(); ++i)
write_raw_file(w, "moviesram." + i->first, i->second);
if(is_savestate) {
write_raw_file(w, "moviestate", movie_state);
write_raw_file(w, "hostmemory", host_memory);
write_raw_file(w, "savestate", savestate);
write_raw_file(w, "screenshot", screenshot);
for(auto i = sram.begin(); i != sram.end(); ++i)
write_raw_file(w, "sram." + i->first, i->second);
}
write_authors_file(w, authors);
write_input(w, input, port1, port2);
w.commit();
}
uint64_t moviefile::get_frame_count() throw()
{
uint64_t frames = 0;
for(size_t i = 0; i < input.size(); i++) {
if(input[i](CONTROL_FRAME_SYNC))
frames++;
}
return frames;
}
namespace
{
const int BLOCK_SECONDS = 0;
const int BLOCK_FRAMES = 1;
const int STEP_W = 2;
const int STEP_N = 3;
uint64_t magic[2][4] = {
{178683, 10738636, 16639264, 596096},
{6448, 322445, 19997208, 266440}
};
}
uint64_t moviefile::get_movie_length() throw()
{
uint64_t frames = get_frame_count();
uint64_t* _magic = magic[(gametype == GT_SNES_PAL || gametype == GT_SGB_PAL) ? 1 : 0];
uint64_t t = _magic[BLOCK_SECONDS] * 1000000000ULL * (frames / _magic[BLOCK_FRAMES]);
frames %= _magic[BLOCK_FRAMES];
t += frames * _magic[STEP_W] + (frames * _magic[STEP_N] / _magic[BLOCK_FRAMES]);
return t;
}
gametype_t gametype_compose(rom_type type, rom_region region)
{
switch(type) {
case ROMTYPE_SNES:
return (region == REGION_PAL) ? GT_SNES_PAL : GT_SNES_NTSC;
case ROMTYPE_BSX:
return GT_BSX;
case ROMTYPE_BSXSLOTTED:
return GT_BSX_SLOTTED;
case ROMTYPE_SUFAMITURBO:
return GT_SUFAMITURBO;
case ROMTYPE_SGB:
return (region == REGION_PAL) ? GT_SGB_PAL : GT_SGB_NTSC;
default:
return GT_INVALID;
}
}
rom_region gametype_region(gametype_t type)
{
switch(type) {
case GT_SGB_PAL:
case GT_SNES_PAL:
return REGION_PAL;
default:
return REGION_NTSC;
}
}
rom_type gametype_romtype(gametype_t type)
{
switch(type) {
case GT_SNES_NTSC:
case GT_SNES_PAL:
return ROMTYPE_SNES;
case GT_BSX:
return ROMTYPE_BSX;
case GT_BSX_SLOTTED:
return ROMTYPE_BSXSLOTTED;
case GT_SUFAMITURBO:
return ROMTYPE_SUFAMITURBO;
case GT_SGB_PAL:
case GT_SGB_NTSC:
return ROMTYPE_SGB;
default:
return ROMTYPE_NONE;
};
}

155
moviefile.hpp Normal file
View file

@ -0,0 +1,155 @@
#ifndef _moviefile__hpp__included__
#define _moviefile__hpp__included__
#include <string>
#include <vector>
#include <stdexcept>
#include <map>
#include "controllerdata.hpp"
#include "rom.hpp"
/**
* \brief Parsed representation of movie file
*
* This structure gives parsed representationg of movie file, as result of decoding or for encoding.
*/
struct moviefile
{
/**
* \brief Construct empty movie
*
* This constructor construct movie structure with default settings.
*
* \throws std::bad_alloc Not enough memory.
*/
moviefile() throw(std::bad_alloc);
/**
* \brief Load a movie/savestate file
*
* This constructor loads a movie/savestate file and fills structure accordingly.
*
* \param filename The file to load.
* \throws std::bad_alloc Not enough memory.
* \throws std::runtime_error Can't load the movie file
*/
moviefile(const std::string& filename) throw(std::bad_alloc, std::runtime_error);
/**
* \brief Save a movie or savestate.
*
* Reads this movie structure and saves it into file.
*
* \param filename The file to save to.
* \param compression The compression level 0-9. 0 is uncompressed.
* \throws std::bad_alloc Not enough memory.
* \throws std::runtime_error Can't save the movie file.
*/
void save(const std::string& filename, unsigned compression) throw(std::bad_alloc, std::runtime_error);
/**
* \brief What is the ROM type and region?
*/
gametype_t gametype;
/**
* \brief What's in port #1?
*/
porttype_t port1;
/**
* \brief What's in port #2?
*/
porttype_t port2;
/**
* \brief Emulator Core version string.
*/
std::string coreversion;
/**
* \brief Name of the game
*/
std::string gamename;
/**
* \brief Project ID (used to identify if two movies are from the same project).
*/
std::string projectid;
/**
* \brief Rerecord count (only loaded).
*/
std::string rerecords;
/**
* \brief SHA-256 of main ROM (empty string if none).
*/
std::string rom_sha256; //SHA-256 of main ROM.
/**
* \brief SHA-256 of main ROM XML (empty string if none).
*/
std::string romxml_sha256; //SHA-256 of main ROM XML.
/**
* \brief SHA-256 of slot A ROM (empty string if none).
*/
std::string slota_sha256; //SHA-256 of SLOT A ROM.
/**
* \brief SHA-256 of slot A XML (empty string if none).
*/
std::string slotaxml_sha256; //SHA-256 of SLOT A XML.
/**
* \brief SHA-256 of slot B ROM (empty string if none).
*/
std::string slotb_sha256; //SHA-256 of SLOT B ROM.
/**
* \brief SHA-256 of slot B XML (empty string if none).
*/
std::string slotbxml_sha256; //SHA-256 of SLOT B XML.
/**
* \brief Authors of the run, first in each pair is full name, second is nickname.
*/
std::vector<std::pair<std::string, std::string>> authors;
/**
* \brief Contents of SRAM on time of initial powerup.
*/
std::map<std::string, std::vector<char>> movie_sram;
/**
* \brief True if savestate, false if movie.
*/
bool is_savestate;
/**
* \brief Contents of SRAM on time of savestate (if is_savestate is true).
*/
std::map<std::string, std::vector<char>> sram;
/**
* \brief Core savestate (if is_savestate is true).
*/
std::vector<char> savestate; //Savestate to load (if is_savestate is true).
/**
* \brief Host memory (if is_savestate is true).
*/
std::vector<char> host_memory;
/**
* \brief Screenshot (if is_savestate is true).
*/
std::vector<char> screenshot;
/**
* \brief State of movie code (if is_savestate is true).
*/
std::vector<char> movie_state;
/**
* \brief Input for each (sub)frame.
*/
std::vector<controls_t> input; //Input for each frame.
/**
* \brief Get number of frames in movie.
*
* \return Number of frames.
*/
uint64_t get_frame_count() throw();
/**
* \brief Get length of the movie
*
* \return Length of the movie in nanoseconds.
*/
uint64_t get_movie_length() throw();
};
#endif

637
movietrunctest.cpp Normal file
View file

@ -0,0 +1,637 @@
#include "movie.hpp"
#include <iostream>
void truncate_past_complete_frame()
{
short in1, in2;
std::cerr << "Test #1: Truncate past complete frame" << std::endl;
movie m;
std::vector<uint8_t> state;
controls_t c;
m.readonly_mode(false);
m.next_frame();
c(4) = 0x1;
c(5) = 0x2;
m.set_controls(c);
in1 = m.next_input(4);
if(in1 != 0x1) {
std::cerr << "FAIL: Unexpected return for m.next_input(4) (1)" << std::endl;
std::cerr << "Expected 1, got " << in1 << "." << std::endl;
return;
}
in2 = m.next_input(5);
if(in2 != 0x2) {
std::cerr << "FAIL: Unexpected return for m.next_input(5) (1)" << std::endl;
return;
}
m.next_frame();
c(4) = 0x3;
c(5) = 0x4;
m.set_controls(c);
in1 = m.next_input(4);
if(in1 != 0x3) {
std::cerr << "FAIL: Unexpected return for m.next_input(4) (2)" << std::endl;
return;
}
in2 = m.next_input(5);
if(in2 != 0x4) {
std::cerr << "FAIL: Unexpected return for m.next_input(5) (2)" << std::endl;
return;
}
c(4) = 0x5;
c(5) = 0x6;
m.set_controls(c);
in1 = m.next_input(4);
if(in1 != 0x5) {
std::cerr << "FAIL: Unexpected return for m.next_input(4) (3)" << std::endl;
return;
}
in2 = m.next_input(5);
if(in2 != 0x6) {
std::cerr << "FAIL: Unexpected return for m.next_input(5) (3)" << std::endl;
return;
}
m.next_frame();
state = m.save_state();
c(4) = 0x7;
c(5) = 0x8;
m.set_controls(c);
in1 = m.next_input(4);
if(in1 != 0x7) {
std::cerr << "FAIL: Unexpected return for m.next_input(4) (4)" << std::endl;
return;
}
in2 = m.next_input(5);
if(in2 != 0x8) {
std::cerr << "FAIL: Unexpected return for m.next_input(5) (4)" << std::endl;
return;
}
m.next_frame();
c(4) = 0x9;
c(5) = 0xa;
m.set_controls(c);
in1 = m.next_input(4);
if(in1 != 0x9) {
std::cerr << "FAIL: Unexpected return for m.next_input(4) (5)" << std::endl;
return;
}
in2 = m.next_input(5);
if(in2 != 0xa) {
std::cerr << "FAIL: Unexpected return for m.next_input(5) (5)" << std::endl;
return;
}
m.restore_state(state, false);
std::vector<controls_t> v = m.save();
if(v.size() != 3) {
std::cerr << "FAIL: Unexpected size for movie" << std::endl;
return;
}
if(v[0](0) != 1 || v[0](4) != 0x1 || v[0](5) != 0x2) {
std::cerr << "FAIL: Wrong input for first frame" << std::endl;
return;
}
if(v[1](0) != 1 || v[1](4) != 0x3 || v[1](5) != 0x4) {
std::cerr << "FAIL: Wrong input for second frame first subframe" << std::endl;
return;
}
if(v[2](0) != 0 || v[2](4) != 0x5 || v[2](5) != 0x6) {
std::cerr << "FAIL: Wrong input for second frame second subframe" << std::endl;
return;
}
std::cerr << "PASS!" << std::endl;
}
void truncate_past_incomplete_frame()
{
short in1, in2;
std::cerr << "Test #2: Truncate past incomplete frame" << std::endl;
movie m;
std::vector<uint8_t> state;
controls_t c;
m.readonly_mode(false);
m.next_frame();
c(4) = 0x1;
c(5) = 0x2;
m.set_controls(c);
in1 = m.next_input(4);
if(in1 != 0x1) {
std::cerr << "FAIL: Unexpected return for m.next_input(4) (1)" << std::endl;
std::cerr << "Expected 1, got " << in1 << "." << std::endl;
return;
}
in2 = m.next_input(5);
if(in2 != 0x2) {
std::cerr << "FAIL: Unexpected return for m.next_input(5) (1)" << std::endl;
return;
}
m.next_frame();
c(4) = 0x3;
c(5) = 0x4;
m.set_controls(c);
in1 = m.next_input(4);
if(in1 != 0x3) {
std::cerr << "FAIL: Unexpected return for m.next_input(4) (2)" << std::endl;
return;
}
in2 = m.next_input(5);
if(in2 != 0x4) {
std::cerr << "FAIL: Unexpected return for m.next_input(5) (2)" << std::endl;
return;
}
c(4) = 0x5;
c(5) = 0x6;
m.set_controls(c);
in1 = m.next_input(4);
if(in1 != 0x5) {
std::cerr << "FAIL: Unexpected return for m.next_input(4) (3)" << std::endl;
return;
}
in2 = m.next_input(5);
if(in2 != 0x6) {
std::cerr << "FAIL: Unexpected return for m.next_input(5) (3)" << std::endl;
return;
}
c(5) = 0x7;
c(6) = 0x8;
m.set_controls(c);
in1 = m.next_input(5);
if(in1 != 0x7) {
std::cerr << "FAIL: Unexpected return for m.next_input(5) (4)" << std::endl;
return;
}
in2 = m.next_input(6);
if(in2 != 0x8) {
std::cerr << "FAIL: Unexpected return for m.next_input(6) (4)" << std::endl;
return;
}
state = m.save_state();
//Now we have 2 subframes on 4, 3 on 5 and 1 on 6. Add 1 subframe for 4, 5 and 7.
c(4) = 0x9;
c(5) = 0xa;
c(7) = 0xb;
m.set_controls(c);
in1 = m.next_input(4);
if(in1 != 0x9) {
std::cerr << "FAIL: Unexpected return for m.next_input(4) (5)" << std::endl;
return;
}
in1 = m.next_input(5);
if(in1 != 0xa) {
std::cerr << "FAIL: Unexpected return for m.next_input(5) (5)" << std::endl;
return;
}
in2 = m.next_input(7);
if(in2 != 0xb) {
std::cerr << "FAIL: Unexpected return for m.next_input(7) (5)" << std::endl;
return;
}
m.next_frame();
c(4) = 0xc;
c(5) = 0xd;
m.set_controls(c);
in1 = m.next_input(4);
if(in1 != 0xc) {
std::cerr << "FAIL: Unexpected return for m.next_input(4) (6)" << std::endl;
return;
}
in2 = m.next_input(5);
if(in2 != 0xd) {
std::cerr << "FAIL: Unexpected return for m.next_input(5) (6)" << std::endl;
return;
}
m.next_frame();
c(4) = 0xe;
c(5) = 0xf;
m.set_controls(c);
in1 = m.next_input(4);
if(in1 != 0xe) {
std::cerr << "FAIL: Unexpected return for m.next_input(4) (7)" << std::endl;
return;
}
in2 = m.next_input(5);
if(in2 != 0xf) {
std::cerr << "FAIL: Unexpected return for m.next_input(5) (7)" << std::endl;
return;
}
m.restore_state(state, false);
std::vector<controls_t> v = m.save();
if(v.size() != 4) {
std::cerr << "FAIL: Unexpected size for movie" << std::endl;
return;
}
if(v[0](0) != 1 || v[0](4) != 0x1 || v[0](5) != 0x2) {
std::cerr << "FAIL: Wrong input for first frame" << std::endl;
return;
}
if(v[1](0) != 1 || v[1](4) != 0x3 || v[1](5) != 0x4 || v[1](6) != 0x8 || v[1](7) != 0xb) {
std::cerr << "FAIL: Wrong input for second frame first subframe" << std::endl;
return;
}
if(v[2](0) != 0 || v[2](4) != 0x5 || v[2](5) != 0x6 || v[2](6) != 0x8 || v[2](7) != 0xb) {
std::cerr << "FAIL: Wrong input for second frame second subframe" << std::endl;
std::cerr << v[2](0) << " " << v[2](4) << " " << v[2](5) << " " << v[2](6) << " " << v[2](7) << std::endl;
return;
}
if(v[3](0) != 0 || v[3](4) != 0x5 || v[3](5) != 0x7 || v[3](6) != 0x8 || v[3](7) != 0xb) {
std::cerr << "FAIL: Wrong input for second frame third subframe" << std::endl;
return;
}
std::cerr << "PASS!" << std::endl;
}
void truncate_current_complete_frame()
{
short in1, in2;
std::cerr << "Test #3: Truncate current complete frame" << std::endl;
movie m;
std::vector<uint8_t> state;
controls_t c;
m.readonly_mode(false);
m.next_frame();
c(4) = 0x1;
c(5) = 0x2;
m.set_controls(c);
in1 = m.next_input(4);
if(in1 != 0x1) {
std::cerr << "FAIL: Unexpected return for m.next_input(4) (1)" << std::endl;
std::cerr << "Expected 1, got " << in1 << "." << std::endl;
return;
}
in2 = m.next_input(5);
if(in2 != 0x2) {
std::cerr << "FAIL: Unexpected return for m.next_input(5) (1)" << std::endl;
return;
}
m.next_frame();
c(4) = 0x3;
c(5) = 0x4;
m.set_controls(c);
in1 = m.next_input(4);
if(in1 != 0x3) {
std::cerr << "FAIL: Unexpected return for m.next_input(4) (2)" << std::endl;
return;
}
in2 = m.next_input(5);
if(in2 != 0x4) {
std::cerr << "FAIL: Unexpected return for m.next_input(5) (2)" << std::endl;
return;
}
c(4) = 0x5;
c(5) = 0x6;
m.set_controls(c);
in1 = m.next_input(4);
if(in1 != 0x5) {
std::cerr << "FAIL: Unexpected return for m.next_input(4) (3)" << std::endl;
return;
}
in2 = m.next_input(5);
if(in2 != 0x6) {
std::cerr << "FAIL: Unexpected return for m.next_input(5) (3)" << std::endl;
return;
}
m.next_frame();
state = m.save_state();
c(4) = 0x7;
c(5) = 0x8;
m.set_controls(c);
in1 = m.next_input(4);
if(in1 != 0x7) {
std::cerr << "FAIL: Unexpected return for m.next_input(4) (4)" << std::endl;
return;
}
in2 = m.next_input(5);
if(in2 != 0x8) {
std::cerr << "FAIL: Unexpected return for m.next_input(5) (4)" << std::endl;
return;
}
m.restore_state(state, false);
std::vector<controls_t> v = m.save();
if(v.size() != 3) {
std::cerr << "FAIL: Unexpected size for movie" << std::endl;
return;
}
if(v[0](0) != 1 || v[0](4) != 0x1 || v[0](5) != 0x2) {
std::cerr << "FAIL: Wrong input for first frame" << std::endl;
return;
}
if(v[1](0) != 1 || v[1](4) != 0x3 || v[1](5) != 0x4) {
std::cerr << "FAIL: Wrong input for second frame first subframe" << std::endl;
return;
}
if(v[2](0) != 0 || v[2](4) != 0x5 || v[2](5) != 0x6) {
std::cerr << "FAIL: Wrong input for second frame second subframe" << std::endl;
return;
}
std::cerr << "PASS!" << std::endl;
}
void truncate_current_incomplete_frame()
{
short in1, in2;
std::cerr << "Test #4: Truncate current incomplete frame" << std::endl;
movie m;
std::vector<uint8_t> state;
controls_t c;
m.readonly_mode(false);
m.next_frame();
c(4) = 0x1;
c(5) = 0x2;
m.set_controls(c);
in1 = m.next_input(4);
if(in1 != 0x1) {
std::cerr << "FAIL: Unexpected return for m.next_input(4) (1)" << std::endl;
std::cerr << "Expected 1, got " << in1 << "." << std::endl;
return;
}
in2 = m.next_input(5);
if(in2 != 0x2) {
std::cerr << "FAIL: Unexpected return for m.next_input(5) (1)" << std::endl;
return;
}
m.next_frame();
c(4) = 0x3;
c(5) = 0x4;
m.set_controls(c);
in1 = m.next_input(4);
if(in1 != 0x3) {
std::cerr << "FAIL: Unexpected return for m.next_input(4) (2)" << std::endl;
return;
}
in2 = m.next_input(5);
if(in2 != 0x4) {
std::cerr << "FAIL: Unexpected return for m.next_input(5) (2)" << std::endl;
return;
}
c(4) = 0x5;
c(5) = 0x6;
m.set_controls(c);
in1 = m.next_input(4);
if(in1 != 0x5) {
std::cerr << "FAIL: Unexpected return for m.next_input(4) (3)" << std::endl;
return;
}
in2 = m.next_input(5);
if(in2 != 0x6) {
std::cerr << "FAIL: Unexpected return for m.next_input(5) (3)" << std::endl;
return;
}
c(5) = 0x7;
c(6) = 0x8;
m.set_controls(c);
in1 = m.next_input(5);
if(in1 != 0x7) {
std::cerr << "FAIL: Unexpected return for m.next_input(5) (4)" << std::endl;
return;
}
in2 = m.next_input(6);
if(in2 != 0x8) {
std::cerr << "FAIL: Unexpected return for m.next_input(6) (4)" << std::endl;
return;
}
state = m.save_state();
//Now we have 2 subframes on 4, 3 on 5 and 1 on 6. Add 1 subframe for 4, 5 and 7.
c(4) = 0x9;
c(5) = 0xa;
c(7) = 0xb;
m.set_controls(c);
in1 = m.next_input(4);
if(in1 != 0x9) {
std::cerr << "FAIL: Unexpected return for m.next_input(4) (5)" << std::endl;
return;
}
in1 = m.next_input(5);
if(in1 != 0xa) {
std::cerr << "FAIL: Unexpected return for m.next_input(5) (5)" << std::endl;
return;
}
in2 = m.next_input(7);
if(in2 != 0xb) {
std::cerr << "FAIL: Unexpected return for m.next_input(7) (5)" << std::endl;
return;
}
m.restore_state(state, false);
std::vector<controls_t> v = m.save();
if(v.size() != 4) {
std::cerr << "FAIL: Unexpected size for movie" << std::endl;
return;
}
if(v[0](0) != 1 || v[0](4) != 0x1 || v[0](5) != 0x2) {
std::cerr << "FAIL: Wrong input for first frame" << std::endl;
return;
}
if(v[1](0) != 1 || v[1](4) != 0x3 || v[1](5) != 0x4 || v[1](6) != 0x8 || v[1](7) != 0xb) {
std::cerr << "FAIL: Wrong input for second frame first subframe" << std::endl;
return;
}
if(v[2](0) != 0 || v[2](4) != 0x5 || v[2](5) != 0x6 || v[2](6) != 0x8 || v[2](7) != 0xb) {
std::cerr << "FAIL: Wrong input for second frame second subframe" << std::endl;
std::cerr << v[2](0) << " " << v[2](4) << " " << v[2](5) << " " << v[2](6) << " " << v[2](7) << std::endl;
return;
}
if(v[3](0) != 0 || v[3](4) != 0x5 || v[3](5) != 0x7 || v[3](6) != 0x8 || v[3](7) != 0xb) {
std::cerr << "FAIL: Wrong input for second frame third subframe" << std::endl;
return;
}
std::cerr << "PASS!" << std::endl;
}
void truncate_future_complete_frame()
{
short in1, in2;
std::cerr << "Test #5: Truncate future complete frame" << std::endl;
movie m;
std::vector<uint8_t> state;
controls_t c;
m.readonly_mode(false);
m.next_frame();
c(4) = 0x1;
c(5) = 0x2;
m.set_controls(c);
in1 = m.next_input(4);
if(in1 != 0x1) {
std::cerr << "FAIL: Unexpected return for m.next_input(4) (1)" << std::endl;
std::cerr << "Expected 1, got " << in1 << "." << std::endl;
return;
}
in2 = m.next_input(5);
if(in2 != 0x2) {
std::cerr << "FAIL: Unexpected return for m.next_input(5) (1)" << std::endl;
return;
}
m.next_frame();
c(4) = 0x3;
c(5) = 0x4;
m.set_controls(c);
in1 = m.next_input(4);
if(in1 != 0x3) {
std::cerr << "FAIL: Unexpected return for m.next_input(4) (2)" << std::endl;
return;
}
in2 = m.next_input(5);
if(in2 != 0x4) {
std::cerr << "FAIL: Unexpected return for m.next_input(5) (2)" << std::endl;
return;
}
c(4) = 0x5;
c(5) = 0x6;
m.set_controls(c);
in1 = m.next_input(4);
if(in1 != 0x5) {
std::cerr << "FAIL: Unexpected return for m.next_input(4) (3)" << std::endl;
return;
}
in2 = m.next_input(5);
if(in2 != 0x6) {
std::cerr << "FAIL: Unexpected return for m.next_input(5) (3)" << std::endl;
return;
}
m.next_frame();
m.readonly_mode(true);
m.next_frame();
m.next_frame();
m.next_frame();
m.readonly_mode(false);
std::vector<controls_t> v = m.save();
if(v.size() != 6) {
std::cerr << "FAIL: Unexpected size for movie" << std::endl;
return;
}
if(v[0](0) != 1 || v[0](4) != 0x1 || v[0](5) != 0x2) {
std::cerr << "FAIL: Wrong input for first frame" << std::endl;
return;
}
if(v[1](0) != 1 || v[1](4) != 0x3 || v[1](5) != 0x4) {
std::cerr << "FAIL: Wrong input for second frame first subframe" << std::endl;
return;
}
if(v[2](0) != 0 || v[2](4) != 0x5 || v[2](5) != 0x6) {
std::cerr << "FAIL: Wrong input for second frame second subframe" << std::endl;
return;
}
if(v[3](0) != 1 || v[3](4) != 0 || v[3](5) != 0) {
std::cerr << "FAIL: Wrong input for third frame" << std::endl;
return;
}
if(v[4](0) != 1 || v[4](4) != 0 || v[4](5) != 0) {
std::cerr << "FAIL: Wrong input for fourth frame" << std::endl;
return;
}
if(v[5](0) != 1 || v[5](4) != 0 || v[5](5) != 0) {
std::cerr << "FAIL: Wrong input for fifth frame" << std::endl;
return;
}
std::cerr << "PASS!" << std::endl;
}
void truncate_future_incomplete_frame()
{
short in1, in2;
std::cerr << "Test #6: Truncate future incomplete frame" << std::endl;
movie m;
std::vector<uint8_t> state;
controls_t c;
m.readonly_mode(false);
m.next_frame();
c(4) = 0x1;
c(5) = 0x2;
m.set_controls(c);
in1 = m.next_input(4);
if(in1 != 0x1) {
std::cerr << "FAIL: Unexpected return for m.next_input(4) (1)" << std::endl;
std::cerr << "Expected 1, got " << in1 << "." << std::endl;
return;
}
in2 = m.next_input(5);
if(in2 != 0x2) {
std::cerr << "FAIL: Unexpected return for m.next_input(5) (1)" << std::endl;
return;
}
m.next_frame();
c(4) = 0x3;
c(5) = 0x4;
m.set_controls(c);
in1 = m.next_input(4);
if(in1 != 0x3) {
std::cerr << "FAIL: Unexpected return for m.next_input(4) (2)" << std::endl;
return;
}
in2 = m.next_input(5);
if(in2 != 0x4) {
std::cerr << "FAIL: Unexpected return for m.next_input(5) (2)" << std::endl;
return;
}
c(4) = 0x5;
c(5) = 0x6;
m.set_controls(c);
in1 = m.next_input(4);
if(in1 != 0x5) {
std::cerr << "FAIL: Unexpected return for m.next_input(4) (3)" << std::endl;
return;
}
in2 = m.next_input(5);
if(in2 != 0x6) {
std::cerr << "FAIL: Unexpected return for m.next_input(5) (3)" << std::endl;
return;
}
m.next_frame();
m.readonly_mode(true);
m.next_frame();
m.next_frame();
m.next_frame();
in1 = m.next_input(4);
if(in1 != 0x0) {
std::cerr << "FAIL: Unexpected return for m.next_input(4) (4)" << std::endl;
return;
}
in1 = m.next_input(4);
if(in1 != 0x0) {
std::cerr << "FAIL: Unexpected return for m.next_input(4) (5)" << std::endl;
return;
}
m.readonly_mode(false);
std::vector<controls_t> v = m.save();
if(v.size() != 7) {
std::cerr << "FAIL: Unexpected size for movie (got " << v.size() << ")" << std::endl;
return;
}
if(v[0](0) != 1 || v[0](4) != 0x1 || v[0](5) != 0x2) {
std::cerr << "FAIL: Wrong input for first frame" << std::endl;
return;
}
if(v[1](0) != 1 || v[1](4) != 0x3 || v[1](5) != 0x4) {
std::cerr << "FAIL: Wrong input for second frame first subframe" << std::endl;
return;
}
if(v[2](0) != 0 || v[2](4) != 0x5 || v[2](5) != 0x6) {
std::cerr << "FAIL: Wrong input for second frame second subframe" << std::endl;
return;
}
if(v[3](0) != 1 || v[3](4) != 0 || v[3](5) != 0) {
std::cerr << "FAIL: Wrong input for third frame" << std::endl;
return;
}
if(v[4](0) != 1 || v[4](4) != 0 || v[4](5) != 0) {
std::cerr << "FAIL: Wrong input for fourth frame" << std::endl;
return;
}
if(v[5](0) != 1 || v[5](4) != 0 || v[5](5) != 0) {
std::cerr << "FAIL: Wrong input for fifth frame" << std::endl;
return;
}
if(v[6](0) != 1 || v[6](4) != 0 || v[6](5) != 0) {
std::cerr << "FAIL: Wrong input for sixth frame" << std::endl;
return;
}
std::cerr << "PASS!" << std::endl;
}
int main()
{
truncate_past_complete_frame();
truncate_past_incomplete_frame();
truncate_current_complete_frame();
truncate_current_incomplete_frame();
truncate_future_complete_frame();
truncate_future_incomplete_frame();
return 0;
}

98
png.cpp Normal file
View file

@ -0,0 +1,98 @@
#include "png.hpp"
#include <fstream>
#include <iostream>
#include <cstdint>
#include <boost/iostreams/categories.hpp>
#include <boost/iostreams/copy.hpp>
#include <boost/iostreams/stream.hpp>
#include <boost/iostreams/stream_buffer.hpp>
#include <boost/iostreams/filter/symmetric.hpp>
#include <boost/iostreams/filter/zlib.hpp>
#include <boost/iostreams/filtering_stream.hpp>
#include <boost/iostreams/device/back_inserter.hpp>
#include <zlib.h>
namespace
{
void encode32(char* _buf, uint32_t val) throw()
{
unsigned char* buf = reinterpret_cast<unsigned char*>(_buf);
buf[0] = ((val >> 24) & 0xFF);
buf[1] = ((val >> 16) & 0xFF);
buf[2] = ((val >> 8) & 0xFF);
buf[3] = (val & 0xFF);
}
class png_hunk_output
{
public:
typedef char char_type;
struct category : boost::iostreams::closable_tag, boost::iostreams::sink_tag {};
png_hunk_output(std::ostream& _os, uint32_t _type)
: os(_os), type(_type)
{
}
void close()
{
uint32_t crc = crc32(0, NULL, 0);
char fixed[12];
encode32(fixed, stream.size());
encode32(fixed + 4, type);
crc = crc32(crc, reinterpret_cast<Bytef*>(fixed + 4), 4);
if(stream.size() > 0)
crc = crc32(crc, reinterpret_cast<Bytef*>(&stream[0]), stream.size());
encode32(fixed + 8, crc);
os.write(fixed, 8);
os.write(&stream[0], stream.size());
os.write(fixed + 8, 4);
}
std::streamsize write(const char* s, std::streamsize n)
{
size_t oldsize = stream.size();
stream.resize(oldsize + n);
memcpy(&stream[oldsize], s, n);
return n;
}
protected:
std::vector<char> stream;
std::ostream& os;
uint32_t type;
};
}
void save_png_data(const std::string& file, uint8_t* data24, uint32_t width, uint32_t height) throw(std::bad_alloc,
std::runtime_error)
{
char* data = reinterpret_cast<char*>(data24);
std::ofstream filp(file.c_str());
if(!filp)
throw std::runtime_error("Can't open target PNG file");
char png_magic[] = {-119, 80, 78, 71, 13, 10, 26, 10};
filp.write(png_magic, sizeof(png_magic));
char ihdr[] = {25, 25, 25, 25, 25, 25, 25, 25, 8, 2, 0, 0, 0};
boost::iostreams::stream<png_hunk_output> ihdr_h(filp, 0x49484452);
encode32(ihdr, width);
encode32(ihdr + 4, height);
ihdr_h.write(ihdr, sizeof(ihdr));
ihdr_h.close();
boost::iostreams::filtering_ostream idat_h;
boost::iostreams::zlib_params params;
params.noheader = false;
idat_h.push(boost::iostreams::zlib_compressor(params));
idat_h.push(png_hunk_output(filp, 0x49444154));
for(uint32_t i = 0; i < height; i++) {
char identity_filter = 0;
idat_h.write(&identity_filter, 1);
idat_h.write(data + i * 3 * width, 3 * width);
}
idat_h.pop();
idat_h.pop();
boost::iostreams::stream<png_hunk_output> iend_h(filp, 0x49454E44);
iend_h.close();
if(!filp)
throw std::runtime_error("Can't write target PNG file");
}

11
png.hpp Normal file
View file

@ -0,0 +1,11 @@
#ifndef _png__hpp__included__
#define _png__hpp__included__
#include <stdexcept>
#include <cstdint>
#include <string>
void save_png_data(const std::string& file, uint8_t* data24, uint32_t width, uint32_t height) throw(std::bad_alloc,
std::runtime_error);
#endif

506
render.cpp Normal file
View file

@ -0,0 +1,506 @@
#include "lsnes.hpp"
#include <snes/snes.hpp>
#include "render.hpp"
#include "png.hpp"
#include <sstream>
#include <list>
#include <iomanip>
#include <cstdint>
#include <string>
typedef uint8_t uint8;
typedef uint16_t uint16;
typedef uint32_t uint32;
typedef int8_t int8;
typedef int16_t int16;
typedef int32_t int32;
#include <nall/platform.hpp>
#include <nall/endian.hpp>
#include <nall/varint.hpp>
#include <nall/bit.hpp>
#include <nall/serializer.hpp>
#include <nall/property.hpp>
using namespace nall;
#include <ui-libsnes/libsnes.hpp>
using namespace SNES;
extern uint32_t fontdata[];
namespace
{
inline uint32_t blend(uint32_t orig, uint16_t ialpha, uint32_t pl, uint32_t ph) throw()
{
const uint32_t X = 0xFF00FFU;
const uint32_t Y = 0xFF00FF00U;
return ((ialpha * ((orig >> 8) & X) + ph) & Y) | (((ialpha * (orig & X) + pl)
>> 8) & X);
}
//This is Jenkin's MIX function.
uint32_t keyhash(uint32_t key, uint32_t item, uint32_t mod) throw()
{
uint32_t a = key;
uint32_t b = 0;
uint32_t c = item;
a=a-b; a=a-c; a=a^(c >> 13);
b=b-c; b=b-a; b=b^(a << 8);
c=c-a; c=c-b; c=c^(b >> 13);
a=a-b; a=a-c; a=a^(c >> 12);
b=b-c; b=b-a; b=b^(a << 16);
c=c-a; c=c-b; c=c^(b >> 5);
a=a-b; a=a-c; a=a^(c >> 3);
b=b-c; b=b-a; b=b^(a << 10);
c=c-a; c=c-b; c=c^(b >> 15);
return c % mod;
}
}
//Locate glyph in font. Returns <width, offset> pair. Zero offset should be interpretted as an empty
//glyph.
std::pair<uint32_t, size_t> find_glyph(uint32_t codepoint, int32_t x, int32_t y, int32_t orig_x,
int32_t& next_x, int32_t& next_y) throw()
{
uint32_t cwidth = 0;
if(codepoint == 9) {
cwidth = 64 - (x - orig_x) % 64;
next_x = x + cwidth;
next_y = y;
return std::make_pair(cwidth, 0);
} else if(codepoint == 10) {
next_x = orig_x;
next_y = y + 16;
return std::make_pair(0, 0);
} else if(codepoint == 32) {
next_x = x + 8;
next_y = y;
return std::make_pair(8, 0);
} else {
uint32_t mdir = fontdata[0];
uint32_t mseed = fontdata[mdir];
uint32_t msize = fontdata[mdir + 1];
uint32_t midx = keyhash(mseed, codepoint, msize);
uint32_t sdir = fontdata[mdir + 2 + midx];
if(!fontdata[sdir + 1]) {
//Character not found.
next_x = x + 8;
next_y = y;
return std::make_pair(8, 0);
}
uint32_t sseed = fontdata[sdir];
uint32_t ssize = fontdata[sdir + 1];
uint32_t sidx = keyhash(sseed, codepoint, ssize);
if(fontdata[sdir + 2 + 2 * sidx] != codepoint) {
//Character not found.
next_x = x + 8;
next_y = y;
return std::make_pair(8, 0);
}
bool wide = (fontdata[fontdata[sdir + 2 + 2 * sidx + 1]] != 0);
next_x = x + (wide ? 16 : 8);
next_y = y;
return std::make_pair(wide ? 16 : 8, fontdata[sdir + 2 + 2 * sidx + 1] + 1);
}
}
render_object::~render_object() throw()
{
}
void render_text(struct screen& scr, int32_t _x, int32_t _y, const std::string& _text, uint32_t _fg,
uint16_t _fgalpha, uint32_t _bg, uint16_t _bgalpha) throw(std::bad_alloc)
{
render_object_text tmp(_x, _y, _text, _fg, _fgalpha, _bg, _bgalpha);
tmp(scr);
}
render_object_text::render_object_text(int32_t _x, int32_t _y, const std::string& _text, uint32_t _fg,
uint16_t _fgalpha, uint32_t _bg, uint16_t _bgalpha) throw(std::bad_alloc)
: x(_x), y(_y), fg(_fg), fgalpha(_fgalpha), bg(_bg), bgalpha(_bgalpha), text(_text)
{
}
void render_queue::add(struct render_object& obj) throw(std::bad_alloc)
{
q.push_back(&obj);
}
void render_queue::run(struct screen& scr) throw()
{
for(auto i = q.begin(); i != q.end(); i++) {
try {
(**i)(scr);
} catch(...) {
}
delete *i;
}
q.clear();
}
void render_queue::clear() throw()
{
for(auto i = q.begin(); i != q.end(); i++)
delete *i;
q.clear();
}
render_queue::~render_queue() throw()
{
clear();
}
uint32_t screen::make_color(uint8_t r, uint8_t g, uint8_t b) throw()
{
return (static_cast<uint32_t>(r) << active_rshift) |
(static_cast<uint32_t>(g) << active_gshift) |
(static_cast<uint32_t>(b) << active_bshift);
}
lcscreen::lcscreen(const uint16_t* mem, bool hires, bool interlace, bool overscan, bool region) throw()
{
uint32_t dataoffset = 0;
width = hires ? 512 : 256;
height = 0;
if(region) {
//PAL.
height = 239;
dataoffset = overscan ? 9 : 1;
} else {
//presumably NTSC.
height = 224;
dataoffset = overscan ? 16 : 9;
}
if(interlace)
height <<= 1;
memory = mem + dataoffset * 1024;
pitch = interlace ? 512 : 1024;
user_memory = false;
}
lcscreen::lcscreen(const uint16_t* mem, uint32_t _width, uint32_t _height) throw()
{
width = _width;
height = _height;
memory = mem;
pitch = width;
user_memory = false;
}
lcscreen::lcscreen() throw()
{
width = 0;
height = 0;
memory = NULL;
user_memory = true;
pitch = 0;
allocated = 0;
}
lcscreen::lcscreen(const lcscreen& ls) throw(std::bad_alloc)
{
width = ls.width;
height = ls.height;
pitch = width;
user_memory = true;
allocated = static_cast<size_t>(width) * height;
memory = new uint16_t[allocated];
for(size_t l = 0; l < height; l++)
memcpy(const_cast<uint16_t*>(memory + l * width), ls.memory + l * ls.pitch, 2 * width);
}
lcscreen& lcscreen::operator=(const lcscreen& ls) throw(std::bad_alloc, std::runtime_error)
{
if(!user_memory)
throw std::runtime_error("Can't copy to non-user memory");
if(this == &ls)
return *this;
if(allocated < static_cast<size_t>(ls.width) * ls.height) {
size_t p_allocated = static_cast<size_t>(ls.width) * ls.height;
memory = new uint16_t[p_allocated];
allocated = p_allocated;
}
width = ls.width;
height = ls.height;
pitch = width;
for(size_t l = 0; l < height; l++)
memcpy(const_cast<uint16_t*>(memory + l * width), ls.memory + l * ls.pitch, 2 * width);
return *this;
}
lcscreen::~lcscreen()
{
if(user_memory)
delete[] const_cast<uint16_t*>(memory);
}
void lcscreen::load(const std::vector<char>& data) throw(std::bad_alloc, std::runtime_error)
{
if(!user_memory)
throw std::runtime_error("Can't load to non-user memory");
const uint8_t* data2 = reinterpret_cast<const uint8_t*>(&data[0]);
if(data.size() < 2)
throw std::runtime_error("Corrupt saved screenshot data");
uint32_t _width = static_cast<uint32_t>(data2[0]) * 256 + static_cast<uint32_t>(data2[1]);
if(_width > 1 && data.size() % (2 * _width) != 2)
throw std::runtime_error("Corrupt saved screenshot data");
uint32_t _height = (data.size() - 2) / (2 * _width);
if(allocated < static_cast<size_t>(_width) * _height) {
size_t p_allocated = static_cast<size_t>(_width) * _height;
memory = new uint16_t[p_allocated];
allocated = p_allocated;
}
uint16_t* mem = const_cast<uint16_t*>(memory);
width = _width;
height = _height;
pitch = width;
for(size_t i = 0; i < (data.size() - 2) / 2; i++)
mem[i] = static_cast<uint16_t>(data2[2 + 2 * i]) * 256 +
static_cast<uint16_t>(data2[2 + 2 * i + 1]);
}
void lcscreen::save(std::vector<char>& data) throw(std::bad_alloc)
{
data.resize(2 + 2 * static_cast<size_t>(width) * height);
uint8_t* data2 = reinterpret_cast<uint8_t*>(&data[0]);
data2[0] = (width >> 8);
data2[1] = width;
for(size_t i = 0; i < (data.size() - 2) / 2; i++) {
data[2 + 2 * i] = memory[(i / width) * pitch + (i % width)] >> 8;
data[2 + 2 * i + 1] = memory[(i / width) * pitch + (i % width)];
}
}
void lcscreen::save_png(const std::string& file) throw(std::bad_alloc, std::runtime_error)
{
unsigned char clevels[32];
for(unsigned i = 0; i < 32; i++)
clevels[i] = 255 * i / 31;
uint8_t* buffer = new uint8_t[3 * static_cast<size_t>(width) * height];
for(uint32_t j = 0; j < height; j++)
for(uint32_t i = 0; i < width; i++) {
uint16_t word = memory[pitch * j + i];
buffer[3 * static_cast<size_t>(width) * j + 3 * i + 0] = clevels[(word >> 10) & 0x1F];
buffer[3 * static_cast<size_t>(width) * j + 3 * i + 1] = clevels[(word >> 5) & 0x1F];
buffer[3 * static_cast<size_t>(width) * j + 3 * i + 2] = clevels[(word) & 0x1F];
}
try {
save_png_data(file, buffer, width, height);
delete[] buffer;
} catch(...) {
delete[] buffer;
throw;
}
}
void render_object_text::operator()(struct screen& scr) throw()
{
uint32_t pfgl = (fg & 0xFF00FF) * fgalpha;
uint32_t pfgh = ((fg >> 8) & 0xFF00FF) * fgalpha;
uint32_t pbgl = (bg & 0xFF00FF) * bgalpha;
uint32_t pbgh = ((bg >> 8) & 0xFF00FF) * bgalpha;
uint16_t ifga = 256 - fgalpha;
uint16_t ibga = 256 - bgalpha;
int32_t orig_x = x;
uint32_t unicode_code = 0;
uint8_t unicode_left = 0;
for(size_t i = 0; i < text.length(); i++) {
uint8_t ch = text[i];
if(ch < 128)
unicode_code = text[i];
else if(ch < 192) {
if(!unicode_left)
continue;
unicode_code = 64 * unicode_code + ch - 128;
if(--unicode_left)
continue;
} else if(ch < 224) {
unicode_code = ch - 192;
unicode_left = 1;
continue;
} else if(ch < 240) {
unicode_code = ch - 224;
unicode_left = 2;
continue;
} else if(ch < 248) {
unicode_code = ch - 240;
unicode_left = 3;
continue;
} else
continue;
int32_t next_x, next_y;
auto p = find_glyph(unicode_code, x, y, orig_x, next_x, next_y);
uint32_t dx = 0;
uint32_t dw = p.first;
uint32_t dy = 0;
uint32_t dh = 16;
uint32_t cx = static_cast<uint32_t>(static_cast<int32_t>(scr.originx) + x);
uint32_t cy = static_cast<uint32_t>(static_cast<int32_t>(scr.originy) + y);
while(cx > scr.width && dw > 0) {
dx++;
dw--;
cx++;
}
while(cy > scr.height && dh > 0) {
dy++;
dh--;
cy++;
}
while(cx + dw > scr.width && dw > 0)
dw--;
while(cy + dh > scr.height && dh > 0)
dh--;
if(!dw || !dh)
continue; //Outside screen.
if(p.second == 0) {
//Blank glyph.
for(uint32_t j = 0; j < dh; j++) {
uint32_t* base = scr.rowptr(cy + j) + cx;
for(uint32_t i = 0; i < dw; i++)
base[i] = blend(base[i], ibga, pbgl, pbgh);
}
} else {
//narrow/wide glyph.
for(uint32_t j = 0; j < dh; j++) {
uint32_t dataword = fontdata[p.second + (dy + j) / (32 / p.first)];
uint32_t* base = scr.rowptr(cy + j) + cx;
for(uint32_t i = 0; i < dw; i++)
if(((dataword >> (31 - ((dy + j) % (32 / p.first)) * p.first - (dx + i))) & 1))
base[i] = blend(base[i], ifga, pfgl, pfgh);
else
base[i] = blend(base[i], ibga, pbgl, pbgh);
}
}
x = next_x;
y = next_y;
}
}
void screen::copy_from(lcscreen& scr, uint32_t hscale, uint32_t vscale) throw()
{
uint32_t copyable_width = (width - originx) / hscale;
uint32_t copyable_height = (height - originy) / vscale;
copyable_width = (copyable_width > scr.width) ? scr.width : copyable_width;
copyable_height = (copyable_height > scr.height) ? scr.height : copyable_height;
for(uint32_t y = 0; y < height; y++) {
memset(rowptr(y), 0, 4 * width);
}
for(uint32_t y = 0; y < copyable_height; y++) {
uint32_t line = y * vscale + originy;
uint32_t* ptr = rowptr(line) + originx;
const uint16_t* sbase = scr.memory + y * scr.pitch;
for(uint32_t x = 0; x < copyable_width; x++) {
uint32_t c = palette[sbase[x] % 32768];
for(uint32_t i = 0; i < hscale; i++)
*(ptr++) = c;
}
for(uint32_t j = 1; j < vscale; j++)
memcpy(rowptr(line + j), rowptr(line), 4 * hscale * copyable_width);
}
}
void screen::reallocate(uint32_t _width, uint32_t _height, uint32_t _originx, uint32_t _originy, bool upside_down)
throw(std::bad_alloc)
{
if(_width == width && _height == height) {
originx = _originx;
originy = _originy;
return;
}
if(!_width || !_height) {
width = height = originx = originy = pitch = 0;
if(memory && !user_memory)
delete[] memory;
memory = NULL;
user_memory = false;
flipped = upside_down;
return;
}
uint32_t* newmem = new uint32_t[_width * _height];
width = _width;
height = _height;
originx = _originx;
originy = _originy;
pitch = 4 * _width;
if(memory && !user_memory)
delete[] memory;
memory = newmem;
user_memory = false;
flipped = upside_down;
}
void screen::set(uint32_t* _memory, uint32_t _width, uint32_t _height, uint32_t _originx, uint32_t _originy,
uint32_t _pitch) throw()
{
if(memory && !user_memory)
delete[] memory;
width = _width;
height = _height;
originx = _originx;
originy = _originy;
pitch = _pitch;
user_memory = true;
memory = _memory;
flipped = false;
}
uint32_t* screen::rowptr(uint32_t row) throw()
{
if(flipped)
row = height - row - 1;
return reinterpret_cast<uint32_t*>(reinterpret_cast<uint8_t*>(memory) + row * pitch);
}
screen::screen() throw()
{
uint32_t _magic = 403703808;
uint8_t* magic = reinterpret_cast<uint8_t*>(&_magic);
memory = NULL;
width = height = originx = originy = pitch = 0;
user_memory = false;
flipped = false;
active_rshift = active_gshift = active_bshift = 255;
set_palette(magic[0], magic[1], magic[2]);
}
screen::~screen() throw()
{
if(memory && !user_memory)
delete[] memory;
}
void screen::set_palette(uint32_t rshift, uint32_t gshift, uint32_t bshift) throw()
{
if(rshift == active_rshift && gshift == active_gshift && bshift == active_bshift)
return;
uint32_t old_rshift = active_rshift;
uint32_t old_gshift = active_gshift;
uint32_t old_bshift = active_bshift;
uint32_t xpalette[32];
for(unsigned i = 0; i < 32; i++)
xpalette[i] = (i * 255 / 31);
for(unsigned i = 0; i < 32768; i++) {
palette[i] = (xpalette[(i >> 10) & 31] << rshift) +
(xpalette[(i >> 5) & 31] << gshift) +
(xpalette[i & 31] << bshift);
}
active_rshift = rshift;
active_gshift = gshift;
active_bshift = bshift;
//Convert the data.
for(uint32_t j = 0; j < height; j++) {
uint32_t* rp = rowptr(j);
for(uint32_t i = 0; i < width; i++) {
uint32_t x = rp[i];
uint32_t r = (x >> old_rshift) & 0xFF;
uint32_t g = (x >> old_gshift) & 0xFF;
uint32_t b = (x >> old_bshift) & 0xFF;
x = (r << active_rshift) | (g << active_gshift) | (b << active_bshift);
rp[i] = x;
}
}
}
render_object_text::~render_object_text() throw()
{
}

345
render.hpp Normal file
View file

@ -0,0 +1,345 @@
#ifndef _render__hpp__included__
#define _render__hpp__included__
#include <cstdint>
#include <string>
#include <list>
#include <vector>
#include <stdexcept>
/**
* \brief Low color (32768 colors) screen from buffer.
*/
struct lcscreen
{
/**
* \brief Create new screen from bsnes output data.
*
* \param mem The output buffer from bsnes.
* \param hires True if in hires mode (512-wide lines instead of 256-wide).
* \param interlace True if in interlace mode.
* \param overscan True if overscan is enabled.
* \param region True if PAL, false if NTSC.
*/
lcscreen(const uint16_t* mem, bool hires, bool interlace, bool overscan, bool region) throw();
lcscreen() throw();
lcscreen(const uint16_t* mem, uint32_t _width, uint32_t _height) throw();
lcscreen(const lcscreen& ls) throw(std::bad_alloc);
lcscreen& operator=(const lcscreen& ls) throw(std::bad_alloc, std::runtime_error);
void load(const std::vector<char>& data) throw(std::bad_alloc, std::runtime_error);
void save(std::vector<char>& data) throw(std::bad_alloc);
void save_png(const std::string& file) throw(std::bad_alloc, std::runtime_error);
/**
* \brief Destructor.
*/
~lcscreen();
/**
* \brief True if memory is allocated by new[] and should be freed by the destructor., false otherwise.
*/
bool user_memory;
/**
* \brief Memory, 1 element per pixel in left-to-right, top-to-bottom order, 15 low bits of each element used.
*/
const uint16_t* memory;
/**
* \brief Number of elements (not bytes) between two successive scanlines.
*/
uint32_t pitch;
/**
* \brief Width of image.
*/
uint32_t width;
/**
* \brief Height of image.
*/
uint32_t height;
/**
* \brief Image allocated size (only valid for user_memory).
*/
size_t allocated;
};
/**
* \brief Truecolor modifiable screen.
*/
struct screen
{
/**
* \brief Create new screen
*
* Creates screen. The screen dimensions are initially 0x0.
*/
screen() throw();
/**
* \brief Destructor.
*/
~screen() throw();
/**
* \brief Set the screen to use specified backing memory.
*
* Sets the backing memory for screen. The specified memory is not freed if screen is reallocated or destroyed.
*
* \param _memory The memory buffer.
* \param _width Width of screen.
* \param _height Height of screen.
* \param _originx X coordinate for origin.
* \param _originy Y coordinate for origin.
* \param _pitch Distance in bytes between successive scanlines.
*/
void set(uint32_t* _memory, uint32_t _width, uint32_t _height, uint32_t _originx, uint32_t _originy,
uint32_t _pitch) throw();
/**
* \brief Set new size for screen, reallocating backing memory.
*
* Sets the size of the screen. The memory is freed if screen is reallocated or destroyed.
*
* \param _width Width of screen.
* \param _height Height of screen.
* \param _originx X coordinate for origin.
* \param _originy Y coordinate for origin.
* \param upside_down If true, image is upside down in memory.
* \throws std::bad_alloc Not enough memory.
*/
void reallocate(uint32_t _width, uint32_t _height, uint32_t _originx, uint32_t _originy,
bool upside_down = false) throw(std::bad_alloc);
/**
* \brief Paint low-color screen into screen.
*
* Paints low-color screen into screen. The upper-left of image will be at origin. Scales the image by given factors.
* If the image does not fit with specified scale factors, it is clipped.
*
* \param scr The screen to paint.
* \param hscale Horizontal scale factor.
* \param vscale Vertical scale factor.
*/
void copy_from(lcscreen& scr, uint32_t hscale, uint32_t vscale) throw();
/**
* \brief Get pointer into specified row.
*
* \param row Number of row (must be less than height).
*/
uint32_t* rowptr(uint32_t row) throw();
/**
* \brief Backing memory for this screen.
*/
uint32_t* memory;
/**
* \brief True if memory is given by user and must not be freed.
*/
bool user_memory;
/**
* \brief Width of screen.
*/
uint32_t width;
/**
* \brief Height of screen.
*/
uint32_t height;
/**
* \brief Distance between lines in bytes.
*/
size_t pitch;
/**
* \brief True if image is upside down in memory.
*/
bool flipped;
/**
* \brief X-coordinate of origin.
*/
uint32_t originx;
/**
* \brief Y-coordinate of origin.
*/
uint32_t originy;
/**
* \brief Palette.
*/
uint32_t palette[32768];
/**
* \brief Set the palette shifts.
*
* Sets the palette shifts, converting the existing image.
*
* \param rshift Shift for red component.
* \param gshift Shift for green component.
* \param bshift Shift for blue component.
*/
void set_palette(uint32_t rshift, uint32_t gshift, uint32_t bshift) throw();
/**
* \brief Return a color value.
*
* Returns color value with specified (r,g,b) values (scale 0-255).
*
* \param r Red component.
* \param g Green component.
* \param b Blue component.
* \return color element value.
*/
uint32_t make_color(uint8_t r, uint8_t g, uint8_t b) throw();
/**
* \brief Current red component shift.
*/
uint32_t active_rshift;
/**
* \brief Current green component shift.
*/
uint32_t active_gshift;
/**
* \brief Current blue component shift.
*/
uint32_t active_bshift;
private:
screen(const screen&);
screen& operator=(const screen&);
};
/**
* \brief Base class for objects to render.
*/
struct render_object
{
/**
* \brief Destructor.
*/
virtual ~render_object() throw();
/**
* \brief Draw the object.
*
* \param scr The screen to draw it on.
*/
virtual void operator()(struct screen& scr) throw() = 0;
};
/**
* \brief Queue of render operations.
*/
struct render_queue
{
/**
* \brief Add object to render queue.
*
* Adds new object to render queue. The object must be allocated by new.
*
* \param obj The object to add
* \throws std::bad_alloc Not enough memory.
*/
void add(struct render_object& obj) throw(std::bad_alloc);
/**
* \brief Apply all objects in order.
*
* Applies all objects in the queue in order, freeing them in progress.
*
* \param scr The screen to apply queue to.
*/
void run(struct screen& scr) throw();
/**
* \brief Clear the queue.
*
* Frees all objects in the queue.
*
*/
void clear() throw();
/**
* \brief Destructor.
*/
~render_queue() throw();
private:
std::list<struct render_object*> q;
};
/**
* \brief Render object rendering given text.
*/
struct render_object_text : public render_object
{
/**
* \brief Constructor.
*
* \param _x The x position to render to (relative to origin).
* \param _y The y position to render to (relative to origin).
* \param _text The text to render.
* \param _fg Foreground color.
* \param _fgalpha Foreground alpha (0-256).
* \param _bg Background color.
* \param _bgalpha Background alpha (0-256).
*/
render_object_text(int32_t _x, int32_t _y, const std::string& _text, uint32_t _fg = 0xFFFFFFFFU,
uint16_t _fgalpha = 255, uint32_t _bg = 0, uint16_t _bgalpha = 0) throw(std::bad_alloc);
~render_object_text() throw();
/**
* \brief Draw the text.
*/
void operator()(struct screen& scr) throw();
private:
int32_t x;
int32_t y;
uint32_t fg;
uint16_t fgalpha;
uint32_t bg;
uint16_t bgalpha;
std::string text;
};
/**
* \brief Read font data for glyph.
*
* \param codepoint Code point of glyph.
* \param x X position to render into.
* \param y Y position to render into.
* \param orig_x X position at start of row.
* \param next_x X position for next glyph is written here.
* \param next_y Y position for next glyph is written here.
* \return Two components: First is width of character, second is its offset in font data (0 if blank glyph).
*/
std::pair<uint32_t, size_t> find_glyph(uint32_t codepoint, int32_t x, int32_t y, int32_t orig_x,
int32_t& next_x, int32_t& next_y) throw();
/**
* \brief Render text into screen.
*
* \param _x The x position to render to (relative to origin).
* \param _y The y position to render to (relative to origin).
* \param _text The text to render.
* \param _fg Foreground color.
* \param _fgalpha Foreground alpha (0-256).
* \param _bg Background color.
* \param _bgalpha Background alpha (0-256).
*/
void render_text(struct screen& scr, int32_t _x, int32_t _y, const std::string& _text, uint32_t _fg = 0xFFFFFFFFU,
uint16_t _fgalpha = 255, uint32_t _bg = 0, uint16_t _bgalpha = 0) throw(std::bad_alloc);
#endif

844
rom.cpp Normal file
View file

@ -0,0 +1,844 @@
#include "lsnes.hpp"
#include <snes/snes.hpp>
using SNES::config;
using SNES::System;
using SNES::Cartridge;
using SNES::Interface;
using SNES::cartridge;
#include <boost/iostreams/categories.hpp>
#include <boost/iostreams/copy.hpp>
#include <boost/iostreams/stream.hpp>
#include <boost/iostreams/stream_buffer.hpp>
#include <boost/iostreams/filter/symmetric.hpp>
#include <boost/iostreams/filter/zlib.hpp>
#include <boost/iostreams/filtering_stream.hpp>
#include <boost/iostreams/device/back_inserter.hpp>
#include "rom.hpp"
#include "fieldsplit.hpp"
#include "zip.hpp"
#include "misc.hpp"
#include "memorymanip.hpp"
#include <stdexcept>
#include <sstream>
#include <iomanip>
#include <cstdint>
#include <set>
typedef uint8_t uint8;
typedef uint16_t uint16;
typedef uint32_t uint32;
typedef int8_t int8;
typedef int16_t int16;
typedef int32_t int32;
#include <nall/platform.hpp>
#include <nall/endian.hpp>
#include <nall/varint.hpp>
#include <nall/bit.hpp>
#include <nall/serializer.hpp>
#include <nall/property.hpp>
using namespace nall;
#include <ui-libsnes/libsnes.hpp>
//Some anti-typo defs.
#define SNES_TYPE "snes"
#define SNES_PAL "snes_pal"
#define SNES_NTSC "snes_ntsc"
#define BSX "bsx"
#define BSXSLOTTED "bsxslotted"
#define SUFAMITURBO "sufamiturbo"
#define SGB_TYPE "SGB"
#define SGB_PAL "sgb_pal"
#define SGB_NTSC "sgb_ntsc"
void strip_CR(std::string& x);
std::string gtype::tostring(rom_type rtype, rom_region region) throw(std::bad_alloc, std::runtime_error)
{
switch(rtype) {
case ROMTYPE_SNES:
switch(region) {
case REGION_AUTO: return "snes";
case REGION_NTSC: return "snes_ntsc";
case REGION_PAL: return "snes_pal";
};
case ROMTYPE_SGB:
switch(region) {
case REGION_AUTO: return "sgb";
case REGION_NTSC: return "sgb_ntsc";
case REGION_PAL: return "sgb_pal";
};
case ROMTYPE_BSX: return "bsx";
case ROMTYPE_BSXSLOTTED: return "bsxslotted";
case ROMTYPE_SUFAMITURBO: return "sufamiturbo";
case ROMTYPE_NONE: throw std::runtime_error("tostring: ROMTYPE_NONE");
};
}
std::string gtype::tostring(gametype_t gametype) throw(std::bad_alloc, std::runtime_error)
{
switch(gametype) {
case GT_SNES_NTSC: return "snes_ntsc";
case GT_SNES_PAL: return "snes_pal";
case GT_SGB_NTSC: return "sgb_ntsc";
case GT_SGB_PAL: return "sgb_pal";
case GT_BSX: return "bsx";
case GT_BSX_SLOTTED: return "bsxslotted";
case GT_SUFAMITURBO: return "sufamiturbo";
case GT_INVALID: throw std::runtime_error("tostring: GT_INVALID");
};
}
gametype_t gtype::togametype(rom_type rtype, rom_region region) throw(std::bad_alloc, std::runtime_error)
{
switch(rtype) {
case ROMTYPE_SNES:
switch(region) {
case REGION_AUTO: return GT_SGB_NTSC;
case REGION_NTSC: return GT_SNES_NTSC;
case REGION_PAL: return GT_SNES_PAL;
};
case ROMTYPE_SGB:
switch(region) {
case REGION_AUTO: return GT_SGB_NTSC;
case REGION_NTSC: return GT_SGB_NTSC;
case REGION_PAL: return GT_SGB_PAL;
};
case ROMTYPE_BSX: return GT_BSX;
case ROMTYPE_BSXSLOTTED: return GT_BSX_SLOTTED;
case ROMTYPE_SUFAMITURBO: return GT_SUFAMITURBO;
case ROMTYPE_NONE: throw std::runtime_error("togametype: ROMTYPE_NONE");
};
}
gametype_t gtype::togametype(const std::string& gametype) throw(std::bad_alloc, std::runtime_error)
{
if(gametype == "snes_ntsc")
return GT_SNES_NTSC;
if(gametype == "snes_pal")
return GT_SNES_PAL;
if(gametype == "sgb_ntsc")
return GT_SGB_NTSC;
if(gametype == "sgb_pal")
return GT_SGB_PAL;
if(gametype == "bsx")
return GT_BSX;
if(gametype == "bsxslotted")
return GT_BSX_SLOTTED;
if(gametype == "sufamiturbo")
return GT_SUFAMITURBO;
throw std::runtime_error("Unknown game type '" + gametype + "'");
}
rom_type gtype::toromtype(const std::string& gametype) throw(std::bad_alloc, std::runtime_error)
{
if(gametype == "snes_ntsc")
return ROMTYPE_SNES;
if(gametype == "snes_pal")
return ROMTYPE_SNES;
if(gametype == "snes")
return ROMTYPE_SNES;
if(gametype == "sgb_ntsc")
return ROMTYPE_SGB;
if(gametype == "sgb_pal")
return ROMTYPE_SGB;
if(gametype == "sgb")
return ROMTYPE_SGB;
if(gametype == "bsx")
return ROMTYPE_BSX;
if(gametype == "bsxslotted")
return ROMTYPE_BSXSLOTTED;
if(gametype == "sufamiturbo")
return ROMTYPE_SUFAMITURBO;
throw std::runtime_error("Unknown game type '" + gametype + "'");
}
rom_type gtype::toromtype(gametype_t gametype) throw()
{
switch(gametype) {
case GT_SNES_NTSC: return ROMTYPE_SNES;
case GT_SNES_PAL: return ROMTYPE_SNES;
case GT_SGB_NTSC: return ROMTYPE_SGB;
case GT_SGB_PAL: return ROMTYPE_SGB;
case GT_BSX: return ROMTYPE_BSX;
case GT_BSX_SLOTTED: return ROMTYPE_BSXSLOTTED;
case GT_SUFAMITURBO: return ROMTYPE_SUFAMITURBO;
case GT_INVALID: throw std::runtime_error("toromtype: GT_INVALID");
};
}
rom_region gtype::toromregion(const std::string& gametype) throw(std::bad_alloc, std::runtime_error)
{
if(gametype == "snes_ntsc")
return REGION_NTSC;
if(gametype == "snes_pal")
return REGION_PAL;
if(gametype == "snes")
return REGION_AUTO;
if(gametype == "sgb_ntsc")
return REGION_NTSC;
if(gametype == "sgb_pal")
return REGION_PAL;
if(gametype == "sgb")
return REGION_AUTO;
if(gametype == "bsx")
return REGION_NTSC;
if(gametype == "bsxslotted")
return REGION_NTSC;
if(gametype == "sufamiturbo")
return REGION_NTSC;
throw std::runtime_error("Unknown game type '" + gametype + "'");
}
rom_region gtype::toromregion(gametype_t gametype) throw()
{
switch(gametype) {
case GT_SNES_NTSC: return REGION_NTSC;
case GT_SNES_PAL: return REGION_PAL;
case GT_SGB_NTSC: return REGION_NTSC;
case GT_SGB_PAL: return REGION_PAL;
case GT_BSX: return REGION_NTSC;
case GT_BSX_SLOTTED: return REGION_NTSC;
case GT_SUFAMITURBO: return REGION_NTSC;
case GT_INVALID: throw std::runtime_error("toromregion: GT_INVALID");
};
}
namespace
{
bool option_set(const std::vector<std::string>& cmdline, const std::string& option)
{
for(auto i = cmdline.begin(); i != cmdline.end(); i++)
if(*i == option)
return true;
return false;
}
const char* romtypes_to_recognize[] = {
"rom", "bsx", "bsxslotted", "dmg", "slot-a", "slot-b",
"rom-xml", "bsx-xml", "bsxslotted-xml", "dmg-xml", "slot-a-xml", "slot-b-xml"
};
enum rom_type current_rom_type = ROMTYPE_NONE;
enum rom_region current_region = REGION_NTSC;
uint64_t readval(const std::vector<char>& patch, size_t offset, size_t vsize) throw(std::runtime_error)
{
if(offset >= patch.size() || offset + vsize > patch.size())
throw std::runtime_error("IPS file corrupt");
uint64_t val = 0;
for(size_t i = 0; i < vsize; i++)
val = (val << 8) | static_cast<uint8_t>(patch[offset + i]);
return val;
}
std::string findoption(const std::vector<std::string>& cmdline, const std::string& option)
{
std::string value;
for(auto i = cmdline.begin(); i != cmdline.end(); ++i) {
std::string arg = *i;
if(arg.length() < 3 + option.length())
continue;
if(arg[0] != '-' || arg[1] != '-' || arg.substr(2, option.length()) != option ||
arg[2 + option.length()] != '=')
continue;
if(value == "")
value = arg.substr(3 + option.length());
else
std::cerr << "WARNING: Ignored duplicate option for '" << option << "'." << std::endl;
if(value == "")
throw std::runtime_error("Empty value for '" + option + "' is not allowed");
}
return value;
}
}
loaded_slot::loaded_slot() throw(std::bad_alloc)
{
valid = false;
}
loaded_slot::loaded_slot(const std::string& filename, const std::string& base, bool xml_flag) throw(std::bad_alloc,
std::runtime_error)
{
xml = xml_flag;
if(filename == "") {
valid = false;
return;
}
valid = true;
data = read_file_relative(filename, base);
sha256 = sha256::hash(data);
if(xml) {
size_t osize = data.size();
data.resize(osize + 1);
data[osize] = 0;
}
}
void loaded_slot::patch(const std::vector<char>& patch, int32_t offset) throw(std::bad_alloc, std::runtime_error)
{
bool warned_extend = false;
bool warned_negative = false;
size_t poffset = 0;
if(xml && valid)
data.resize(data.size() - 1);
if(readval(patch, poffset, 5) != 0x5041544348)
throw std::runtime_error("Bad IPS file magic");
poffset += 5;
while(1) {
uint64_t addr = readval(patch, poffset, 3);
if(addr == 0x454F46)
break;
uint64_t len = readval(patch, poffset + 3, 2);
size_t readstride;
size_t roffset;
size_t opsize;
if(len) {
//Verbatim block.
readstride = 1;
roffset = poffset + 5;
opsize = 5 + len;
} else {
//RLE block. Read real size first.
len = readval(patch, poffset + 5, 2);
readstride = 0;
roffset = poffset + 7;
opsize = 8;
}
for(uint64_t i = 0; i < len; i++) {
int64_t baddr = addr + i + offset;
if(baddr < 0) {
if(!warned_negative)
std::cerr << "WARNING: IPS patch tries to modify negative offset. "
<< "Bad patch or offset?" << std::endl;
warned_negative = true;
continue;
} else if(baddr >= static_cast<int64_t>(data.size())) {
if(!warned_extend)
std::cerr << "WARNING: IPS patch tries to extend the ROM. "
<< "Bad patch or offset? " << std::endl;
warned_extend = true;
size_t oldsize = data.size();
data.resize(baddr + 1);
for(size_t j = oldsize; j <= static_cast<uint64_t>(baddr); j++)
data[j] = 0;
}
size_t srcoff = roffset + readstride * i;
if(srcoff >= patch.size())
throw std::runtime_error("Corrupt IPS patch");
data[baddr] = static_cast<uint8_t>(patch[srcoff]);
}
poffset += opsize;
}
//Mark the slot as valid and update hash.
valid = true;
sha256 = sha256::hash(data);
if(xml) {
size_t osize = data.size();
data.resize(osize + 1);
data[osize] = 0;
}
}
rom_files::rom_files() throw()
{
rtype = ROMTYPE_NONE;
region = REGION_AUTO;
}
rom_files::rom_files(const std::vector<std::string>& cmdline, window* win) throw(std::bad_alloc, std::runtime_error)
{
rom = rom_xml = slota = slota_xml = slotb = slotb_xml = "";
std::string arr[sizeof(romtypes_to_recognize) / sizeof(romtypes_to_recognize[0])];
unsigned long flags = 0;
for(size_t i = 0; i < sizeof(romtypes_to_recognize) / sizeof(romtypes_to_recognize[0]); i++) {
arr[i] = findoption(cmdline, romtypes_to_recognize[i]);
if(arr[i] != "")
flags |= (1L << i);
}
rtype = recognize_platform(flags);
for(size_t i = 0; i < sizeof(romtypes_to_recognize) / sizeof(romtypes_to_recognize[0]); i++) {
if(arr[i] != "")
switch(recognize_commandline_rom(rtype, romtypes_to_recognize[i])) {
case 0: rom = arr[i]; break;
case 1: rom_xml = arr[i]; break;
case 2: slota = arr[i]; break;
case 3: slota_xml = arr[i]; break;
case 4: slotb = arr[i]; break;
case 5: slotb_xml = arr[i]; break;
};
}
region = (rtype == ROMTYPE_SGB || rtype == ROMTYPE_SNES) ? REGION_AUTO : REGION_NTSC;
if(option_set(cmdline, "--ntsc"))
region = REGION_NTSC;
else if(option_set(cmdline, "--pal"))
region = REGION_PAL;
base_file = "";
}
void rom_files::resolve_relative() throw(std::bad_alloc, std::runtime_error)
{
rom = resolve_file_relative(rom, base_file);
rom_xml = resolve_file_relative(rom_xml, base_file);
slota = resolve_file_relative(slota, base_file);
slota_xml = resolve_file_relative(slota_xml, base_file);
slotb = resolve_file_relative(slotb, base_file);
slotb_xml = resolve_file_relative(slotb_xml, base_file);
base_file = "";
}
std::pair<enum rom_type, enum rom_region> get_current_rom_info() throw()
{
return std::make_pair(current_rom_type, current_region);
}
loaded_rom::loaded_rom() throw()
{
rtype = ROMTYPE_NONE;
region = orig_region = REGION_AUTO;
}
loaded_rom::loaded_rom(const rom_files& files, window* win) throw(std::bad_alloc, std::runtime_error)
{
std::string _slota = files.slota;
std::string _slota_xml = files.slota_xml;
std::string _slotb = files.slotb;
std::string _slotb_xml = files.slotb_xml;
if(files.rtype == ROMTYPE_NONE) {
rtype = ROMTYPE_NONE;
region = orig_region = files.region;
return;
}
if((_slota != "" || _slota_xml != "") && files.rtype == ROMTYPE_SNES) {
out(win) << "WARNING: SNES takes only 1 ROM image" << std::endl;
_slota = "";
_slota_xml = "";
}
if((_slotb != "" || _slotb_xml != "") && files.rtype != ROMTYPE_SUFAMITURBO) {
out(win) << "WARNING: Only Sufami Turbo takes 3 ROM images" << std::endl;
_slotb = "";
_slotb_xml = "";
}
if(files.rom_xml != "" && files.rom == "")
out(win) << "WARNING: " << name_subrom(files.rtype, 0) << " specified without corresponding "
<< name_subrom(files.rtype, 1) << std::endl;
if(_slota_xml != "" && _slota == "")
out(win) << "WARNING: " << name_subrom(files.rtype, 2) << " specified without corresponding "
<< name_subrom(files.rtype, 3) << std::endl;
if(_slotb_xml != "" && _slotb == "")
out(win) << "WARNING: " << name_subrom(files.rtype, 4) << " specified without corresponding "
<< name_subrom(files.rtype, 5) << std::endl;
rtype = files.rtype;
rom = loaded_slot(files.rom, files.base_file);
rom_xml = loaded_slot(files.rom_xml, files.base_file, true);
slota = loaded_slot(_slota, files.base_file);
slota_xml = loaded_slot(_slota_xml, files.base_file, true);
slotb = loaded_slot(_slotb, files.base_file);
slotb_xml = loaded_slot(_slotb_xml, files.base_file, true);
orig_region = region = files.region;
}
void loaded_rom::load() throw(std::bad_alloc, std::runtime_error)
{
current_rom_type = ROMTYPE_NONE;
if(region == REGION_AUTO && orig_region != REGION_AUTO)
region = orig_region;
if(region != orig_region && orig_region != REGION_AUTO)
throw std::runtime_error("Trying to force incompatible region");
if(rtype == ROMTYPE_NONE)
throw std::runtime_error("Can't insert cartridge of type NONE!");
switch(region) {
case REGION_AUTO:
config.region = System::Region::Autodetect;
break;
case REGION_NTSC:
config.region = System::Region::NTSC;
break;
case REGION_PAL:
config.region = System::Region::PAL;
break;
default:
throw std::runtime_error("Trying to force unknown region");
}
switch(rtype) {
case ROMTYPE_SNES:
if(!snes_load_cartridge_normal(rom_xml, rom, rom))
throw std::runtime_error("Can't load cartridge ROM");
break;
case ROMTYPE_BSX:
if(region == REGION_PAL)
throw std::runtime_error("BSX can't be PAL");
if(!snes_load_cartridge_bsx(rom_xml, rom, rom, slota_xml, slota, slota))
throw std::runtime_error("Can't load cartridge ROM");
break;
case ROMTYPE_BSXSLOTTED:
if(region == REGION_PAL)
throw std::runtime_error("Slotted BSX can't be PAL");
if(!snes_load_cartridge_bsx_slotted(rom_xml, rom, rom, slota_xml, slota, slota))
throw std::runtime_error("Can't load cartridge ROM");
break;
case ROMTYPE_SGB:
if(!snes_load_cartridge_super_game_boy(rom_xml, rom, rom, slota_xml, slota, slota))
throw std::runtime_error("Can't load cartridge ROM");
break;
case ROMTYPE_SUFAMITURBO:
if(region == REGION_PAL)
throw std::runtime_error("Sufami Turbo can't be PAL");
if(!snes_load_cartridge_sufami_turbo(rom_xml, rom, rom, slota_xml, slota, slota, slotb_xml, slotb,
slotb))
throw std::runtime_error("Can't load cartridge ROM");
break;
default:
throw std::runtime_error("Unknown cartridge type");
}
if(region == REGION_AUTO)
region = snes_get_region() ? REGION_PAL : REGION_NTSC;
snes_power();
current_rom_type = rtype;
current_region = region;
refresh_cart_mappings();
}
void loaded_rom::do_patch(const std::vector<std::string>& cmdline, window* win) throw(std::bad_alloc,
std::runtime_error)
{
int32_t offset = 0;
for(auto i = cmdline.begin(); i != cmdline.end(); i++) {
std::string opt = *i;
if(opt.length() >= 13 && opt.substr(0, 13) == "--ips-offset=") {
try {
offset = parse_value<int32_t>(opt.substr(13));
} catch(std::exception& e) {
throw std::runtime_error("Invalid IPS offset option '" + opt + "': " + e.what());
}
continue;
}
if(opt.length() < 6 || opt.substr(0, 6) != "--ips-")
continue;
size_t split = opt.find_first_of("=");
if(split > opt.length())
throw std::runtime_error("Invalid IPS patch argument '" + opt + "'");
std::string kind = opt.substr(6, split - 6);
std::string filename = opt.substr(split + 1);
out(win) << "Patching " << kind << " using '" << filename << "'" << std::endl;
std::vector<char> ips;
try {
ips = read_file_relative(filename, "");
} catch(std::bad_alloc& e) {
OOM_panic(win);
} catch(std::exception& e) {
throw std::runtime_error("Can't read IPS '" + filename + "': " + e.what());
}
try {
switch(recognize_commandline_rom(rtype, kind)) {
case 0: rom.patch(ips, offset); break;
case 1: rom_xml.patch(ips, offset); break;
case 2: slota.patch(ips, offset); break;
case 3: slota_xml.patch(ips, offset); break;
case 4: slotb.patch(ips, offset); break;
case 5: slotb_xml.patch(ips, offset); break;
default:
throw std::runtime_error("Invalid subROM '" + kind + "' to patch");
}
} catch(std::bad_alloc& e) {
OOM_panic(win);
} catch(std::exception& e) {
throw std::runtime_error("Can't Patch with IPS '" + filename + "': " + e.what());
}
}
}
namespace
{
std::string sram_name(const nall::string& _id, Cartridge::Slot slotname)
{
std::string id(_id, _id.length());
if(slotname == Cartridge::Slot::SufamiTurboA)
return "slota." + id.substr(1);
if(slotname == Cartridge::Slot::SufamiTurboB)
return "slotb." + id.substr(1);
return id.substr(1);
}
}
std::map<std::string, std::vector<char>> save_sram() throw(std::bad_alloc)
{
std::map<std::string, std::vector<char>> out;
for(unsigned i = 0; i < cartridge.nvram.size(); i++) {
Cartridge::NonVolatileRAM& r = cartridge.nvram[i];
std::string savename = sram_name(r.id, r.slot);
std::vector<char> x;
x.resize(r.size);
memcpy(&x[0], r.data, r.size);
out[savename] = x;
}
return out;
}
void load_sram(std::map<std::string, std::vector<char>>& sram, window* win) throw(std::bad_alloc)
{
std::set<std::string> used;
if(sram.empty())
return;
for(unsigned i = 0; i < cartridge.nvram.size(); i++) {
Cartridge::NonVolatileRAM& r = cartridge.nvram[i];
std::string savename = sram_name(r.id, r.slot);
if(sram.count(savename)) {
std::vector<char>& x = sram[savename];
if(r.size != x.size())
out(win) << "WARNING: SRAM '" << savename << "': Loaded " << x.size() << " bytes, "
<< " but the SRAM is " << r.size << "." << std::endl;
memcpy(r.data, &x[0], (r.size < x.size()) ? r.size : x.size());
used.insert(savename);
} else
out(win) << "WARNING: SRAM '" << savename << ": No data." << std::endl;
}
for(auto i = sram.begin(); i != sram.end(); ++i)
if(!used.count(i->first))
out(win) << "WARNING: SRAM '" << i->first << ": Not found on cartridge." << std::endl;
}
std::map<std::string, std::vector<char>> load_sram_commandline(const std::vector<std::string>& cmdline)
throw(std::bad_alloc, std::runtime_error)
{
std::map<std::string, std::vector<char>> ret;
for(auto i = cmdline.begin(); i != cmdline.end(); i++) {
std::string opt = *i;
if(opt.length() >= 11 && opt.substr(0, 11) == "--continue=") {
size_t split = opt.find_first_of("=");
if(split > opt.length() - 1)
throw std::runtime_error("Bad SRAM option '" + opt + "'");
std::string file = opt.substr(split + 1);
zip_reader r(file);
for(auto j = r.begin(); j != r.end(); j++) {
std::string fname = *j;
if(fname.length() < 6 || fname.substr(0, 5) != "sram.")
continue;
std::istream& x = r[fname];
try {
std::vector<char> out;
boost::iostreams::back_insert_device<std::vector<char>> rd(out);
boost::iostreams::copy(x, rd);
delete &x;
ret[fname.substr(5, split - 5)] = out;
} catch(...) {
delete &x;
throw;
}
}
continue;
}
if(opt.length() < 8 || opt.substr(0, 7) != "--sram-")
continue;
size_t split = opt.find_first_of("=");
if(split > opt.length() - 1)
throw std::runtime_error("Bad SRAM option '" + opt + "'");
std::string kind = opt.substr(7, split - 7);
std::string file = opt.substr(split + 1);
if(kind == "")
throw std::runtime_error("Bad SRAM option '" + opt + "'");
try {
ret[kind] = read_file_relative(file, "");
} catch(std::bad_alloc& e) {
throw;
} catch(std::runtime_error& e) {
throw std::runtime_error("Can't load SRAM '" + kind + "': " + e.what());
}
}
return ret;
}
void emulate_frame() throw()
{
SNES::system.run();
}
void reset_snes() throw()
{
SNES::system.reset();
}
std::vector<char> save_core_state() throw(std::bad_alloc)
{
SNES::system.runtosave();
std::vector<char> ret;
serializer s = SNES::system.serialize();
ret.resize(s.size());
memcpy(&ret[0], s.data(), s.size());
size_t offset = ret.size();
unsigned char tmp[32];
sha256::hash(tmp, ret);
ret.resize(offset + 32);
memcpy(&ret[offset], tmp, 32);
return ret;
}
void load_core_state(const std::vector<char>& buf) throw(std::runtime_error)
{
if(buf.size() < 32)
throw std::runtime_error("Savestate corrupt");
unsigned char tmp[32];
sha256::hash(tmp, reinterpret_cast<const uint8_t*>(&buf[0]), buf.size() - 32);
if(memcmp(tmp, &buf[buf.size() - 32], 32))
throw std::runtime_error("Savestate corrupt");
serializer s(reinterpret_cast<const uint8_t*>(&buf[0]), buf.size() - 32);
if(!SNES::system.unserialize(s))
throw std::runtime_error("SNES core rejected savestate");
}
namespace
{
struct index_entry
{
std::string hash;
std::string relpath;
std::string from;
};
std::list<index_entry> rom_index;
void replace_index(std::list<index_entry> new_index, const std::string& source)
{
std::list<index_entry> tmp_index;
for(auto i = rom_index.begin(); i != rom_index.end(); i++) {
if(i->from != source)
tmp_index.push_back(*i);
}
for(auto i = new_index.begin(); i != new_index.end(); i++) {
tmp_index.push_back(*i);
}
rom_index = new_index;
}
}
void load_index_file(const std::string& filename) throw(std::bad_alloc, std::runtime_error)
{
std::istream& s = open_file_relative(filename, "");
try {
std::list<index_entry> partial_index;
std::string line;
while(std::getline(s, line)) {
index_entry e;
if(line == "")
continue;
tokensplitter t(line);
e.hash = static_cast<std::string>(t);
e.relpath = t.tail();
e.from = filename;
if(e.hash.length() != 64 || e.relpath == "")
throw std::runtime_error("Bad index file");
partial_index.push_back(e);
}
replace_index(partial_index, filename);
} catch(...) {
delete &s;
throw;
}
delete &s;
}
std::string lookup_file_by_sha256(const std::string& hash) throw(std::bad_alloc, std::runtime_error)
{
if(hash == "")
return "";
for(auto i = rom_index.begin(); i != rom_index.end(); i++) {
if(i->hash != hash)
continue;
try {
std::istream& o = open_file_relative(i->relpath, i->from);
delete &o;
return resolve_file_relative(i->relpath, i->from);
} catch(...) {
continue;
}
}
throw std::runtime_error("No file with hash '" + hash + "' found in known indices");
}
std::string name_subrom(enum rom_type major, unsigned romnumber) throw(std::bad_alloc)
{
if(romnumber == 0)
return "ROM";
else if(romnumber == 1)
return "ROM XML";
else if(major == ROMTYPE_BSX && romnumber == 2)
return "BSX ROM";
else if(major == ROMTYPE_BSX && romnumber == 3)
return "BSX XML";
else if(major == ROMTYPE_BSXSLOTTED && romnumber == 2)
return "BSX ROM";
else if(major == ROMTYPE_BSXSLOTTED && romnumber == 3)
return "BSX XML";
else if(major == ROMTYPE_SGB && romnumber == 2)
return "DMG ROM";
else if(major == ROMTYPE_SGB && romnumber == 3)
return "DMG XML";
else if(major == ROMTYPE_SUFAMITURBO && romnumber == 2)
return "SLOT A ROM";
else if(major == ROMTYPE_SUFAMITURBO && romnumber == 3)
return "SLOT A XML";
else if(major == ROMTYPE_SUFAMITURBO && romnumber == 4)
return "SLOT B ROM";
else if(major == ROMTYPE_SUFAMITURBO && romnumber == 5)
return "SLOT B XML";
else if(romnumber % 2)
return "UNKNOWN XML";
else
return "UNKNOWN ROM";
}
int recognize_commandline_rom(enum rom_type major, const std::string& romname) throw(std::bad_alloc)
{
if(romname == romtypes_to_recognize[0])
return 0;
else if(romname == romtypes_to_recognize[6])
return 1;
else if(major == ROMTYPE_BSX && romname == romtypes_to_recognize[1])
return 2;
else if(major == ROMTYPE_BSX && romname == romtypes_to_recognize[7])
return 3;
else if(major == ROMTYPE_BSX && romname == romtypes_to_recognize[2])
return 2;
else if(major == ROMTYPE_BSX && romname == romtypes_to_recognize[8])
return 3;
else if(major == ROMTYPE_SGB && romname == romtypes_to_recognize[3])
return 2;
else if(major == ROMTYPE_SGB && romname == romtypes_to_recognize[9])
return 3;
else if(major == ROMTYPE_SUFAMITURBO && romname == romtypes_to_recognize[4])
return 2;
else if(major == ROMTYPE_SUFAMITURBO && romname == romtypes_to_recognize[10])
return 3;
else if(major == ROMTYPE_SUFAMITURBO && romname == romtypes_to_recognize[5])
return 4;
else if(major == ROMTYPE_SUFAMITURBO && romname == romtypes_to_recognize[11])
return 5;
else
return -1;
}
rom_type recognize_platform(unsigned long flags) throw(std::bad_alloc, std::runtime_error)
{
if((flags & 07700) >> 6 & ~(flags & 077))
throw std::runtime_error("SubROM XML specified without corresponding subROM");
if((flags & 1) == 0)
throw std::runtime_error("No SNES main cartridge ROM specified");
if((flags & 077) == 1)
return ROMTYPE_SNES;
if((flags & 077) == 3)
return ROMTYPE_BSX;
if((flags & 077) == 5)
return ROMTYPE_BSXSLOTTED;
if((flags & 077) == 9)
return ROMTYPE_SGB;
if((flags & 060) != 0 && (flags & 017) == 1)
return ROMTYPE_SUFAMITURBO;
throw std::runtime_error("Not valid combination of rom/bsx/bsxslotted/dmg/slot-a/slot-b");
}

444
rom.hpp Normal file
View file

@ -0,0 +1,444 @@
#ifndef _rom__hpp__included__
#define _rom__hpp__included__
#include <string>
#include "window.hpp"
#include <map>
#include <vector>
#include <stdexcept>
#include "misc.hpp"
/**
* \brief Region of ROM.
*/
enum rom_region
{
/**
* \brief Autodetect region
*/
REGION_AUTO = 0,
/**
* \brief (force) PAL region
*/
REGION_PAL,
/**
* \brief (force) NTSC region
*/
REGION_NTSC
};
/**
* \brief Major type of ROM
*/
enum rom_type
{
/**
* \brief Ordinary SNES ROM
*/
ROMTYPE_SNES, //ROM is Ordinary SNES ROM.
/**
* \brief BS-X Slotted ROM.
*/
ROMTYPE_BSXSLOTTED,
/**
* \brief BS-X (non-slotted) ROM.
*/
ROMTYPE_BSX,
/**
* \brief Sufami Turbo ROM.
*/
ROMTYPE_SUFAMITURBO,
/**
* \brief Super Game Boy ROM.
*/
ROMTYPE_SGB,
/**
* \brief No ROM.
*/
ROMTYPE_NONE
};
/**
* \brief Type of ROM and region
*
* This enumeration enumerates possible ROM types and regions for those.
*/
enum gametype_t
{
/**
* \brief NTSC-region SNES game
*/
GT_SNES_NTSC = 0,
/**
* \brief PAL-region SNES game
*/
GT_SNES_PAL = 1,
/**
* \brief NTSC-region BSX slotted game
*/
GT_BSX_SLOTTED = 2,
/**
* \brief NTSC-region BSX (non-slotted) game
*/
GT_BSX = 3,
/**
* \brief NTSC-region sufami turbo game
*/
GT_SUFAMITURBO = 4,
/**
* \brief NTSC-region Super Game Boy game
*/
GT_SGB_NTSC = 5,
/**
* \brief PAL-region Super Game Boy game
*/
GT_SGB_PAL = 6,
/**
* \brief Invalid game type
*/
GT_INVALID = 7
};
/**
* \brief Translations between diffrent representations of type.
*/
class gtype
{
public:
static std::string tostring(rom_type rtype, rom_region region) throw(std::bad_alloc, std::runtime_error);
static std::string tostring(gametype_t gametype) throw(std::bad_alloc, std::runtime_error);
static gametype_t togametype(rom_type rtype, rom_region region) throw(std::bad_alloc, std::runtime_error);
static gametype_t togametype(const std::string& gametype) throw(std::bad_alloc, std::runtime_error);
static rom_type toromtype(const std::string& gametype) throw(std::bad_alloc, std::runtime_error);
static rom_type toromtype(gametype_t gametype) throw();
static rom_region toromregion(const std::string& gametype) throw(std::bad_alloc, std::runtime_error);
static rom_region toromregion(gametype_t gametype) throw();
};
/**
* \brief Filenames associated with ROM.
*
* This structure gives all files associated with given ROM image.
*/
struct rom_files
{
/**
* \brief Construct defaults
*/
rom_files() throw();
/**
* \brief Read files from command line arguments.
*
* Reads the filenames out of command line arguments given. Also supports bundle files.
*
* \param cmdline The commmand line
* \throws std::bad_alloc Not enough memory
* \throws std::runtime_error Failed to load ROM filenames.
*/
rom_files(const std::vector<std::string>& cmdline, window* win) throw(std::bad_alloc, std::runtime_error);
/**
* \brief Resolve relative references.
*/
void resolve_relative() throw(std::bad_alloc, std::runtime_error);
/**
* \brief Base ROM image
*
* The file to look other ROM files relative to. May be blank.
*/
std::string base_file;
/**
* \brief Major ROM type.
*/
enum rom_type rtype;
/**
* \brief Game region
*/
enum rom_region region;
/**
* \brief Relative filename of main ROM file.
*/
std::string rom;
/**
* \brief Relative filename of main ROM XML file.
*/
std::string rom_xml;
/**
* \brief Relative filename of slot A ROM file (non-SNES only).
*/
std::string slota;
/**
* \brief Relative filename of slot A XML file (non-SNES only).
*/
std::string slota_xml;
/**
* \brief Relative filename of slot B ROM file (Sufami Turbo only).
*/
std::string slotb;
/**
* \brief Relative filename of slot B XML file (Sufami Turbo only).
*/
std::string slotb_xml;
};
/**
* \brief Loaded data
*
* Some loaded data or indication of no data.
*/
struct loaded_slot
{
/**
* \brief Construct empty slot.
* \throws std::bad_alloc Not enough memory.
*/
loaded_slot() throw(std::bad_alloc);
/**
* \brief Read a slot
*
* This constructor construct slot by reading data from file. If filename is "", constructs an empty slot.
*
* \param filename The filename to read. If "", empty slot is constructed.
* \param base Base filename to interpret the filename against. If "", no base filename is used.
* \param xml_flag If set, always keep trailing NUL.
* \throws std::bad_alloc Not enough memory.
* \throws std::runtime_error Can't load the data.
*/
loaded_slot(const std::string& filename, const std::string& base, bool xml_flag = false) throw(std::bad_alloc,
std::runtime_error);
/**
* \brief IPS-patch a slot
*
* This method patches this slot using specified IPS patch.
*
* \param patch The patch to apply
* \param offset The amount to add to the offsets in the IPS file. Parts with offsets below zero are not patched.
* \throws std::bad_alloc Not enough memory.
* \throws std::runtime_error Bad IPS patch.
*/
void patch(const std::vector<char>& patch, int32_t offset) throw(std::bad_alloc, std::runtime_error);
/**
* \brief Is this slot XML slot?
*/
bool xml;
/**
* \brief True if this slot has valid data
*
* If this slot is blank, this is set to false, data is undefined and sha256 is "". Otherwise this is set to true,
* data to apporiate data, and sha256 to hash of data.
*/
bool valid;
/**
* \brief The actual data for this slot.
*/
std::vector<char> data;
/**
* \brief SHA-256 for the data in this slot.
*
* SHA-256 for the data in this slot if data is valid. If no valid data, this field is "".
*/
std::string sha256;
/**
* \brief Get pointer to loaded data
* \return Pointer to loaded data, or NULL if slot is blank.
*/
operator const char*() const throw()
{
return valid ? reinterpret_cast<const char*>(&data[0]) : NULL;
}
/**
* \brief Get pointer to loaded data
* \return Pointer to loaded data, or NULL if slot is blank.
*/
operator const uint8_t*() const throw()
{
return valid ? reinterpret_cast<const uint8_t*>(&data[0]) : NULL;
}
/**
* \brief Get size of slot
* \return The number of bytes in slot, or 0 if slot is blank.
*/
operator unsigned() const throw()
{
return valid ? data.size() : 0;
}
};
/**
* \brief ROM loaded into memory.
*/
struct loaded_rom
{
/**
* \brief Create blank ROM
*/
loaded_rom() throw();
/**
* \brief Load specified ROM files into memory.
*
* Takes in collection of ROM filenames and loads them into memory.
*
* \throws std::bad_alloc Not enough memory.
* \throws std::runtime_error Loading ROM files failed.
*/
loaded_rom(const rom_files& files, window* win) throw(std::bad_alloc, std::runtime_error);
/**
* \brief ROM type
*/
enum rom_type rtype;
/**
* \brief ROM region
*/
enum rom_region region;
/**
* \brief ROM original region
*/
enum rom_region orig_region;
/**
* \brief Loaded main ROM
*/
loaded_slot rom;
/**
* \brief Loaded main ROM XML
*/
loaded_slot rom_xml;
/**
* \brief Loaded slot A ROM
*/
loaded_slot slota;
/**
* \brief Loaded slot A XML
*/
loaded_slot slota_xml;
/**
* \brief Loaded slot B ROM
*/
loaded_slot slotb;
/**
* \brief Loaded slot B XML
*/
loaded_slot slotb_xml;
/**
* \brief Patch the ROM.
*/
void do_patch(const std::vector<std::string>& cmdline, window* win) throw(std::bad_alloc, std::runtime_error);
/**
* \brief Load this ROM into "SNES".
*
* Switches the active cartridge to this cartridge. The compatiblity between selected region and original region
* is checked. Region is updated after cartridge has been loaded.
*
* \throw std::bad_alloc Not enough memory
* \throw std::runtime_error Switching cartridges failed.
*/
void load() throw(std::bad_alloc, std::runtime_error);
};
int recognize_commandline_rom(enum rom_type major, const std::string& romname) throw(std::bad_alloc);
rom_type recognize_platform(unsigned long flags) throw(std::bad_alloc, std::runtime_error);
/**
* \brief Name a sub-ROM.
*/
std::string name_subrom(enum rom_type major, unsigned romnumber) throw(std::bad_alloc);
/**
* \brief Get major type and region of loaded ROM.
* \return rom type and region of current ROM.
*/
std::pair<enum rom_type, enum rom_region> get_current_rom_info() throw();
/**
* \brief Save all SRAMs
*
* Take current values of all SRAMs in current system and save their contents.
*
* \return Saved SRAM contents.
* \throws std::bad_alloc Out of memory.
*/
std::map<std::string, std::vector<char>> save_sram() throw(std::bad_alloc);
/**
* \brief Load all SRAMs
*
* Write contents of saved SRAMs into current system SRAMs.
*
* \param sram Saved SRAM contents.
* \throws std::bad_alloc Out of memory.
*/
void load_sram(std::map<std::string, std::vector<char>>& sram, window* win) throw(std::bad_alloc);
/**
* \brief Load SRAMs specified on command-line
*
* Read SRAMs from command-line and and load the files.
*
* \param cmdline Command line
* \return The loaded SRAM contents.
* \throws std::bad_alloc Out of memory.
* \throws std::runtime_error Failed to load.
*/
std::map<std::string, std::vector<char>> load_sram_commandline(const std::vector<std::string>& cmdline)
throw(std::bad_alloc, std::runtime_error);
/**
* \brief Emulate a frame
*/
void emulate_frame() throw();
/**
* \brief Reset the SNES
*/
void reset_snes() throw();
/**
* \brief Save core state into buffer
*
* Saves core state into buffer. WARNING: This takes emulated time.
*
* \return The saved state.
* \throws std::bad_alloc Not enough memory.
*/
std::vector<char> save_core_state() throw(std::bad_alloc);
/**
* \brief Restore core state from buffer.
*
* Loads core state from buffer.
*
* \param buf The buffer containing the state.
* \throws std::runtime_error Loading state failed.
*/
void load_core_state(const std::vector<char>& buf) throw(std::runtime_error);
/**
* \brief Read index file.
*
* Read index of ROMs and add ROMs found to content-searchable storage.
*
* \param filename The filename of index file.
* \throws std::bad_alloc Not enough memory.
* \throws std::runtime_error Loading index failed.
*/
void load_index_file(const std::string& filename) throw(std::bad_alloc, std::runtime_error);
/**
* \brief Lookup absolute filename by hash.
*
* Search all indices, looking for file with specified SHA-256 (specifying hash of "" results "").
*
* \param hash The hash of file.
* \return Absolute filename.
* \throws std::bad_alloc Not enough memory.
* \throws std::runtime_error Not found.
*/
std::string lookup_file_by_sha256(const std::string& hash) throw(std::bad_alloc, std::runtime_error);
#endif

248
rrdata.cpp Normal file
View file

@ -0,0 +1,248 @@
#include "rrdata.hpp"
#include <set>
#include <cstring>
#include <string>
#include <iostream>
#include <fstream>
#include <sstream>
#include "misc.hpp"
//
// XABCDEFXXXXXXXXX
// 0123456789XXXXXX
//
// ABCDEF0123456789XXXXXX
rrdata::instance::instance() throw(std::bad_alloc)
{
std::string rnd = get_random_hexstring(2 * RRDATA_BYTES);
memset(bytes, 0, RRDATA_BYTES);
for(unsigned i = 0; i < 2 * RRDATA_BYTES; i++) {
unsigned x = rnd[i];
x = x & 0x1F;
x = x - x / 16 * 9 - 1;
bytes[i / 2] = 16 * bytes[i / 2] + x;
}
}
rrdata::instance::instance(unsigned char* b) throw()
{
memcpy(bytes, b, RRDATA_BYTES);
}
bool rrdata::instance::operator<(const struct instance& i) const throw()
{
for(unsigned j = 0; j < RRDATA_BYTES; j++)
if(bytes[j] < i.bytes[j])
return true;
else if(bytes[j] > i.bytes[j])
return false;
return false;
}
bool rrdata::instance::operator==(const struct instance& i) const throw()
{
for(unsigned j = 0; j < RRDATA_BYTES; j++)
if(bytes[j] != i.bytes[j])
return false;
return true;
}
const struct rrdata::instance rrdata::instance::operator++(int) throw()
{
instance i = *this;
++*this;
return i;
}
const struct rrdata::instance& rrdata::instance::operator++() throw()
{
unsigned carry = 1;
for(unsigned i = 31; i < 32; i--) {
unsigned newcarry = (bytes[i] == 255 && carry);
bytes[i] += carry;
carry = newcarry;
}
}
namespace
{
std::set<rrdata::instance> rrset;
std::ifstream ihandle;
std::ofstream ohandle;
bool handle_open;
std::string current_project;
}
void rrdata::read_base(const std::string& project) throw(std::bad_alloc)
{
if(project == current_project)
return;
std::set<rrdata::instance> new_rrset;
std::string filename = get_config_path(NULL) + "/" + project + ".rr";
if(handle_open) {
ohandle.close();
handle_open = false;
}
ihandle.open(filename.c_str(), std::ios_base::in);
while(ihandle) {
unsigned char bytes[RRDATA_BYTES];
ihandle.read(reinterpret_cast<char*>(bytes), RRDATA_BYTES);
instance k(bytes);
//std::cerr << "Loaded symbol: " << k << std::endl;
new_rrset.insert(k);
}
ihandle.close();
ohandle.open(filename.c_str(), std::ios_base::out | std::ios_base::app);
if(ohandle)
handle_open = true;
rrset = new_rrset;
current_project = project;
}
void rrdata::close() throw()
{
current_project = "";
if(handle_open)
ohandle.close();
handle_open = false;
}
void rrdata::add(const struct rrdata::instance& i) throw(std::bad_alloc)
{
if(rrset.insert(i).second) {
//std::cerr << "New symbol: " << i << std::endl;
ohandle.write(reinterpret_cast<const char*>(i.bytes), RRDATA_BYTES);
ohandle.flush();
}
}
void rrdata::add_internal() throw(std::bad_alloc)
{
if(!internal)
internal = new instance();
add((*internal)++);
}
namespace
{
void flush_symbol(std::ostream& strm, const rrdata::instance& base, const rrdata::instance& predicted,
unsigned count)
{
char opcode;
char buf1[RRDATA_BYTES + 4];
char buf2[3];
unsigned bias;
if(count == 1) {
opcode = 0x00;
bias = 1;
} else if(count < 258) {
opcode = 0x20;
bias = 2;
} else if(count < 65794) {
opcode = 0x40;
bias = 258;
} else {
opcode = 0x60;
bias = 65794;
}
unsigned j;
for(j = 0; j < 31; j++)
if(base.bytes[j] != predicted.bytes[j])
break;
opcode += j;
buf1[0] = opcode;
memcpy(buf1 + 1, base.bytes + j, RRDATA_BYTES - j);
buf2[0] = (count - bias) >> 16;
buf2[1] = (count - bias) >> 8;
buf2[2] = (count - bias);
memcpy(buf1 + (RRDATA_BYTES - j + 1), buf2 + (3 - (opcode >> 5)), opcode >> 5);
strm.write(buf1, (RRDATA_BYTES - j + 1) + (opcode >> 5));
//std::cerr << "Encoding " << count << " symbols starting from " << base << std::endl;
}
}
uint64_t rrdata::write(std::ostream& strm) throw(std::bad_alloc)
{
uint64_t count = 0;
instance last_encode_end;
memset(last_encode_end.bytes, 0, RRDATA_BYTES);
instance predicted;
instance encode_base;
unsigned encode_count = 0;
bool first = true;
for(auto i = rrset.begin(); i != rrset.end(); i++) {
//std::cerr << "Considering " << *i << std::endl;
count++;
if(encode_count == 0) {
//This is the first symbol.
encode_base = *i;
encode_count = 1;
} else if(predicted == *i && encode_count < 16843009) {
//Correct prediction.
encode_count++;
} else {
//Failed prediction
flush_symbol(strm, encode_base, last_encode_end, encode_count);
last_encode_end = predicted;
encode_base = *i;
encode_count = 1;
}
predicted = *i;
++predicted;
}
if(encode_count > 0)
flush_symbol(strm, encode_base, last_encode_end, encode_count);
return count;
}
uint64_t rrdata::read(std::istream& strm) throw(std::bad_alloc)
{
uint64_t count = 0;
instance decoding;
memset(decoding.bytes, 0, RRDATA_BYTES);
while(strm) {
char opcode;
char buf1[RRDATA_BYTES];
char buf2[3];
strm.read(&opcode, 1);
if(!strm)
continue;
unsigned validbytes = (opcode & 0x1F);
unsigned lengthbytes = (opcode & 0x60) >> 5;
unsigned repeat = 1;
strm.read(buf1, RRDATA_BYTES - validbytes);
memcpy(decoding.bytes + validbytes, buf1, RRDATA_BYTES - validbytes);
if(lengthbytes > 0)
strm.read(buf2, lengthbytes);
if(lengthbytes == 1)
repeat = 2 + buf2[0];
if(lengthbytes == 2)
repeat = 258 + static_cast<unsigned>(buf2[0]) * 256 + buf2[1];
if(lengthbytes == 3)
repeat = 65794 + static_cast<unsigned>(buf2[0]) * 65536 + static_cast<unsigned>(buf2[1]) *
256 + buf2[2];
//std::cerr << "Decoding " << count << " symbols starting from " << decoding << std::endl;
for(unsigned i = 0; i < repeat; i++)
rrdata::add(decoding++);
count += repeat;
}
return count;
}
const char* hexes = "0123456789ABCDEF";
std::ostream& operator<<(std::ostream& os, const struct rrdata::instance& j)
{
for(unsigned i = 0; i < 32; i++) {
os << hexes[j.bytes[i] / 16] << hexes[j.bytes[i] % 16];
}
return os;
}
rrdata::instance* rrdata::internal;
//DBC0AB8CBAAC6ED4B7781E34057891E8B9D93AAE733DEF764C06957FF705DE00
//DBC0AB8CBAAC6ED4B7781E34057891E8B9D93AAE733DEF764C06957FF705DDF3

34
rrdata.hpp Normal file
View file

@ -0,0 +1,34 @@
#ifndef _rrdata__hpp__included__
#define _rrdata__hpp__included__
#define RRDATA_BYTES 32
#include <cstdint>
#include <stdexcept>
class rrdata
{
public:
struct instance
{
instance() throw(std::bad_alloc);
instance(unsigned char* b) throw();
unsigned char bytes[RRDATA_BYTES];
bool operator<(const struct instance& i) const throw();
bool operator==(const struct instance& i) const throw();
const struct instance operator++(int) throw();
const struct instance& operator++() throw();
};
static void read_base(const std::string& project) throw(std::bad_alloc);
static void close() throw();
static void add(const struct instance& i) throw(std::bad_alloc);
static void add_internal() throw(std::bad_alloc);
static uint64_t write(std::ostream& strm) throw(std::bad_alloc);
static uint64_t read(std::istream& strm) throw(std::bad_alloc);
static struct instance* internal;
};
std::ostream& operator<<(std::ostream& os, const struct rrdata::instance& i);
#endif

119
settings.cpp Normal file
View file

@ -0,0 +1,119 @@
#include "settings.hpp"
#include "misc.hpp"
#include <map>
#include <sstream>
#include "misc.hpp"
#include <iostream>
namespace
{
std::map<std::string, setting*>* settings;
}
setting::setting(const std::string& name) throw(std::bad_alloc)
{
if(!settings)
settings = new std::map<std::string, setting*>();
(*settings)[settingname = name] = this;
}
setting::~setting() throw()
{
if(!settings)
return;
settings->erase(settingname);
}
void setting_set(const std::string& _setting, const std::string& value) throw(std::bad_alloc, std::runtime_error)
{
if(!settings || !settings->count(_setting))
throw std::runtime_error("No such setting '" + _setting + "'");
try {
(*settings)[_setting]->set(value);
} catch(std::bad_alloc& e) {
throw;
} catch(std::exception& e) {
throw std::runtime_error("Can't set setting '" + _setting + "': " + e.what());
}
}
void setting_blank(const std::string& _setting) throw(std::bad_alloc, std::runtime_error)
{
if(!settings || !settings->count(_setting))
throw std::runtime_error("No such setting '" + _setting + "'");
try {
(*settings)[_setting]->blank();
} catch(std::bad_alloc& e) {
throw;
} catch(std::exception& e) {
throw std::runtime_error("Can't blank setting '" + _setting + "': " + e.what());
}
}
std::string setting_get(const std::string& _setting) throw(std::bad_alloc, std::runtime_error)
{
if(!settings || !settings->count(_setting))
throw std::runtime_error("No such setting '" + _setting + "'");
return (*settings)[_setting]->get();
}
bool setting_isblank(const std::string& _setting) throw(std::bad_alloc, std::runtime_error)
{
if(!settings || !settings->count(_setting))
throw std::runtime_error("No such setting '" + _setting + "'");
return !((*settings)[_setting]->is_set());
}
void setting_print_all(window* win) throw(std::bad_alloc)
{
if(!settings)
return;
for(auto i = settings->begin(); i != settings->end(); i++) {
if(!i->second->is_set())
out(win) << i->first << ": (unset)" << std::endl;
else
out(win) << i->first << ": " << i->second->get() << std::endl;
}
}
numeric_setting::numeric_setting(const std::string& sname, int32_t minv, int32_t maxv, int32_t dflt)
throw(std::bad_alloc)
: setting(sname)
{
minimum = minv;
maximum = maxv;
value = dflt;
}
void numeric_setting::blank() throw(std::bad_alloc, std::runtime_error)
{
throw std::runtime_error("This setting can't be blanked");
}
bool numeric_setting::is_set() throw()
{
return true;
}
void numeric_setting::set(const std::string& _value) throw(std::bad_alloc, std::runtime_error)
{
int32_t v = parse_value<int32_t>(_value);
if(v < minimum || v > maximum) {
std::ostringstream x;
x << "Value out of range (" << minimum << " - " << maximum << ")";
throw std::runtime_error(x.str());
}
value = v;
}
std::string numeric_setting::get() throw(std::bad_alloc)
{
std::ostringstream x;
x << value;
return x.str();
}
numeric_setting::operator int32_t() throw()
{
return value;
}

123
settings.hpp Normal file
View file

@ -0,0 +1,123 @@
#ifndef _settings__hpp__included__
#define _settings__hpp__included__
#include <string>
#include <stdexcept>
#include "window.hpp"
/**
* \brief A setting.
*/
class setting
{
public:
/**
* \brief Create new setting.
*
* \param name Name of the setting.
* \throws std::bad_alloc Not enough memory.
*/
setting(const std::string& name) throw(std::bad_alloc);
/**
* \brief Remove the setting.
*/
~setting() throw();
/**
* \brief Blank a setting.
*
* Set the setting to special blank state.
*
* \throws std::bad_alloc Not enough memory.
* \throws std::runtime_error Blanking this setting is not allowed (currently).
*/
virtual void blank() throw(std::bad_alloc, std::runtime_error) = 0;
/**
* \brief Is this setting set (not blanked)?
*
* \return True if setting is not blanked, false if it is blanked.
*/
virtual bool is_set() throw() = 0;
/**
* \brief Set value of setting.
*
* \param value New value for setting.
* \throws std::bad_alloc Not enough memory.
* \throws std::runtime_error Setting the setting to this value is not allowed (currently).
*/
virtual void set(const std::string& value) throw(std::bad_alloc, std::runtime_error) = 0;
/**
* \brief Get the value of setting.
*
* \return The setting value.
* \throws std::bad_alloc Not enough memory.
*/
virtual std::string get() throw(std::bad_alloc) = 0;
protected:
std::string settingname;
};
/**
* \brief Look up setting and call set() on it.
*
* \param _setting The setting to set.
* \param value The value to set it into.
* \throws std::bad_alloc Not enough memory.
* \throws std::runtime_error Setting the setting to this value is not allowed (currently), or no such setting.
*/
void setting_set(const std::string& _setting, const std::string& value) throw(std::bad_alloc, std::runtime_error);
/**
* \brief Look up setting and call blank() on it.
*
* \param _setting The setting to blank.
* \throws std::bad_alloc Not enough memory.
* \throws std::runtime_error Blanking this setting is not allowed (currently), or no such setting.
*/
void setting_blank(const std::string& _setting) throw(std::bad_alloc, std::runtime_error);
/**
* \brief Look up setting and call get() on it.
*
* \param _setting The setting to get.
* \return The setting value.
* \throws std::bad_alloc Not enough memory.
* \throws std::runtime_error No such setting.
*/
std::string setting_get(const std::string& _setting) throw(std::bad_alloc, std::runtime_error);
/**
* \brief Look up setting and call is_set() on it.
*
* \param _setting The setting to get.
* \return Flase if setting is not blanked, true if it is blanked (note: this is reverse of is_set().
* \throws std::bad_alloc Not enough memory.
* \throws std::runtime_error No such setting.
*/
bool setting_isblank(const std::string& _setting) throw(std::bad_alloc, std::runtime_error);
/**
* \brief Print all settings and values.
*/
void setting_print_all(window* win) throw(std::bad_alloc);
class numeric_setting : public setting
{
public:
numeric_setting(const std::string& sname, int32_t minv, int32_t maxv, int32_t dflt) throw(std::bad_alloc);
void blank() throw(std::bad_alloc, std::runtime_error);
bool is_set() throw();
void set(const std::string& value) throw(std::bad_alloc, std::runtime_error);
std::string get() throw(std::bad_alloc);
operator int32_t() throw();
private:
int32_t value;
int32_t minimum;
int32_t maximum;
};
#endif

84
specialframes.cpp Normal file
View file

@ -0,0 +1,84 @@
#include <cstdint>
#include "render.hpp"
struct render_list_entry
{
uint32_t codepoint;
uint32_t x;
uint32_t y;
uint32_t scale;
};
struct render_list_entry rl_nosignal[] = {
{'N', 4, 168, 7},
{'O', 60, 168, 7},
{'S', 172, 168, 7},
{'I', 228, 168, 7},
{'G', 284, 168, 7},
{'N', 340, 168, 7},
{'A', 396, 168, 7},
{'L', 452, 168, 7},
{0, 0, 0, 0}
};
struct render_list_entry rl_corrupt[] = {
{'S', 88, 56, 7},
{'Y', 144, 56, 7},
{'S', 200, 56, 7},
{'T', 256, 56, 7},
{'E', 312, 56, 7},
{'M', 368, 56, 7},
{'S', 116, 168, 7},
{'T', 172, 168, 7},
{'A', 224, 168, 7},
{'T', 280, 168, 7},
{'E', 336, 168, 7},
{'C', 60, 280, 7},
{'O', 116, 280, 7},
{'R', 172, 280, 7},
{'R', 228, 280, 7},
{'U', 284, 280, 7},
{'P', 340, 280, 7},
{'T', 396, 280, 7},
{0, 0, 0, 0}
};
extern uint32_t fontdata[];
void draw_special_screen(uint16_t* target, struct render_list_entry* rlist)
{
while(rlist->scale) {
int32_t x;
int32_t y;
auto g = find_glyph(rlist->codepoint, 0, 0, 0, x, y);
for(uint32_t j = 0; j < 16; j++) {
for(uint32_t i = 0; i < 8; i++) {
uint32_t slice = g.second + j / 4;
uint32_t bit = 31 - ((j % 4) * 8 + i);
uint32_t value = (fontdata[slice] >> bit) & 1;
if(value) {
uint32_t basex = rlist->x + rlist->scale * i;
uint32_t basey = rlist->y + rlist->scale * j;
for(uint32_t j2 = 0; j2 < rlist->scale; j2++)
for(uint32_t i2 = 0; i2 < rlist->scale; i2++)
target[(basey + j2) * 512 + (basex + i2)] = 0x7FFF;
}
}
}
rlist++;
}
}
void draw_nosignal(uint16_t* target)
{
for(unsigned i = 0; i < 512 * 448; i++)
target[i] = 0x1F;
draw_special_screen(target, rl_nosignal);
}
void draw_corrupt(uint16_t* target)
{
for(unsigned i = 0; i < 512 * 448; i++)
target[i] = 0x1F;
draw_special_screen(target, rl_corrupt);
}

682
videodumper.cpp Normal file
View file

@ -0,0 +1,682 @@
#include "videodumper.hpp"
#include <iomanip>
#include <cassert>
#include <cstring>
#include <sstream>
#include <zlib.h>
//#include "misc.hpp"
#define AVI_CUTOFF 2000000000
avi_frame::avi_frame(uint32_t _flags, uint32_t _type, uint32_t _offset, uint32_t _size)
{
flags = _flags;
type = _type;
offset = _offset;
size = _size;
}
void avi_frame::write(uint8_t* buf)
{
//Yes, this is written big-endian!
buf[0] = type >> 24;
buf[1] = type >> 16;
buf[2] = type >> 8;
buf[3] = type;
buf[4] = flags;
buf[5] = flags >> 8;
buf[6] = flags >> 16;
buf[7] = flags >> 24;
buf[8] = offset;
buf[9] = offset >> 8;
buf[10] = offset >> 16;
buf[11] = offset >> 24;
buf[12] = size;
buf[13] = size >> 8;
buf[14] = size >> 16;
buf[15] = size >> 24;
}
namespace
{
struct dumper_thread_obj
{
int operator()(avidumper* d)
{
try {
return d->encode_thread();
} catch(std::exception& e) {
std::cerr << "Encode thread threw: " << e.what() << std::endl;
d->set_capture_error(e.what());
}
return 1;
}
};
void write_double(uint8_t* buf, double v)
{
unsigned mag = 1023;
while(v >= 2) {
mag++;
v /= 2;
}
while(v < 1) {
mag--;
v *= 2;
}
uint64_t v2 = mag;
v -= 1;
for(unsigned i = 0; i < 52; i++) {
v *= 2;
v2 = 2 * v2 + ((v >= 1) ? 1 : 0);
if(v >= 1)
v -= 1;
}
buf[0] = v2;
buf[1] = v2 >> 8;
buf[2] = v2 >> 16;
buf[3] = v2 >> 24;
buf[4] = v2 >> 32;
buf[5] = v2 >> 40;
buf[6] = v2 >> 48;
buf[7] = v2 >> 56;
}
// buffer[20] = 0x20; //Rate.
// buffer[21] = 0x4A; //Rate.
// buffer[22] = 0xDF; //Rate.
// buffer[23] = 0x40; //Rate.
}
int avidumper::encode_thread()
{
umutex_class _frame_mutex(frame_mutex);
while(!sigquit) {
if(mt_data) {
_frame_mutex.unlock();
on_frame_threaded(mt_data, mt_width, mt_height, mt_fps_n, mt_fps_d);
_frame_mutex.lock();
}
mt_data = NULL;
frame_cond.notify_all();
frame_cond.wait(_frame_mutex);
}
return 0;
}
avidumper::avidumper(const std::string& _prefix, struct avi_info parameters)
{
compression_level = parameters.compression_level;
audio_drop_counter_inc = parameters.audio_drop_counter_inc;
audio_drop_counter_max = parameters.audio_drop_counter_max;
audio_sampling_rate = parameters.audio_sampling_rate;
audio_native_sampling_rate = parameters.audio_native_sampling_rate;
keyframe_interval = parameters.keyframe_interval;
sox_open = false;
avi_open = false;
capture_error = false;
pwidth = 0xFFFF;
pheight = 0xFFFF;
pfps_n = 0xFFFFFFFFU;
pfps_d = 0xFFFFFFFFU;
current_segment = 0;
prefix = _prefix;
total_data = 0;
total_frames = 0;
total_samples = 0;
audio_drop_counter = 0;
raw_samples = 0;
audio_put_ptr = 0;
audio_get_ptr = 0;
audio_commit_ptr = 0;
mt_data = NULL;
mt_width = 0;
mt_height = 0;
mt_fps_n = 0;
mt_fps_d = 0;
sigquit = false;
std::cerr << "Creating thread..." << std::endl;
dumper_thread_obj dto;
frame_thread = new thread_class(dto, this);
std::cerr << "Created thread..." << std::endl;
}
void avidumper::set_capture_error(const char* err) throw()
{
try {
capture_error = true;
capture_error_str = err;
} catch(std::bad_alloc& e) {
}
}
void avidumper::on_sample(short left, short right) throw(std::bad_alloc, std::runtime_error)
{
if(capture_error)
throw std::runtime_error("Video capture thread crashed: " + capture_error_str);
audio_buffer[audio_put_ptr++] = left;
audio_buffer[audio_put_ptr++] = right;
if(audio_put_ptr == AVIDUMPER_AUDIO_BUFFER)
audio_put_ptr = 0;
//Don't write secondary audio if primary is of perfect quality.
if(!audio_drop_counter_inc)
return;
if(!sox_open) {
sox_stream.open(prefix + ".sox", std::ios::out | std::ios::binary);
if(!sox_stream)
throw std::runtime_error("Can't open audio stream");
uint8_t buffer[32] = {0};
buffer[0] = 0x2E; //Magic.
buffer[1] = 0x53; //Magic.
buffer[2] = 0x6F; //Magic.
buffer[3] = 0x58; //Magic.
buffer[4] = 0x1C; //Header size.
write_double(buffer + 16, audio_native_sampling_rate);
buffer[24] = 0x02; //Stereo.
sox_stream.write(reinterpret_cast<char*>(buffer), 32);
if(!sox_stream)
throw std::runtime_error("Can't write audio header");
sox_open = true;
}
uint8_t buffer[8] = {0};
buffer[2] = static_cast<unsigned short>(left) & 0xFF;
buffer[3] = static_cast<unsigned short>(left) >> 8;
buffer[6] = static_cast<unsigned short>(right) & 0xFF;
buffer[7] = static_cast<unsigned short>(right) >> 8;
sox_stream.write(reinterpret_cast<char*>(buffer), 8);
if(!sox_stream)
throw std::runtime_error("Can't write audio sample");
raw_samples += 2;
}
void avidumper::on_frame(const uint32_t* data, uint16_t width, uint16_t height, uint32_t fps_n, uint32_t fps_d)
throw(std::bad_alloc, std::runtime_error)
{
if(capture_error)
throw std::runtime_error("Video capture thread crashed: " + capture_error_str);
wait_idle();
frame_mutex.lock();
audio_commit_ptr = audio_put_ptr;
mt_data = data;
mt_width = width;
mt_height = height;
mt_fps_n = fps_n;
mt_fps_d = fps_d;
frame_cond.notify_all();
frame_mutex.unlock();
#ifdef NO_THREADS
on_frame_threaded(mt_data, mt_width, mt_height, mt_fps_n, mt_fps_d);
mt_data = NULL;
#endif
}
namespace
{
std::string fmtint(uint64_t val, unsigned prec)
{
std::ostringstream s2;
s2 << std::setw(prec) << std::setfill(' ') << val;
return s2.str();
}
std::string fmtdbl(double val, unsigned prec)
{
std::ostringstream s2;
s2 << std::setw(prec) << std::setfill(' ') << val;
std::string x = s2.str();
if(x.length() == prec)
return x;
size_t p = x.find_first_of("e");
if(p >= x.length())
return x.substr(0, prec);
return x.substr(0, p - (x.length() - prec)) + x.substr(p);
}
}
void avidumper::print_summary(std::ostream& str)
{
uint64_t local_segno = current_segment;
uint64_t local_vframes = segment_frames;
double local_vlength = segment_frames * static_cast<double>(pfps_d) / pfps_n;
uint64_t global_vframes = total_frames;
double global_vlength = total_frames * static_cast<double>(pfps_d) / pfps_n;
uint64_t local_aframes = segment_samples;
double local_alength = static_cast<double>(segment_samples) / audio_sampling_rate;
uint64_t global_aframes = total_samples;
double global_alength = static_cast<double>(total_samples) / audio_sampling_rate;
uint64_t local_size = segment_movi_ptr + 352 + 16 * segment_chunks.size();
uint64_t global_size = total_data + 8 + 16 * segment_chunks.size();
uint64_t global_a2frames = raw_samples / 2;
double global_a2length = raw_samples / (2.0 * audio_native_sampling_rate);
std::ostringstream s2;
s2 << "Quantity |This segment |All segments |" << std::endl;
s2 << "----------------+---------------------+---------------------+" << std::endl;
s2 << "Segment number | " << fmtint(local_segno, 10) << "| N/A|" << std::endl;
s2 << "Video stream |" << fmtint(local_vframes, 10) << "/" << fmtdbl(local_vlength, 10) << "|"
<< fmtint(global_vframes, 10) << "/" << fmtdbl(global_vlength, 10) << "|" << std::endl;
s2 << "Audio stream |" << fmtint(local_aframes, 10) << "/" << fmtdbl(local_alength, 10) << "|"
<< fmtint(global_aframes, 10) << "/" << fmtdbl(global_alength, 10) << "|" << std::endl;
s2 << "A/V desync | " << fmtdbl(local_alength - local_vlength, 10) << "| "
<< fmtdbl(global_alength - global_vlength, 10) << "|" << std::endl;
s2 << "Size | " << fmtint(local_size, 10) << "| "
<< fmtint(global_size, 10) << "|" << std::endl;
s2 << "Audio2 stream | N/A|"
<< fmtint(global_a2frames, 10) << "/" << fmtdbl(global_a2length, 10) << "|" << std::endl;
s2 << "A2/V desync | N/A| "
<< fmtdbl(global_a2length - global_vlength, 10) << "|" << std::endl;
s2 << "----------------+---------------------+---------------------+" << std::endl;
str << s2.str();
}
void avidumper::on_frame_threaded(const uint32_t* data, uint16_t width, uint16_t height, uint32_t fps_n, uint32_t fps_d)
throw(std::bad_alloc, std::runtime_error)
{
//The AVI part of sound to write is [audio_get, audio_commit). We don't write part [audio_commit,audio_put)
//yet, as it is being concurrently written. Also grab lock to read the commit value. Also, if global frame
//counter is 0, don't write audio to avoid A/V desync.
frame_mutex.lock();
unsigned commit_to = audio_commit_ptr;
frame_mutex.unlock();
if(total_frames)
flush_audio_to(commit_to);
else
audio_get_ptr = commit_to;
if(segment_movi_ptr > AVI_CUTOFF - 16 * segment_chunks.size())
fixup_avi_header_and_close();
uint16_t rheight = (height + 3) / 4 * 4;
bool this_is_keyframe;
if(width != pwidth || height != pheight || fps_n != pfps_n || fps_d != pfps_d || !avi_open) {
std::cerr << "Starting segment # " << current_segment << ": " << width << "x" << height << "."
<< std::endl;
fixup_avi_header_and_close();
pwidth = width;
pheight = height;
pfps_n = fps_n;
pfps_d = fps_d;
pframe.resize(4 * static_cast<size_t>(width) * height);
tframe.resize(4 * static_cast<size_t>(width) * rheight);
cframe.resize(compressBound(4 * static_cast<size_t>(width) * rheight) + 13);
memset(&tframe[0], 0, 4 * static_cast<size_t>(width) * rheight);
open_and_write_avi_header(width, rheight, fps_n, fps_d);
}
this_is_keyframe = (segment_frames == 0 || segment_frames - segment_last_keyframe >= keyframe_interval);
if(this_is_keyframe) {
memcpy(&tframe[0], data, 4 * static_cast<size_t>(width) * height);
segment_last_keyframe = segment_frames;
} else {
memcpy(&tframe[0], data, 4 * static_cast<size_t>(width) * height);
for(size_t i = 0; i < 4 * static_cast<size_t>(width) * height; i++)
tframe[i] -= pframe[i];
}
size_t l = cframe.size() - 10;
if(compress2(&cframe[10], &l, &tframe[0], tframe.size(), compression_level) != Z_OK)
throw std::runtime_error("Error compressing frame");
//Pad the frame.
while((l % 4) != 2)
l++;
cframe[0] = '0';
cframe[1] = '0';
cframe[2] = 'd';
cframe[3] = 'b'; //strictly speaking, this is wrong, but FCEUX does this when dumping.
cframe[4] = (l + 2);
cframe[5] = (l + 2) >> 8;
cframe[6] = (l + 2) >> 16;
cframe[7] = (l + 2) >> 24;
cframe[8] = (this_is_keyframe ? 0x3 : 0x2) | (compression_level << 4);
cframe[9] = 12;
avi_stream.write(reinterpret_cast<char*>(&cframe[0]), l + 10);
if(!avi_stream)
throw std::runtime_error("Error writing video frame");
//Flags is 0x10 for keyframes, because those frames are always keyframes, chunks, independent and take time.
//For non-keyframes, flags are 0x00 (chunk, not a keyframe).
segment_chunks.push_back(avi_frame(this_is_keyframe ? 0x10 : 0x00, 0x30306462, segment_movi_ptr + 4, l + 2));
segment_movi_ptr += (l + 10);
total_data += (l + 10);
segment_frames++;
total_frames++;
if((segment_frames % 1200) == 0)
print_summary(std::cerr);
memcpy(&pframe[0], data, 4 * static_cast<size_t>(width) * height);
}
void avidumper::on_end() throw(std::bad_alloc, std::runtime_error)
{
if(capture_error)
throw std::runtime_error("Video capture thread crashed: " + capture_error_str);
frame_mutex.lock();
sigquit = true;
frame_cond.notify_all();
frame_mutex.unlock();
frame_thread->join();
flush_audio_to(audio_put_ptr);
fixup_avi_header_and_close();
if(sox_open) {
sox_stream.seekp(8, std::ios::beg);
uint8_t buffer[8];
buffer[0] = raw_samples;
buffer[1] = raw_samples >> 8;
buffer[2] = raw_samples >> 16;
buffer[3] = raw_samples >> 24;
buffer[4] = raw_samples >> 32;
buffer[5] = raw_samples >> 40;
buffer[6] = raw_samples >> 48;
buffer[7] = raw_samples >> 56;
sox_stream.write(reinterpret_cast<char*>(buffer), 8);
if(!sox_stream)
throw std::runtime_error("Can't fixup audio header");
sox_stream.close();
}
}
namespace
{
struct str
{
str(const char* _s)
{
s = _s;
}
const char* s;
};
struct u16
{
u16(uint16_t _v)
{
v = _v;
}
uint16_t v;
};
struct u32
{
u32(uint32_t _v)
{
v = _v;
}
uint32_t v;
};
struct ptr
{
ptr(uint32_t& _p) : p(_p)
{
}
uint32_t& p;
};
void append(std::vector<uint8_t>& v)
{
}
template<typename... rest>
void append(std::vector<uint8_t>& v, struct str s, rest... _rest)
{
const char* str = s.s;
while(*str)
v.push_back(*(str++));
append(v, _rest...);
}
template<typename... rest>
void append(std::vector<uint8_t>& v, struct u16 u, rest... _rest)
{
uint16_t val = u.v;
v.push_back(val);
v.push_back(val >> 8);
append(v, _rest...);
}
template<typename... rest>
void append(std::vector<uint8_t>& v, struct u32 u, rest... _rest)
{
uint32_t val = u.v;
v.push_back(val);
v.push_back(val >> 8);
v.push_back(val >> 16);
v.push_back(val >> 24);
append(v, _rest...);
}
template<typename... rest>
void append(std::vector<uint8_t>& v, ptr u, rest... _rest)
{
u.p = v.size();
append(v, _rest...);
}
void fix_write(std::ostream& str, uint32_t off, uint32_t val)
{
str.seekp(off, std::ios::beg);
char x[4];
x[0] = val;
x[1] = val >> 8;
x[2] = val >> 16;
x[3] = val >> 24;
str.write(x, 4);
if(!str)
throw std::runtime_error("Can't fixup AVI header");
}
}
void avidumper::flush_audio_to(unsigned commit_to)
{
if(!avi_open)
return;
//Count the number of samples to actually write.
unsigned samples_to_write = 0;
unsigned adc = audio_drop_counter;
unsigned aptr = audio_get_ptr;
unsigned idx = 8;
while(aptr != commit_to) {
if((adc += audio_drop_counter_inc) >= audio_drop_counter_max)
adc -= audio_drop_counter_max;
else
samples_to_write++;
if((aptr += 2) == AVIDUMPER_AUDIO_BUFFER)
aptr = 0;
}
std::vector<uint8_t> buf;
buf.resize(8 + 4 * samples_to_write);
buf[0] = '0';
buf[1] = '1';
buf[2] = 'w';
buf[3] = 'b';
buf[4] = (4 * samples_to_write);
buf[5] = (4 * samples_to_write) >> 8;
buf[6] = (4 * samples_to_write) >> 16;
buf[7] = (4 * samples_to_write) >> 24;
while(audio_get_ptr != commit_to) {
if((audio_drop_counter += audio_drop_counter_inc) >= audio_drop_counter_max) {
//Don't write this sample, reset the counter. This is so that the output is at the correct
//rate.
audio_drop_counter -= audio_drop_counter_max;
} else {
//Write sample.
buf[idx] = static_cast<unsigned short>(audio_buffer[audio_get_ptr]);
buf[idx + 1] = static_cast<unsigned short>(audio_buffer[audio_get_ptr]) >> 8;
buf[idx + 2] = static_cast<unsigned short>(audio_buffer[audio_get_ptr + 1]);
buf[idx + 3] = static_cast<unsigned short>(audio_buffer[audio_get_ptr + 1]) >> 8;
idx += 4;
segment_samples++;
total_samples++;
}
if((audio_get_ptr += 2) == AVIDUMPER_AUDIO_BUFFER)
audio_get_ptr = 0;
}
assert(idx == 8 + 4 * samples_to_write);
avi_stream.write(reinterpret_cast<char*>(&buf[0]), idx);
if(!avi_stream)
throw std::runtime_error("Error writing audio frame");
//Flags is 0x10, because sound frames are always keyframes, chunks, independent and take time.
segment_chunks.push_back(avi_frame(0x10, 0x30317762, segment_movi_ptr + 4, 4 * samples_to_write));
segment_movi_ptr += idx;
total_data += idx;
}
void avidumper::open_and_write_avi_header(uint16_t width, uint16_t height, uint32_t fps_n, uint32_t fps_d)
{
std::string fstr;
{
std::ostringstream str;
str << prefix << "_" << std::setw(9) << std::setfill('0') << (current_segment++) << ".avi";
fstr = str.str();
}
avi_stream.clear();
avi_stream.open(fstr.c_str(), std::ios::out | std::ios::binary);
if(!avi_stream)
throw std::runtime_error("Can't open output AVI file");
std::vector<uint8_t> aviheader;
uint32_t usecs_per_frame = static_cast<uint32_t>(1000000ULL * fps_d / fps_n);
/* AVI main chunk header. */
/* The tentative AVI header size of 336 doesn't include the video data, so we need to fix it up later. */
append(aviheader, str("RIFF"), ptr(fixup_avi_size), u32(336), str("AVI "));
/* Header list header. Has 312 bytes of data. */
append(aviheader, str("LIST"), u32(312), str("hdrl"));
/* Main AVI header. */
append(aviheader, str("avih"), u32(56), /* 56 byte header of type avih. */
u32(usecs_per_frame), /* usecs per frame. */
u32(1000000), /* Max transfer rate... Give some random value. */
u32(0), /* Padding granularity (no padding). */
u32(2064), /* Flags... Has index, trust chunk types */
ptr(fixup_avi_frames), u32(0), /* Frame count... To be fixed later. */
u32(0), /* Initial frames... We don't have any. */
u32(2), /* 2 streams (video + audio). */
u32(1000000), /* Suggested buffer size... Give some random value. */
u32(width), u32(height), /* Size of image. */
u32(0), u32(0), u32(0), u32(0)); /* Reserved. */
/* Stream list header For stream #1, 124 bytes of data. */
append(aviheader, str("LIST"), u32(124), str("strl"));
/* Stream header for stream #1 (video). */
append(aviheader, str("strh"), u32(64), /* 64 byte header of type strh */
str("vids"), u32(0), /* Video data??? */
u32(0), /* Some flags that are all clear. */
u16(0), u16(0), /* Priority and language... Doesn't matter. */
u32(0), /* Initial frames... We don't have any. */
u32(fps_d), u32(fps_n), /* Frame rate is fps_n / fps_d. */
u32(0), /* Starting time... It starts at t=0. */
ptr(fixup_avi_length), u32(0), /* Video length (to be fixed later). */
u32(1000000), /* Suggested buffer size... Just give some random value. */
u32(9999), /* Quality... Doesn't matter. */
u32(4), /* Video sample size... 32bpp. */
u32(0), u32(0), /* Bounding box upper left. */
u32(width), u32(height)); /* Bounding box lower right. */
/* BITMAPINFO header for the video stream. */
append(aviheader, str("strf"), u32(40), /* 40 byte header of type strf. */
u32(40), /* BITMAPINFOHEADER is 40 bytes. */
u32(width), u32(height), /* Image size. */
u16(1), u16(32), /* 1 plane, 32 bits (RGB32). */
str("CSCD"), /* Compressed with Camstudio codec. */
u32(4 * width * height), /* Image size. */
u32(4000), u32(4000), /* Resolution... Give some random values. */
u32(0), u32(0)); /* Colors used values (0 => All colors used). */
/* Stream list header For stream #2, 104 bytes of data. */
append(aviheader, str("LIST"), u32(104), str("strl"));
/* Stream header for stream #2. */
append(aviheader, str("strh"), u32(64), /* 64 byte header of type strh */
str("auds"), u32(0), /* audio data??? */
u32(0), /* Flags... None set. */
u16(0), u16(0), /* Priority and language... Doesn't matter. */
u32(0), /* Initial frames... None. */
u32(1), u32(audio_sampling_rate), /* Audio sampling rate. */
u32(0), /* Starts at t=0s. */
ptr(fixup_avi_a_length), u32(0), /* Audio length (to be fixed later). */
u32(4096), /* Suggested buffer size... Some random value. */
u32(5), /* Audio quality... Some random value. */
u32(4), /* Sample size (16bit Stereo PCM). */
u32(0), u32(0), u32(0), u32(0)); /* Bounding box, not sane for audio data. */
/* WAVEFORMAT header for the audio stream. */
append(aviheader, str("strf"), u32(20), /* 20 byte header of type strf. */
u16(1), /* PCM. */
u16(2), /* Stereo. */
u32(audio_sampling_rate), /* Audio Sampling rate. */
u32(4 * audio_sampling_rate), /* Audio transfer rate (4 times sampling rate). */
u16(4), /* Sample size. */
u16(16), /* Bits per sample. */
u16(0), /* Extension size... We don't have extension. */
u16(0)); /* Dummy. */
/* MOVI list header. 4 bytes without movie data. */
append(aviheader, str("LIST"), ptr(fixup_movi_size), u32(4), str("movi"));
avi_stream.write(reinterpret_cast<char*>(&aviheader[0]), aviheader.size());
if(!avi_stream)
throw std::runtime_error("Can't write AVI header");
total_data += aviheader.size();
avi_open = true;
segment_movi_ptr = 0;
segment_frames = 0;
segment_samples = 0;
segment_last_keyframe = 0;
segment_chunks.clear();
}
void avidumper::fixup_avi_header_and_close()
{
if(!avi_open)
return;
print_summary(std::cerr);
uint8_t buf[16];
buf[0] = 'i';
buf[1] = 'd';
buf[2] = 'x';
buf[3] = '1';
buf[4] = (16 * segment_chunks.size());
buf[5] = (16 * segment_chunks.size()) >> 8;
buf[6] = (16 * segment_chunks.size()) >> 16;
buf[7] = (16 * segment_chunks.size()) >> 24;
avi_stream.write(reinterpret_cast<char*>(buf), 8);
if(!avi_stream)
throw std::runtime_error("Error writing index header");
for(auto i = segment_chunks.begin(); i != segment_chunks.end(); ++i) {
i->write(buf);
avi_stream.write(reinterpret_cast<char*>(buf), 16);
if(!avi_stream)
throw std::runtime_error("Error writing index entry");
}
total_data += (8 + 16 * segment_chunks.size());
fix_write(avi_stream, fixup_avi_size, 344 + segment_movi_ptr + 16 * segment_chunks.size());
fix_write(avi_stream, fixup_avi_frames, segment_frames);
fix_write(avi_stream, fixup_avi_length, segment_frames);
fix_write(avi_stream, fixup_avi_a_length, segment_samples);
fix_write(avi_stream, fixup_movi_size, 4 + segment_movi_ptr);
segment_chunks.clear();
avi_stream.close();
avi_open = false;
}
avidumper::~avidumper() throw()
{
delete frame_thread;
}
void avidumper::wait_idle() throw()
{
umutex_class _frame_mutex(frame_mutex);
while(1) {
if(!mt_data)
return;
frame_cond.wait(_frame_mutex);
}
}

296
videodumper.hpp Normal file
View file

@ -0,0 +1,296 @@
#ifndef _videodumper__hpp__included__
#define _videodumper__hpp__included__
#include <cstdint>
#include <iostream>
#include <fstream>
#include <vector>
#include <list>
#include <stdexcept>
#ifndef NO_THREADS
#include <thread>
#include <condition_variable>
#include <mutex>
/**
* \brief Class of thread.
*/
typedef std::thread thread_class;
/**
* \brief Class of condition variables.
*/
typedef std::condition_variable cv_class;
/**
* \brief Class of mutexes.
*/
typedef std::mutex mutex_class;
/**
* \brief Class of unique mutexes (for condition variable waiting).
*/
typedef std::unique_lock<std::mutex> umutex_class;
#else
/**
* \brief Class of thread.
*/
struct thread_class
{
/**
* \brief Does nothing.
*/
template<typename T, typename... args>
thread_class(T obj, args... a) {}
/**
* \brief Does nothing.
*/
void join() {}
};
/**
* \brief Class of mutexes.
*/
typedef struct mutex_class
{
/**
* \brief Does nothing.
*/
void lock() {}
/**
* \brief Does nothing.
*/
void unlock() {}
} umutex_class;
/**
* \brief Class of condition variables.
*/
struct cv_class
{
/**
* \brief Does nothing.
*/
void wait(umutex_class& m) {}
/**
* \brief Does nothing.
*/
void notify_all() {}
};
#endif
/**
* \brief Size of audio buffer (enough to buffer 3 frames).
*/
#define AVIDUMPER_AUDIO_BUFFER 4096
/**
* \brief Information about frame in AVI.
*/
struct avi_frame
{
/**
* \brief Constructor.
*
* \param _flags Flags for frame.
* \param _type AVI type for frame (big-endian!).
* \param _offset Offset of frame from start of MOVI.
* \param _size Size of frame data.
*/
avi_frame(uint32_t _flags, uint32_t _type, uint32_t _offset, uint32_t _size);
/**
* \brief Write the index entry for frame.
*
* \param buf Buffer to write to.
*/
void write(uint8_t* buf);
/**
* \brief Flags.
*/
uint32_t flags;
/**
* \brief Chunk type.
*/
uint32_t type;
/**
* \brief Chunk offset.
*/
uint32_t offset;
/**
* \brief Chunk size.
*/
uint32_t size;
};
/**
* \brief Parameters for AVI dumping.
*/
struct avi_info
{
/**
* \brief Zlib compression level (0-9).
*/
unsigned compression_level;
/**
* \brief Audio drop counter increments by this much every frame.
*/
uint64_t audio_drop_counter_inc;
/**
* \brief Audio drop counter modulus (when audio drop counter warps around, sample is dropped).
*/
uint64_t audio_drop_counter_max;
/**
* \brief Audio sampling rate to write to AVI.
*/
uint32_t audio_sampling_rate;
/**
* \brief Native audio sampling rate to write to auxillary SOX file.
*/
double audio_native_sampling_rate;
/**
* \brief Interval of keyframes (WARNING: >1 gives non-keyframes which AVISource() doesn't like).
*/
uint32_t keyframe_interval;
};
/**
* \brief The actual AVI dumper.
*/
class avidumper
{
public:
avidumper(const std::string& _prefix, struct avi_info parameters);
~avidumper() throw();
/**
* \brief Wait for encode thread to become idle.
*
* Waits for the encode thread. Not needed: Operations that need to synchronize synchronize themselves.
*/
void wait_idle() throw();
/**
* \brief Dump a frame (new segment starts if needed).
*
* \param data The frame data.
* \param width Width of frame.
* \param height Height of frame.
* \param fps_n Numerator of fps value.
* \param fps_d Denomerator of fps value.
* \throws std::bad_alloc Not enough memory.
* \throws std::runtime_error Error dumping frame.
*/
void on_frame(const uint32_t* data, uint16_t width, uint16_t height, uint32_t fps_n, uint32_t fps_d)
throw(std::bad_alloc, std::runtime_error);
/**
* \brief Dump an audio sample
*
* \param left Signed sample for left channel (-32768 - 327678).
* \param right Signed sample for right channel (-32768 - 327678).
* \throws std::bad_alloc Not enough memory.
* \throws std::runtime_error Error dumping sample.
*/
void on_sample(short left, short right) throw(std::bad_alloc, std::runtime_error);
/**
* \brief Notify end of dump.
*
* \throws std::bad_alloc Not enough memory.
* \throws std::runtime_error Error closing dump.
*/
void on_end() throw(std::bad_alloc, std::runtime_error);
/**
* \brief Act as encode thread.
*
* Causes current thread to become encode thread. Do not call this, the code internally uses it.
*
* \return Return status for the thread.
*/
int encode_thread();
/**
* \brief Set capture errored flag.
*/
void set_capture_error(const char* err) throw();
private:
void print_summary(std::ostream& str);
void on_frame_threaded(const uint32_t* data, uint16_t width, uint16_t height, uint32_t fps_n, uint32_t fps_d)
throw(std::bad_alloc, std::runtime_error);
void flush_audio_to(unsigned commit_ptr);
void open_and_write_avi_header(uint16_t width, uint16_t height, uint32_t fps_n, uint32_t fps_d);
void fixup_avi_header_and_close();
std::ofstream sox_stream;
std::ofstream avi_stream;
bool capture_error;
std::string capture_error_str;
bool sox_open;
bool avi_open;
//Global settings.
unsigned compression_level;
uint64_t audio_drop_counter_inc;
uint64_t audio_drop_counter_max;
uint32_t audio_sampling_rate;
double audio_native_sampling_rate;
uint32_t keyframe_interval;
//Previous frame.
uint16_t pwidth;
uint16_t pheight;
uint32_t pfps_n;
uint32_t pfps_d;
//Current segment.
uint32_t segment_movi_ptr;
uint32_t segment_frames;
uint32_t segment_samples;
uint32_t segment_last_keyframe;
uint32_t current_segment;
uint32_t fixup_avi_size;
uint32_t fixup_avi_frames;
uint32_t fixup_avi_length;
uint32_t fixup_avi_a_length;
uint32_t fixup_movi_size;
std::list<avi_frame> segment_chunks;
//Global info.
std::string prefix;
uint64_t total_data;
uint64_t total_frames;
uint64_t total_samples;
uint64_t raw_samples;
uint64_t audio_drop_counter;
//Temporary buffers.
std::vector<uint8_t> pframe;
std::vector<uint8_t> tframe;
std::vector<uint8_t> cframe;
short audio_buffer[AVIDUMPER_AUDIO_BUFFER];
unsigned audio_put_ptr;
unsigned audio_get_ptr;
volatile unsigned audio_commit_ptr; //Protected by frame_mutex.
//Multithreading stuff.
thread_class* frame_thread;
cv_class frame_cond;
mutex_class frame_mutex;
const uint32_t* mt_data;
volatile uint16_t mt_width;
volatile uint16_t mt_height;
volatile uint32_t mt_fps_n;
volatile uint32_t mt_fps_d;
volatile bool sigquit;
};
#endif

148
videodumper2.cpp Normal file
View file

@ -0,0 +1,148 @@
#include "videodumper.hpp"
#include "videodumper2.hpp"
#include <iomanip>
#include <cassert>
#include <cstring>
#include <sstream>
#include <zlib.h>
#include "misc.hpp"
#include "fieldsplit.hpp"
avidumper* vid_dumper = NULL;
void update_movie_state();
bool vid_dumper_command(std::string& cmd, window* win) throw(std::bad_alloc, std::runtime_error)
{
std::map<std::string, std::string>* y;
std::map<std::string, std::string> _y;
if(win)
y = &(win->get_emustatus());
else
y = &_y;
if(is_cmd_prefix(cmd, "dump-video")) {
std::string syntax = "Syntax: dump-video <0-18> <prefix>";
tokensplitter t(cmd);
std::string dummy = t;
std::string level = t;
std::string prefix = t.tail();
if(prefix == "")
throw std::runtime_error(syntax);
if(vid_dumper)
throw std::runtime_error("Video dumping already in progress");
unsigned long level2;
try {
level2 = parse_value<unsigned long>(level);
if(level2 > 18)
throw std::runtime_error("Level must be 0-18");
} catch(std::bad_alloc& e) {
OOM_panic(win);
} catch(std::runtime_error& e) {
throw std::runtime_error("Bad video compression level '" + level + "': " + e.what());
}
struct avi_info parameters;
parameters.compression_level = (level2 > 9) ? (level2 - 9) : level2;
parameters.audio_drop_counter_inc = 81;
parameters.audio_drop_counter_max = 64081;
parameters.audio_sampling_rate = 32000;
parameters.audio_native_sampling_rate = 32040.5;
parameters.keyframe_interval = (level2 > 9) ? 300 : 1;
try {
vid_dumper = new avidumper(prefix, parameters);
} catch(std::bad_alloc& e) {
OOM_panic(win);
} catch(std::exception& e) {
std::ostringstream x;
x << "Error starting dump: " << e.what();
throw std::runtime_error(x.str());
}
out(win) << "Dumping to " << prefix << " at level " << level2 << std::endl;
update_movie_state();
return true;
} else if(is_cmd_prefix(cmd, "end-video")) {
if(!vid_dumper)
throw std::runtime_error("No video dump in progress");
try {
vid_dumper->on_end();
out(win) << "Dump finished" << std::endl;
} catch(std::bad_alloc& e) {
OOM_panic(win);
} catch(std::exception& e) {
out(win) << "Error ending dump: " << e.what() << std::endl;
}
delete vid_dumper;
vid_dumper = NULL;
update_movie_state();
return true;
} else
return false;
}
void end_vid_dump() throw(std::bad_alloc, std::runtime_error)
{
if(vid_dumper)
try {
vid_dumper->on_end();
} catch(std::bad_alloc& e) {
throw;
} catch(std::exception& e) {
std::cerr << "Error ending dump: " << e.what() << std::endl;
}
}
screen dscr;
void dump_frame(lcscreen& ls, render_queue* rq, uint32_t left, uint32_t right, uint32_t top, uint32_t bottom,
bool region, window* win) throw(std::bad_alloc, std::runtime_error)
{
if(vid_dumper)
try {
uint32_t _magic = 403703808;
uint8_t* magic = reinterpret_cast<uint8_t*>(&_magic);
dscr.reallocate(left + ls.width + right, top + ls.height + bottom, left, top, true);
dscr.set_palette(magic[2], magic[1], magic[0]);
dscr.copy_from(ls, 1, 1);
if(rq)
rq->run(dscr);
assert(dscr.memory);
assert(dscr.width);
assert(dscr.height);
uint32_t fps_n = 10738636;
uint32_t fps_d = 178683;
if(region) {
fps_n = 322445;
fps_d = 6448;
}
vid_dumper->on_frame(dscr.memory, dscr.width, dscr.height, fps_n, fps_d);
} catch(std::bad_alloc& e) {
OOM_panic(win);
} catch(std::exception& e) {
out(win) << "Error sending video frame: " << e.what() << std::endl;
}
if(rq)
rq->clear();
}
void dump_audio_sample(int16_t l_sample, int16_t r_sample, window* win) throw(std::bad_alloc, std::runtime_error)
{
if(vid_dumper)
try {
vid_dumper->on_sample(l_sample, r_sample);
} catch(std::bad_alloc& e) {
OOM_panic(win);
} catch(std::exception& e) {
out(win) << "Error sending audio sample: " << e.what() << std::endl;
}
}
bool dump_in_progress() throw()
{
return (vid_dumper != NULL);
}
void video_fill_shifts(uint32_t& r, uint32_t& g, uint32_t& b)
{
r = dscr.active_rshift;
g = dscr.active_gshift;
b = dscr.active_bshift;
}

78
videodumper2.hpp Normal file
View file

@ -0,0 +1,78 @@
#ifndef _videodumper2__hpp__included__
#define _videodumper2__hpp__included__
#include <cstdint>
#include <iostream>
#include <fstream>
#include <vector>
#include <list>
#include <stdexcept>
#include "window.hpp"
#include "videodumper.hpp"
/**
* \brief End dumping.
*
* Forcibly ends dumping. Mainly useful for quitting.
*
* \throws std::bad_alloc Not enough memory.
* \throws std::runtime_error Failed to end dump.
*/
void end_vid_dump() throw(std::bad_alloc, std::runtime_error);
/**
* \brief Send control command.
*
* Sends command for dumping.
*
* \param cmd Command.
* \param win Graphics system handle.
* \return True if command was recognized, false if not.
* \throws std::bad_alloc Not enough memory.
* \throws std::runtime_error Failed to start dump or invalid syntax.
*/
bool vid_dumper_command(std::string& cmd, window* win) throw(std::bad_alloc, std::runtime_error);
/**
* \brief Dump a frame.
*
* Dumps a frame. Does nothing if dumping is not in progress.
*
* \param ls Screen to dump.
* \param rq Render queue to run.
* \param left Left border.
* \param right Right border.
* \param top Top border.
* \param bottom Bottom border.
* \param region True if PAL, false if NTSC.
* \param win Graphics system handle.
* \throws std::bad_alloc Not enough memory.
* \throws std::runtime_error Failed to dump frame.
*/
void dump_frame(lcscreen& ls, render_queue* rq, uint32_t left, uint32_t right, uint32_t top, uint32_t bottom,
bool region, window* win) throw(std::bad_alloc, std::runtime_error);
/**
* \brief Dump sample of audio.
*
* Dumps one sample of audio. Does nothing if dumping is not in progress.
*
* \param l_sample Left channel sample (-32768-32767)
* \param r_sample Right channel sample (-32768-32767)
* \param win Graphics System handle.
* \throws std::bad_alloc Not enough memory.
* \throws std::runtime_error Failed to dump sample.
*/
void dump_audio_sample(int16_t l_sample, int16_t r_sample, window* win) throw(std::bad_alloc, std::runtime_error);
/**
* \brief Is the dump in progress?
*/
bool dump_in_progress() throw();
/**
* \brief Fill rendering shifts.
*/
void video_fill_shifts(uint32_t& r, uint32_t& g, uint32_t& b);
#endif

2001
window-sdl.cpp Normal file

File diff suppressed because it is too large Load diff

61
window.cpp Normal file
View file

@ -0,0 +1,61 @@
#include "window.hpp"
#include <boost/iostreams/categories.hpp>
#include <boost/iostreams/copy.hpp>
#include <boost/iostreams/stream.hpp>
#include <boost/iostreams/stream_buffer.hpp>
#include <boost/iostreams/filter/symmetric.hpp>
#include <boost/iostreams/filter/zlib.hpp>
#include <boost/iostreams/filtering_stream.hpp>
#include <boost/iostreams/device/back_inserter.hpp>
namespace
{
class window_output
{
public:
typedef char char_type;
typedef boost::iostreams::sink_tag category;
window_output(window* _win)
: win(_win)
{
}
void close()
{
}
std::streamsize write(const char* s, std::streamsize n)
{
size_t oldsize = stream.size();
stream.resize(oldsize + n);
memcpy(&stream[oldsize], s, n);
while(true) {
size_t lf = stream.size();
for(size_t i = 0; i < stream.size(); i++)
if(stream[i] == '\n') {
lf = i;
break;
}
if(lf == stream.size())
break;
std::string foo(stream.begin(), stream.begin() + lf);
win->message(foo);
if(lf + 1 < stream.size())
memmove(&stream[0], &stream[lf + 1], stream.size() - lf - 1);
stream.resize(stream.size() - lf - 1);
}
return n;
}
protected:
std::vector<char> stream;
window* win;
};
}
std::ostream& window::out() throw(std::bad_alloc)
{
static std::ostream* cached = NULL;
if(!cached)
cached = new boost::iostreams::stream<window_output>(this);
return *cached;
}

213
window.hpp Normal file
View file

@ -0,0 +1,213 @@
#ifndef _window__hpp__included__
#define _window__hpp__included__
#include "SDL.h"
#include "render.hpp"
#include <string>
#include <map>
#include <list>
#include <stdexcept>
#define WINSTATE_NORMAL 0
#define WINSTATE_COMMAND 1
#define WINSTATE_MODAL 2
#define WINSTATE_IDENTIFY 3
class window_internal;
class window;
class commandhandler
{
public:
commandhandler();
~commandhandler();
virtual void docommand(std::string& cmd, window* win) = 0;
private:
commandhandler(const commandhandler&);
commandhandler& operator=(const commandhandler&);
};
/**
* \brief Handle to the graphics system.
*
* This is a handle to graphics system. Note that creating multiple contexts produces undefined results.
*/
class window
{
public:
window();
~window();
/**
* \brief Add messages to message queue.
*
* Adds a messages to mesage queue to be shown.
*
* \param msg The messages to add (split by '\n').
* \throws std::bad_alloc Not enough memory.
*/
void message(const std::string& msg) throw(std::bad_alloc);
/**
* \brief Get output stream printing into message queue.
*
* \return The output stream.
* \throws std::bad_alloc Not enough memory.
*/
std::ostream& out() throw(std::bad_alloc);
/**
* \brief Display a modal message.
*
* Displays a modal message, not returning until the message is acknowledged. Keybindings are not available, but
* should quit be generated somehow, modal message will be closed and command callback triggered.
*
* \param msg The message to show.
* \param confirm If true, ask for Ok/cancel type input.
* \return If confirm is true, true if ok was chosen, false if cancel was chosen. Otherwise always false.
* \throws std::bad_alloc Not enough memory.
*/
bool modal_message(const std::string& msg, bool confirm = false) throw(std::bad_alloc);
/**
* \brief Signal that the emulator state is too screwed up to continue.
*
* Displays fatal error message, quitting after the user acks it.
*/
void fatal_error() throw();
/**
* \brief Bind a key.
*
* \param mod Set of modifiers.
* \param modmask Modifier mask (set of modifiers).
* \param keyname Name of key or pseudo-key.
* \param command Command to run.
* \throws std::bad_alloc Not enough memory.
* \throws std::runtime_error Invalid key or modifier name, or conflict.
*/
void bind(std::string mod, std::string modmask, std::string keyname, std::string command)
throw(std::bad_alloc, std::runtime_error);
/**
* \brief Unbind a key.
*
* \param mod Set of modifiers.
* \param modmask Modifier mask (set of modifiers).
* \param keyname Name of key or pseudo-key.
* \throws std::bad_alloc Not enough memory.
* \throws std::runtime_error Invalid key or modifier name, or not bound.
*/
void unbind(std::string mod, std::string modmask, std::string keyname) throw(std::bad_alloc,
std::runtime_error);
/**
* \brief Dump bindings into this window.
*
* \throws std::bad_alloc Not enough memory.
*/
//Dump bindings.
void dumpbindings() throw(std::bad_alloc);
/**
* \brief Process inputs, calling command handler if needed.
*
* Processes inputs. If in non-modal mode (normal mode without pause), this returns quickly. Otherwise it waits
* for modal mode to exit.
*
* \throws std::bad_alloc Not enough memory.
*/
void poll_inputs() throw(std::bad_alloc);
/**
* \brief Get emulator status area
*
* \return Emulator status area.
*/
std::map<std::string, std::string>& get_emustatus() throw();
/**
* \brief Set command handler.
*
* \param cmdh New command handler.
*/
void set_commandhandler(commandhandler& cmdh) throw();
/**
* \brief Notify that the screen has been updated.
*
* \param full Do full refresh.
*/
void notify_screen_update(bool full = false) throw();
/**
* \brief Set the screen to use as main surface.
*
* \param scr The screen to use.
*/
void set_main_surface(screen& scr) throw();
/**
* \brief Enable/Disable pause mode.
*
* \param enable Enable pause if true, disable otherwise.
*/
void paused(bool enable) throw();
/**
* \brief Execute platform-specific commands.
*
* \param cmd The command.
* \return True if command was recognized, false otherwise.
* \throw std::bad_alloc Not enough memory.
* \throw std::runtime_error Can't execute command.
*/
bool exec_command(std::string cmd) throw(std::bad_alloc, std::runtime_error);
/**
* \brief Wait specified number of milliseconds (polling for input).
*
* \param msec Number of ms to wait.
* \throws std::bad_alloc Not enough memory.
*/
void wait_msec(uint64_t msec) throw(std::bad_alloc);
/**
* \brief Enable or disable sound.
*
* \param enable Enable sounds if true, otherwise disable sounds.
*/
void sound_enable(bool enable) throw();
/**
* \brief Input audio sample (at 32040.5Hz).
*
* \param left Left sample.
* \param right Right sample.
*/
void play_audio_sample(uint16_t left, uint16_t right) throw();
/**
* \brief Cancel pending wait, making it return now.
*/
void cancel_wait() throw();
/**
* \brief Set window compensation parameters.
*/
void set_window_compensation(uint32_t xoffset, uint32_t yoffset, uint32_t hscl, uint32_t vscl);
private:
window_internal* i;
window(const window&);
window& operator==(const window&);
};
/**
* \brief Get number of msec since some undetermined epoch.
*
* \return The number of milliseconds.
*/
uint64_t get_ticks_msec() throw();
#endif

651
zip.cpp Normal file
View file

@ -0,0 +1,651 @@
#include "lsnes.hpp"
#include "zip.hpp"
#include <cstdint>
#include <cstring>
#include <iostream>
#include <stdexcept>
#include <boost/iostreams/categories.hpp>
#include <boost/iostreams/copy.hpp>
#include <boost/iostreams/stream.hpp>
#include <boost/iostreams/stream_buffer.hpp>
#include <boost/iostreams/filter/symmetric.hpp>
#include <boost/iostreams/filter/zlib.hpp>
#include <boost/iostreams/filtering_stream.hpp>
#include <boost/iostreams/device/back_inserter.hpp>
namespace
{
uint32_t read32(const unsigned char* buf, unsigned offset = 0, unsigned modulo = 4) throw()
{
return (uint32_t)buf[offset % modulo] |
((uint32_t)buf[(offset + 1) % modulo] << 8) |
((uint32_t)buf[(offset + 2) % modulo] << 16) |
((uint32_t)buf[(offset + 3) % modulo] << 24);
}
uint16_t read16(const unsigned char* buf) throw()
{
return (uint16_t)buf[0] | ((uint16_t)buf[1] << 8);
}
void write16(unsigned char* buf, uint16_t value) throw()
{
buf[0] = (value) & 0xFF;
buf[1] = (value >> 8) & 0xFF;
}
void write32(unsigned char* buf, uint32_t value) throw()
{
buf[0] = (value) & 0xFF;
buf[1] = (value >> 8) & 0xFF;
buf[2] = (value >> 16) & 0xFF;
buf[3] = (value >> 24) & 0xFF;
}
class file_input
{
public:
typedef char char_type;
typedef boost::iostreams::source_tag category;
file_input(std::ifstream& _stream, size_t* _refcnt)
: stream(_stream), stream_refcnt(*_refcnt)
{
stream_refcnt++;
position = stream.tellg();
left_unlimited = true;
}
file_input(std::ifstream& _stream, uint32_t size, size_t* _refcnt)
: stream(_stream), stream_refcnt(*_refcnt)
{
stream_refcnt++;
position = stream.tellg();
left_unlimited = false;
left = size;
}
void close()
{
}
std::streamsize read(char* s, std::streamsize n)
{
stream.clear();
stream.seekg(position, std::ios_base::beg);
if(stream.fail())
throw std::runtime_error("Can't seek ZIP file");
if(!left_unlimited && left == 0)
return -1;
if(!left_unlimited && n > left)
n = left;
stream.read(s, n);
std::streamsize r = stream.gcount();
if(r == 0 && stream.fail())
throw std::runtime_error("Can't read compressed data from ZIP file");
if(!stream && r == 0)
return -1;
position += r;
left -= r;
return r;
}
~file_input()
{
if(!--stream_refcnt) {
delete &stream;
delete &stream_refcnt;
}
}
file_input(const file_input& f)
: stream(f.stream), stream_refcnt(f.stream_refcnt)
{
stream_refcnt++;
position = f.position;
left_unlimited = f.left_unlimited;
left = f.left;
}
protected:
std::ifstream& stream;
size_t& stream_refcnt;
std::streamoff position;
bool left_unlimited;
uint32_t left;
private:
file_input& operator=(const file_input& f);
};
class vector_output
{
public:
typedef char char_type;
typedef boost::iostreams::sink_tag category;
vector_output(std::vector<char>& _stream)
: stream(_stream)
{
}
void close()
{
}
std::streamsize write(const char* s, std::streamsize n)
{
size_t oldsize = stream.size();
stream.resize(oldsize + n);
memcpy(&stream[oldsize], s, n);
return n;
}
protected:
std::vector<char>& stream;
};
class size_and_crc_filter_impl
{
public:
typedef char char_type;
size_and_crc_filter_impl()
{
dsize = 0;
crc = ::crc32(0, NULL, 0);
}
void close()
{
}
bool filter(const char*& src_begin, const char* src_end, char*& dest_begin, char* dest_end,
bool flush)
{
ptrdiff_t amount = src_end - src_begin;
if(flush && amount == 0)
return false;
if(amount > dest_end - dest_begin)
amount = dest_end - dest_begin;
dsize += amount;
crc = ::crc32(crc, reinterpret_cast<const unsigned char*>(src_begin), amount);
memcpy(dest_begin, src_begin, amount);
src_begin += amount;
dest_begin += amount;
return true;
}
uint32_t size()
{
return dsize;
}
uint32_t crc32()
{
return crc;
}
private:
uint32_t dsize;
uint32_t crc;
};
class size_and_crc_filter : public boost::iostreams::symmetric_filter<size_and_crc_filter_impl,
std::allocator<char>>
{
typedef symmetric_filter<size_and_crc_filter_impl, std::allocator<char>> base_type;
public:
typedef typename base_type::char_type char_type;
typedef typename base_type::category category;
size_and_crc_filter(int bsize)
: base_type(bsize)
{
}
uint32_t size()
{
return filter().size();
}
uint32_t crc32()
{
return filter().crc32();
}
};
struct zipfile_member_info
{
bool central_directory_special; //Central directory, not real member.
uint16_t version_needed;
uint16_t flags;
uint16_t compression;
uint16_t mtime_time;
uint16_t mtime_day;
uint32_t crc;
uint32_t compressed_size;
uint32_t uncompressed_size;
std::string filename;
uint32_t header_offset;
uint32_t data_offset;
uint32_t next_offset;
};
//Parse member starting from current offset.
zipfile_member_info parse_member(std::ifstream& file)
{
zipfile_member_info info;
info.central_directory_special = false;
info.header_offset = file.tellg();
//The file header is 30 bytes (this could also hit central header, but that's even larger).
unsigned char buffer[30];
if(!(file.read(reinterpret_cast<char*>(buffer), 30)))
throw std::runtime_error("Can't read file header from ZIP file");
uint32_t magic = read32(buffer);
if(magic == 0x02014b50) {
info.central_directory_special = true;
return info;
}
if(magic != 0x04034b50)
throw std::runtime_error("ZIP archive corrupt: Expected file or central directory magic");
info.version_needed = read16(buffer + 4);
info.flags = read16(buffer + 6);
info.compression = read16(buffer + 8);
info.mtime_time = read16(buffer + 10);
info.mtime_day = read16(buffer + 12);
info.crc = read32(buffer + 14);
info.compressed_size = read32(buffer + 18);
info.uncompressed_size = read32(buffer + 22);
uint16_t filename_len = read16(buffer + 26);
uint16_t extra_len = read16(buffer + 28);
if(!filename_len)
throw std::runtime_error("Unsupported ZIP feature: Empty filename not allowed");
if(info.version_needed > 20) {
throw std::runtime_error("Unsupported ZIP feature: Only ZIP versions up to 2.0 supported");
}
if(info.flags & 0x2001)
throw std::runtime_error("Unsupported ZIP feature: Encryption is not supported");
if(info.flags & 0x8)
throw std::runtime_error("Unsupported ZIP feature: Indeterminate length not supported");
if(info.flags & 0x20)
throw std::runtime_error("Unsupported ZIP feature: Binary patching is not supported");
if(info.compression != 0 && info.compression != 8)
throw std::runtime_error("Unsupported ZIP feature: Unsupported compression method");
if(info.compression == 0 && info.compressed_size != info.uncompressed_size)
throw std::runtime_error("ZIP archive corrupt: csize ≠ usize for stored member");
std::vector<unsigned char> filename_storage;
filename_storage.resize(filename_len);
if(!(file.read(reinterpret_cast<char*>(&filename_storage[0]), filename_len)))
throw std::runtime_error("Can't read file name from zip file");
info.filename = std::string(reinterpret_cast<char*>(&filename_storage[0]), filename_len);
info.data_offset = info.header_offset + 30 + filename_len + extra_len;
info.next_offset = info.data_offset + info.compressed_size;
return info;
}
}
bool zip_reader::has_member(const std::string& name) throw()
{
return (offsets.count(name) > 0);
}
std::string zip_reader::find_first() throw(std::bad_alloc)
{
if(offsets.empty())
return "";
else
return offsets.begin()->first;
}
std::string zip_reader::find_next(const std::string& name) throw(std::bad_alloc)
{
auto i = offsets.upper_bound(name);
if(i == offsets.end())
return "";
else
return i->first;
}
std::istream& zip_reader::operator[](const std::string& name) throw(std::bad_alloc, std::runtime_error)
{
if(!offsets.count(name))
throw std::runtime_error("No such file '" + name + "' in zip archive");
zipstream->clear();
zipstream->seekg(offsets[name], std::ios::beg);
zipfile_member_info info = parse_member(*zipstream);
zipstream->clear();
zipstream->seekg(info.data_offset, std::ios::beg);
if(info.compression == 0) {
return *new boost::iostreams::stream<file_input>(*zipstream, info.uncompressed_size, refcnt);
} else if(info.compression == 8) {
boost::iostreams::filtering_istream* s = new boost::iostreams::filtering_istream();
boost::iostreams::zlib_params params;
params.noheader = true;
s->push(boost::iostreams::zlib_decompressor(params));
s->push(file_input(*zipstream, info.compressed_size, refcnt));
return *s;
} else
throw std::runtime_error("Unsupported ZIP feature: Unsupported compression method");
}
zip_reader::iterator zip_reader::begin() throw(std::bad_alloc)
{
return iterator(offsets.begin());
}
zip_reader::iterator zip_reader::end() throw(std::bad_alloc)
{
return iterator(offsets.end());
}
zip_reader::riterator zip_reader::rbegin() throw(std::bad_alloc)
{
return riterator(offsets.rbegin());
}
zip_reader::riterator zip_reader::rend() throw(std::bad_alloc)
{
return riterator(offsets.rend());
}
zip_reader::~zip_reader() throw()
{
if(!--*refcnt) {
delete zipstream;
delete refcnt;
}
}
zip_reader::zip_reader(const std::string& zipfile) throw(std::bad_alloc, std::runtime_error)
{
zipfile_member_info info;
info.next_offset = 0;
zipstream = new std::ifstream;
zipstream->open(zipfile.c_str(), std::ios::binary);
refcnt = new size_t;
*refcnt = 1;
if(!*zipstream)
throw std::runtime_error("Can't open zipfile '" + zipfile + "' for reading");
do {
zipstream->clear();
zipstream->seekg(info.next_offset);
if(zipstream->fail())
throw std::runtime_error("Can't seek ZIP file");
info = parse_member(*zipstream);
if(info.central_directory_special)
break;
offsets[info.filename] = info.header_offset;
} while(1);
}
zip_writer::zip_writer(const std::string& zipfile, unsigned _compression) throw(std::bad_alloc, std::runtime_error)
{
compression = _compression;
zipfile_path = zipfile;
temp_path = zipfile + ".tmp";
zipstream.open(temp_path.c_str(), std::ios::binary);
if(!zipstream)
throw std::runtime_error("Can't open zipfile '" + temp_path + "' for writing");
committed = false;
}
zip_writer::~zip_writer() throw()
{
if(!committed)
remove(temp_path.c_str());
}
void zip_writer::commit() throw(std::bad_alloc, std::logic_error, std::runtime_error)
{
if(committed)
throw std::logic_error("Can't commit twice");
if(open_file != "")
throw std::logic_error("Can't commit with file open");
std::vector<unsigned char> directory_entry;
uint32_t cdirsize = 0;
uint32_t cdiroff = zipstream.tellp();
if(cdiroff == (uint32_t)-1)
throw std::runtime_error("Can't read current ZIP stream position");
for(auto i = files.begin(); i != files.end(); ++i) {
cdirsize += (46 + i->first.length());
directory_entry.resize(46 + i->first.length());
write32(&directory_entry[0], 0x02014b50);
write16(&directory_entry[4], 3);
write16(&directory_entry[6], 20);
write16(&directory_entry[8], 8);
write16(&directory_entry[10], compression ? 8 : 0);
write16(&directory_entry[12], 0);
write16(&directory_entry[14], 10273);
write32(&directory_entry[16], i->second.crc);
write32(&directory_entry[20], i->second.compressed_size);
write32(&directory_entry[24], i->second.uncompressed_size);
write16(&directory_entry[28], i->first.length());
write16(&directory_entry[30], 0);
write16(&directory_entry[32], 0);
write16(&directory_entry[34], 0);
write16(&directory_entry[36], 0);
write32(&directory_entry[38], 0);
write32(&directory_entry[42], i->second.offset);
memcpy(&directory_entry[46], i->first.c_str(), i->first.length());
zipstream.write(reinterpret_cast<char*>(&directory_entry[0]), directory_entry.size());
if(!zipstream)
throw std::runtime_error("Failed to write central directory entry to output file");
}
directory_entry.resize(22);
write32(&directory_entry[0], 0x06054b50);
write16(&directory_entry[4], 0);
write16(&directory_entry[6], 0);
write16(&directory_entry[8], files.size());
write16(&directory_entry[10], files.size());
write32(&directory_entry[12], cdirsize);
write32(&directory_entry[16], cdiroff);
write16(&directory_entry[20], 0);
zipstream.write(reinterpret_cast<char*>(&directory_entry[0]), directory_entry.size());
if(!zipstream)
throw std::runtime_error("Failed to write central directory end marker to output file");
zipstream.close();
#if defined(_WIN32) || defined(_WIN64)
//Grumble, Windows seemingly can't do this atomically.
remove(zipfile_path.c_str());
#endif
if(rename(temp_path.c_str(), zipfile_path.c_str()) < 0)
throw std::runtime_error("Can't rename '" + temp_path + "' -> '" + zipfile_path + "'");
committed = true;
}
std::ostream& zip_writer::create_file(const std::string& name) throw(std::bad_alloc, std::logic_error,
std::runtime_error)
{
if(open_file != "")
throw std::logic_error("Can't open file with file open");
if(name == "")
throw std::runtime_error("Bad member name");
current_compressed_file.resize(0);
s = new boost::iostreams::filtering_ostream();
s->push(size_and_crc_filter(4096));
if(compression) {
boost::iostreams::zlib_params params;
params.noheader = true;
s->push(boost::iostreams::zlib_compressor(params));
}
s->push(vector_output(current_compressed_file));
open_file = name;
return *s;
}
void zip_writer::close_file() throw(std::bad_alloc, std::logic_error, std::runtime_error)
{
if(open_file == "")
throw std::logic_error("Can't close file with no file open");
uint32_t ucs, cs, crc32;
boost::iostreams::close(*s);
size_and_crc_filter& f = *s->component<size_and_crc_filter>(0);
cs = current_compressed_file.size();
ucs = f.size();
crc32 = f.crc32();
delete s;
base_offset = zipstream.tellp();
if(base_offset == (uint32_t)-1)
throw std::runtime_error("Can't read current ZIP stream position");
unsigned char header[30];
memset(header, 0, 30);
write32(header, 0x04034b50);
header[4] = 20;
header[6] = 0;
header[8] = compression ? 8 : 0;
header[12] = 33;
header[13] = 40;
write32(header + 14, crc32);
write32(header + 18, cs);
write32(header + 22, ucs);
write16(header + 26, open_file.length());
zipstream.write(reinterpret_cast<char*>(header), 30);
zipstream.write(open_file.c_str(), open_file.length());
zipstream.write(&current_compressed_file[0], current_compressed_file.size());
if(!zipstream)
throw std::runtime_error("Can't write member to ZIP file");
current_compressed_file.resize(0);
zip_file_info info;
info.crc = crc32;
info.uncompressed_size = ucs;
info.compressed_size = cs;
info.offset = base_offset;
files[open_file] = info;
open_file = "";
}
namespace
{
#if defined(_WIN32) || defined(_WIN64)
const char* path_splitters = "\\/";
bool drives_allowed = true;
#else
//Assume Unix(-like) system.
const char* path_splitters = "/";
bool drives_allowed = false;
#endif
bool ispathsep(char ch)
{
return (index(path_splitters, static_cast<int>(static_cast<unsigned char>(ch))) != NULL);
}
bool isroot(const std::string& path)
{
if(path.length() == 1 && ispathsep(path[0]))
return true;
if(!drives_allowed)
//NO more cases for this.
return false;
if(path.length() == 3 && path[0] >= 'A' && path[0] <= 'Z' && path[1] == ':' && ispathsep(path[2]))
return true;
//UNC.
if(path.length() <= 3 || !ispathsep(path[0]) || !ispathsep(path[1]) ||
!ispathsep(path[path.length() - 1]))
return false;
return (path.find_first_of(path_splitters, 2) == path.length() - 1);
}
std::string walk(const std::string& path, const std::string& component)
{
if(component == "" || component == ".")
//Current directory.
return path;
else if(component == "..") {
//Parent directory.
if(path == "" || isroot(path))
throw std::runtime_error("Can't rise to containing directory");
std::string _path = path;
size_t split = _path.find_last_of(path_splitters);
if(split < _path.length())
return _path.substr(0, split);
else
return "";
} else if(path == "" || ispathsep(path[path.length() - 1]))
return path + component;
else
return path + "/" + component;
}
std::string combine_path(const std::string& _name, const std::string& _referencing_path)
{
std::string name = _name;
std::string referencing_path = _referencing_path;
size_t x = referencing_path.find_last_of(path_splitters);
if(x < referencing_path.length())
referencing_path = referencing_path.substr(0, x);
else
return name;
//Check if name is absolute.
if(ispathsep(name[0]))
return name;
if(drives_allowed && name.length() >= 3 && name[0] >= 'A' && name[0] <= 'Z' && name[1] == ':' &&
ispathsep(name[2]))
return name;
//It is not absolute.
std::string path = referencing_path;
size_t pindex = 0;
while(true) {
size_t split = name.find_first_of(path_splitters, pindex);
std::string c;
if(split < name.length())
c = name.substr(pindex, split - pindex);
else
c = name.substr(pindex);
path = walk(path, c);
if(split < name.length())
pindex = split + 1;
else
break;
}
//If path becomes empty, assume it means current directory.
if(path == "")
path = ".";
return path;
}
}
std::string resolve_file_relative(const std::string& name, const std::string& referencing_path) throw(std::bad_alloc,
std::runtime_error)
{
return combine_path(name, referencing_path);
}
std::istream& open_file_relative(const std::string& name, const std::string& referencing_path) throw(std::bad_alloc,
std::runtime_error)
{
std::string path_to_open = combine_path(name, referencing_path);
std::string final_path = path_to_open;
//Try to open this from the main OS filesystem.
std::ifstream* i = new std::ifstream(path_to_open.c_str(), std::ios::binary);
if(i->is_open()) {
return *i;
}
delete i;
//Didn't succeed. Try to open as ZIP archive.
std::string membername;
while(true) {
size_t split = path_to_open.find_last_of("/");
if(split >= path_to_open.length())
throw std::runtime_error("Can't open '" + final_path + "'");
//Move a component to member name.
if(membername != "")
membername = path_to_open.substr(split + 1) + "/" + membername;
else
membername = path_to_open.substr(split + 1);
path_to_open = path_to_open.substr(0, split);
try {
zip_reader r(path_to_open);
return r[membername];
} catch(std::bad_alloc& e) {
throw;
} catch(std::runtime_error& e) {
}
}
}
std::vector<char> read_file_relative(const std::string& name, const std::string& referencing_path) throw(std::bad_alloc,
std::runtime_error)
{
std::vector<char> out;
std::istream& s = open_file_relative(name, referencing_path);
boost::iostreams::back_insert_device<std::vector<char>> rd(out);
boost::iostreams::copy(s, rd);
delete &s;
return out;
}

395
zip.hpp Normal file
View file

@ -0,0 +1,395 @@
#ifndef _zip__hpp__included__
#define _zip__hpp__included__
#include <boost/iostreams/filtering_stream.hpp>
#include <iostream>
#include <iterator>
#include <string>
#include <map>
#include <fstream>
#include <zlib.h>
/**
* \brief Read files from ZIP archive
*
* This class opens ZIP archive and offers methods to read members off it.
*/
class zip_reader
{
public:
/**
* \brief ZIP file iterator
*
* This iterator iterates members of ZIP archive.
*/
template<typename T, typename V>
class iterator_class
{
public:
/**
* \brief C++ iterators stuff.
*/
typedef std::bidirectional_iterator_tag iterator_category;
/**
* \brief C++ iterators stuff.
*/
typedef V value_type;
/**
* \brief C++ iterators stuff.
*/
typedef int difference_type;
/**
* \brief C++ iterators stuff.
*/
typedef const V& reference;
/**
* \brief C++ iterators stuff.
*/
typedef const V* pointer;
/**
* \brief Construct new iterator with specified names
*
* This constructs new iteration sequence. Only the first component (keys) are taken into
* account, the second component (values) are ignored.
*
* \param _itr The underlying map iterator.
* \throws std::bad_alloc Not enough memory.
*/
iterator_class(T _itr) throw(std::bad_alloc)
: itr(_itr)
{
}
/**
* \brief Get name of current member.
* \return Name of member.
* \throws std::bad_alloc Not enough memory.
*/
reference operator*() throw(std::bad_alloc)
{
return itr->first;
}
/**
* \brief Get name of current member.
* \return Name of member.
* \throws std::bad_alloc Not enough memory.
*/
pointer operator->() throw(std::bad_alloc)
{
return &(itr->first);
}
/**
* \brief Are these two iterators the same?
* \param i The another iterator
* \return True if iterators are the same, false otherwise.
*/
bool operator==(const iterator_class<T, V>& i) const throw()
{
return itr == i.itr;
}
/**
* \brief Are these two iterators diffrent?
* \param i The another iterator
* \return True if iterators are diffrent, false otherwise.
*/
bool operator!=(const iterator_class<T, V>& i) const throw()
{
return itr != i.itr;
}
/**
* \brief Advance iterator one step.
* \return The old value of iterator.
* \throws std::bad_alloc Not enough memory.
*/
const iterator_class<T, V> operator++(int) throw(std::bad_alloc)
{
iterator_class<T, V> c(*this);
++itr;
return c;
}
/**
* \brief Regress iterator one step.
* \return The old value of iterator.
* \throws std::bad_alloc Not enough memory.
*/
const iterator_class<T, V> operator--(int) throw(std::bad_alloc)
{
iterator_class<T, V> c(*this);
--itr;
return c;
}
/**
* \brief Advance iterator one step.
* \return Reference to this iterator.
*/
iterator_class<T, V>& operator++() throw()
{
++itr;
return *this;
}
/**
* \brief Regress iterator one step.
* \return Reference to this iterator.
*/
iterator_class<T, V>& operator--() throw()
{
--itr;
return *this;
}
private:
T itr;
};
/**
* \brief ZIP file forward iterator
*
* This iterator iterates members of ZIP archive in forward order.
*/
typedef iterator_class<std::map<std::string, unsigned long long>::iterator, std::string> iterator;
/**
* \brief ZIP file reverse iterator
*
* This iterator iterates members of ZIP archive in reverse order
*/
typedef iterator_class<std::map<std::string, unsigned long long>::reverse_iterator, std::string>
riterator;
/**
* \brief Open a ZIP archive.
*
* Opens specified ZIP archive.
* \param zipfile The ZIP file to open.
* \throws std::bad_alloc Not enough memory.
* \throws std::runtime_error Can't open the ZIP file.
*/
zip_reader(const std::string& zipfile) throw(std::bad_alloc, std::runtime_error);
/**
* \brief Destructor
*
* Destroy the ZIP reader. Opened input streams continue to be valid.
*/
~zip_reader() throw();
/**
* \brief Find the name of first member.
*
* Gives the name of the first member, or "" if empty archive.
*
* \return The member name
* \throws std::bad_alloc Not enough memory.
*/
std::string find_first() throw(std::bad_alloc);
/**
* \brief Find the next member.
*
* Gives the name of the next member after specified, or "" if that member is the last.
*
* \param name The name to start the search from.
* \return The member name
* \throws std::bad_alloc Not enough memory.
*/
std::string find_next(const std::string& name) throw(std::bad_alloc);
/**
* \brief Starting iterator
* \return The iterator pointing to first name.
* \throws std::bad_alloc Not enough memory.
*/
iterator begin() throw(std::bad_alloc);
/**
* \brief Ending iterator
* \return The iterator pointing to one past the last name.
* \throws std::bad_alloc Not enough memory.
*/
iterator end() throw(std::bad_alloc);
/**
* \brief Starting reverse iterator
* \return The iterator pointing to last name and acting in reverse.
* \throws std::bad_alloc Not enough memory.
*/
riterator rbegin() throw(std::bad_alloc);
/**
* \brief Ending reverse iterator
* \return The iterator pointing to one before the first name and acting in reverse.
* \throws std::bad_alloc Not enough memory.
*/
riterator rend() throw(std::bad_alloc);
/**
* \brief Does the member exist?
* \param name The name of the member.
* \return True if specified member exists, false otherwise.
*/
bool has_member(const std::string& name) throw();
/**
* \brief Open member
*
* Opens specified member. The stream is not seekable, allocated using new and continues to be valid after
* ZIP reader has been destroyed.
*
* \param name The name of member to open.
* \return The stream corresponding to member.
* \throws std::bad_alloc Not enough memory.
* \throws std::runtime_error The specified member does not exist
*/
std::istream& operator[](const std::string& name) throw(std::bad_alloc, std::runtime_error);
private:
zip_reader(zip_reader&);
zip_reader& operator=(zip_reader&);
std::map<std::string, unsigned long long> offsets;
std::ifstream* zipstream;
size_t* refcnt;
};
/**
* \brief Open file relative to another
*
* Opens the file named by name parameter, which is interpretted relative to file designated by referencing_path.
* The file can be inside ZIP archive. The resulting stream may or may not be seekable.
*
* If referencing_path is "", then name is traditional relative/absolute path. Otherwise if name is relative,
* it is relative to directory containing referencing_path, not current directory.
*
* \param name The name of file to open.
* \param referencing_path The path to file name is interpretted against.
* \return The new stream, allocated by new.
* \throw std::bad_alloc Not enough memory.
* \throw std::runtime_error The file does not exist or can't be opened.
*/
std::istream& open_file_relative(const std::string& name, const std::string& referencing_path) throw(std::bad_alloc,
std::runtime_error);
/**
* \brief Read file relative to another.
*
* Reads the entiere content of file named by name parameter, which is interpretted relative to file designated by
* referencing_path. The file can be inside ZIP archive.
*
* If referencing_path is "", then name is traditional relative/absolute path. Otherwise if name is relative,
* it is relative to directory containing referencing_path, not current directory.
*
* \param name The name of file to read.
* \param referencing_path The path to file name is interpretted against.
* \return The file contents.
* \throw std::bad_alloc Not enough memory.
* \throw std::runtime_error The file does not exist or can't be opened.
*/
std::vector<char> read_file_relative(const std::string& name, const std::string& referencing_path)
throw(std::bad_alloc, std::runtime_error);
/**
* \brief Resolve full path of file relative to another.
*
* Resolves the final file path that open_file_relative/read_file_relative would open.
*
* \param name The name of file to read.
* \param referencing_path The path to file name is interpretted against.
* \return The file absolute path.
* \throw std::bad_alloc Not enough memory.
* \throw std::runtime_error Bad path.
*/
std::string resolve_file_relative(const std::string& name, const std::string& referencing_path) throw(std::bad_alloc,
std::runtime_error);
/**
* \brief Write a ZIP archive
*
* This class handles writing a ZIP archive.
*/
class zip_writer
{
public:
/**
* \brief Create new empty ZIP archive.
*
* Creates new empty ZIP archive. The members will be compressed according to specified compression.
*
* \param zipfile The zipfile to create.
* \param _compression Compression. 0 is uncompressed, 1-9 are deflate compression levels.
* \throws std::bad_alloc Not enough memory.
* \throws std::runtime_error Can't open archive or invalid argument.
*/
zip_writer(const std::string& zipfile, unsigned _compression) throw(std::bad_alloc, std::runtime_error);
/**
* \brief Destroy ZIP writer.
*
* Destroys ZIP writer, aborting the transaction (unless commit() has been called).
*/
~zip_writer() throw();
/**
* \brief Commit the transaction
*
* Commits the ZIP file. Does atomic replace of existing file if possible.
*
* \throws std::bad_alloc Not enough memory.
* \throws std::logic_error Existing file open.
* \throws std::runtime_error Can't commit archive (OS error or member open).
*/
void commit() throw(std::bad_alloc, std::logic_error, std::runtime_error);
/**
* \brief Create a new member
*
* Create a new member inside ZIP file. No existing member may be open.
*
* \param name The name for new member.
* \return Writing stream for the file (don't free).
*
* \throws std::bad_alloc Not enough memory.
* \throws std::logic_error Existing file open.
* \throws std::runtime_error Illegal name.
*/
std::ostream& create_file(const std::string& name) throw(std::bad_alloc, std::logic_error, std::runtime_error);
/**
* \brief Close open member
*
* Closes open member and destroys stream corresponding to it.
*
* \throws std::bad_alloc Not enough memory.
* \throws std::logic_error No file open.
* \throws std::runtime_error Error from operating system.
*/
void close_file() throw(std::bad_alloc, std::logic_error, std::runtime_error);
private:
struct zip_file_info
{
unsigned long crc;
unsigned long uncompressed_size;
unsigned long compressed_size;
unsigned long offset;
};
zip_writer(zip_writer&);
zip_writer& operator=(zip_writer&);
std::ofstream zipstream;
std::string temp_path;
std::string zipfile_path;
std::string open_file;
uint32_t base_offset;
std::vector<char> current_compressed_file;
std::map<std::string, zip_file_info> files;
unsigned compression;
boost::iostreams::filtering_ostream* s;
uint32_t basepos;
bool committed;
};
#endif