mirror of
https://github.com/libretro/gambatte-libretro.git
synced 2024-11-23 07:49:48 +00:00
Merge pull request #56 from UnimatrixX01/network_link
Adding support for linking 2 Game boy emulation sessions over a network.
This commit is contained in:
commit
92df2922ef
@ -27,6 +27,7 @@ SOURCES_CXX := $(CORE_DIR)/cpu.cpp \
|
||||
$(CORE_DIR)/video/next_m0_time.cpp \
|
||||
$(CORE_DIR)/video/ppu.cpp \
|
||||
$(CORE_DIR)/video/sprite_mapper.cpp \
|
||||
$(CORE_DIR)/../libretro/net_serial.cpp \
|
||||
$(CORE_DIR)/../libretro/libretro.cpp
|
||||
|
||||
SOURCES_C := $(CORE_DIR)/../libretro/blipper.c
|
||||
|
@ -20,6 +20,7 @@
|
||||
#define GAMBATTE_H
|
||||
|
||||
#include "inputgetter.h"
|
||||
#include "serial_io.h"
|
||||
#include "gbint.h"
|
||||
#include <string>
|
||||
#include <stddef.h>
|
||||
@ -81,6 +82,9 @@ public:
|
||||
|
||||
/** Sets the callback used for getting input state. */
|
||||
void setInputGetter(InputGetter *getInput);
|
||||
|
||||
/** Sets the callback used for transferring serial data. */
|
||||
void setSerialIO(SerialIO *serial_io);
|
||||
|
||||
/** Sets the directory used for storing save data. The default is the same directory as the ROM Image file. */
|
||||
void setSaveDir(const std::string &sdir);
|
||||
|
@ -2,6 +2,7 @@
|
||||
#include "blipper.h"
|
||||
#include <gambatte.h>
|
||||
#include "gbcpalettes.h"
|
||||
#include "net_serial.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
@ -56,13 +57,23 @@ class SNESInput : public gambatte::InputGetter
|
||||
}
|
||||
} static gb_input;
|
||||
|
||||
enum SerialMode {
|
||||
SERIAL_NONE,
|
||||
SERIAL_SERVER,
|
||||
SERIAL_CLIENT
|
||||
};
|
||||
static NetSerial gb_net_serial;
|
||||
static SerialMode gb_serialMode = SERIAL_NONE;
|
||||
static int gb_NetworkPort = 12345;
|
||||
static std::string gb_NetworkClientAddr;
|
||||
|
||||
static blipper_t *resampler_l;
|
||||
static blipper_t *resampler_r;
|
||||
|
||||
void retro_get_system_info(struct retro_system_info *info)
|
||||
{
|
||||
info->library_name = "Gambatte";
|
||||
info->library_version = "v0.5.0";
|
||||
info->library_version = "v0.5.0-netlink";
|
||||
info->need_fullpath = false;
|
||||
info->block_extract = false;
|
||||
info->valid_extensions = "gb|gbc|dmg";
|
||||
@ -153,6 +164,12 @@ void retro_set_environment(retro_environment_t cb)
|
||||
{ "gambatte_gb_internal_palette", "Internal Palette; GBC - Blue|GBC - Brown|GBC - Dark Blue|GBC - Dark Brown|GBC - Dark Green|GBC - Grayscale|GBC - Green|GBC - Inverted|GBC - Orange|GBC - Pastel Mix|GBC - Red|GBC - Yellow|Special 1|Special 2|Special 3" },
|
||||
{ "gambatte_gbc_color_correction", "Color correction; enabled|disabled" },
|
||||
{ "gambatte_gb_hwmode", "Emulated hardware; Auto|GB|GBA" }, // unfortunately, libgambatte does not have a 'force GBC' flag
|
||||
{ "gambatte_gb_link_mode", "GameBoy Link Mode; Not Connected|Network Server|Network Client" },
|
||||
{ "gambatte_gb_link_network_port", "Network Link Port; 56400|56401|56402|56403|56404|56405|56406|56407|56408|56409|56410|56411|56412|56413|56414|56415|56416|56417|56418|56419|56420" },
|
||||
{ "gambatte_gb_link_network_server_ip_octet1", "Network link server address part 1 (client only); 0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|48|49|50|51|52|53|54|55|56|57|58|59|60|61|62|63|64|65|66|67|68|69|70|71|72|73|74|75|76|77|78|79|80|81|82|83|84|85|86|87|88|89|90|91|92|93|94|95|96|97|98|99|100|101|102|103|104|105|106|107|108|109|110|111|112|113|114|115|116|117|118|119|120|121|122|123|124|125|126|127|128|129|130|131|132|133|134|135|136|137|138|139|140|141|142|143|144|145|146|147|148|149|150|151|152|153|154|155|156|157|158|159|160|161|162|163|164|165|166|167|168|169|170|171|172|173|174|175|176|177|178|179|180|181|182|183|184|185|186|187|188|189|190|191|192|193|194|195|196|197|198|199|200|201|202|203|204|205|206|207|208|209|210|211|212|213|214|215|216|217|218|219|220|221|222|223|224|225|226|227|228|229|230|231|232|233|234|235|236|237|238|239|240|241|242|243|244|245|246|247|248|249|250|251|252|253|254|255" },
|
||||
{ "gambatte_gb_link_network_server_ip_octet2", "Network link server address part 2 (client only); 0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|48|49|50|51|52|53|54|55|56|57|58|59|60|61|62|63|64|65|66|67|68|69|70|71|72|73|74|75|76|77|78|79|80|81|82|83|84|85|86|87|88|89|90|91|92|93|94|95|96|97|98|99|100|101|102|103|104|105|106|107|108|109|110|111|112|113|114|115|116|117|118|119|120|121|122|123|124|125|126|127|128|129|130|131|132|133|134|135|136|137|138|139|140|141|142|143|144|145|146|147|148|149|150|151|152|153|154|155|156|157|158|159|160|161|162|163|164|165|166|167|168|169|170|171|172|173|174|175|176|177|178|179|180|181|182|183|184|185|186|187|188|189|190|191|192|193|194|195|196|197|198|199|200|201|202|203|204|205|206|207|208|209|210|211|212|213|214|215|216|217|218|219|220|221|222|223|224|225|226|227|228|229|230|231|232|233|234|235|236|237|238|239|240|241|242|243|244|245|246|247|248|249|250|251|252|253|254|255" },
|
||||
{ "gambatte_gb_link_network_server_ip_octet3", "Network link server address part 3 (client only); 0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|48|49|50|51|52|53|54|55|56|57|58|59|60|61|62|63|64|65|66|67|68|69|70|71|72|73|74|75|76|77|78|79|80|81|82|83|84|85|86|87|88|89|90|91|92|93|94|95|96|97|98|99|100|101|102|103|104|105|106|107|108|109|110|111|112|113|114|115|116|117|118|119|120|121|122|123|124|125|126|127|128|129|130|131|132|133|134|135|136|137|138|139|140|141|142|143|144|145|146|147|148|149|150|151|152|153|154|155|156|157|158|159|160|161|162|163|164|165|166|167|168|169|170|171|172|173|174|175|176|177|178|179|180|181|182|183|184|185|186|187|188|189|190|191|192|193|194|195|196|197|198|199|200|201|202|203|204|205|206|207|208|209|210|211|212|213|214|215|216|217|218|219|220|221|222|223|224|225|226|227|228|229|230|231|232|233|234|235|236|237|238|239|240|241|242|243|244|245|246|247|248|249|250|251|252|253|254|255" },
|
||||
{ "gambatte_gb_link_network_server_ip_octet4", "Network link server address part 4 (client only); 0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|48|49|50|51|52|53|54|55|56|57|58|59|60|61|62|63|64|65|66|67|68|69|70|71|72|73|74|75|76|77|78|79|80|81|82|83|84|85|86|87|88|89|90|91|92|93|94|95|96|97|98|99|100|101|102|103|104|105|106|107|108|109|110|111|112|113|114|115|116|117|118|119|120|121|122|123|124|125|126|127|128|129|130|131|132|133|134|135|136|137|138|139|140|141|142|143|144|145|146|147|148|149|150|151|152|153|154|155|156|157|158|159|160|161|162|163|164|165|166|167|168|169|170|171|172|173|174|175|176|177|178|179|180|181|182|183|184|185|186|187|188|189|190|191|192|193|194|195|196|197|198|199|200|201|202|203|204|205|206|207|208|209|210|211|212|213|214|215|216|217|218|219|220|221|222|223|224|225|226|227|228|229|230|231|232|233|234|235|236|237|238|239|240|241|242|243|244|245|246|247|248|249|250|251|252|253|254|255" },
|
||||
{ NULL, NULL },
|
||||
};
|
||||
|
||||
@ -367,6 +384,55 @@ static void check_variables(void)
|
||||
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value && !strcmp(var.value, "disabled")) colorCorrection=false;
|
||||
gb.setColorCorrection(colorCorrection);
|
||||
|
||||
gb_serialMode = SERIAL_NONE;
|
||||
var.key = "gambatte_gb_link_mode";
|
||||
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
|
||||
if (!strcmp(var.value, "Network Server")) {
|
||||
gb_serialMode = SERIAL_SERVER;
|
||||
} else if (!strcmp(var.value, "Network Client")) {
|
||||
gb_serialMode = SERIAL_CLIENT;
|
||||
}
|
||||
}
|
||||
|
||||
var.key = "gambatte_gb_link_network_port";
|
||||
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
|
||||
gb_NetworkPort=atoi(var.value);
|
||||
}
|
||||
|
||||
gb_NetworkClientAddr = "";
|
||||
var.key = "gambatte_gb_link_network_server_ip_octet1";
|
||||
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
|
||||
gb_NetworkClientAddr += std::string(var.value);
|
||||
}
|
||||
var.key = "gambatte_gb_link_network_server_ip_octet2";
|
||||
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
|
||||
gb_NetworkClientAddr += "." + std::string(var.value);
|
||||
}
|
||||
var.key = "gambatte_gb_link_network_server_ip_octet3";
|
||||
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
|
||||
gb_NetworkClientAddr += "." + std::string(var.value);
|
||||
}
|
||||
var.key = "gambatte_gb_link_network_server_ip_octet4";
|
||||
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
|
||||
gb_NetworkClientAddr += "." + std::string(var.value);
|
||||
}
|
||||
|
||||
switch(gb_serialMode)
|
||||
{
|
||||
case SERIAL_SERVER:
|
||||
gb_net_serial.start(true, gb_NetworkPort, gb_NetworkClientAddr);
|
||||
gb.setSerialIO(&gb_net_serial);
|
||||
break;
|
||||
case SERIAL_CLIENT:
|
||||
gb_net_serial.start(false, gb_NetworkPort, gb_NetworkClientAddr);
|
||||
gb.setSerialIO(&gb_net_serial);
|
||||
break;
|
||||
default:
|
||||
gb_net_serial.stop();
|
||||
gb.setSerialIO(NULL);
|
||||
break;
|
||||
}
|
||||
|
||||
var.key = "gambatte_gb_colorization";
|
||||
|
||||
if (!environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) || !var.value)
|
||||
|
288
libgambatte/libretro/net_serial.cpp
Normal file
288
libgambatte/libretro/net_serial.cpp
Normal file
@ -0,0 +1,288 @@
|
||||
#include "net_serial.h"
|
||||
#include "libretro.h"
|
||||
#include <iostream>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netdb.h>
|
||||
|
||||
extern retro_log_printf_t log_cb;
|
||||
//FILE* fpout;
|
||||
//FILE* fpin;
|
||||
NetSerial::NetSerial()
|
||||
: is_stopped_(true)
|
||||
, is_server_(false)
|
||||
, port_(12345)
|
||||
, hostname_()
|
||||
, server_fd_(-1)
|
||||
, sockfd_(-1)
|
||||
, lastConnectAttempt_(0)
|
||||
{
|
||||
// fpout = fopen("/tmp/serial_out.txt", "w");
|
||||
// fpin = fopen("/tmp/serial_in.txt", "w");
|
||||
}
|
||||
|
||||
NetSerial::~NetSerial()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
//int master_txn_cnt = 0;
|
||||
//int slave_txn_cnt = 0;
|
||||
bool NetSerial::start(bool is_server, int port, const std::string& hostname)
|
||||
{
|
||||
stop();
|
||||
|
||||
log_cb(RETRO_LOG_INFO, "Starting GameLink nework %s on %s:%d\n",
|
||||
is_server ? "server" : "client", hostname.c_str(), port);
|
||||
is_server_ = is_server;
|
||||
port_ = port;
|
||||
hostname_ = hostname;
|
||||
is_stopped_ = false;
|
||||
|
||||
return checkAndRestoreConnection(false);
|
||||
}
|
||||
void NetSerial::stop()
|
||||
{
|
||||
if (!is_stopped_) {
|
||||
log_cb(RETRO_LOG_INFO, "Stoping GameLink nework\n");
|
||||
is_stopped_ = true;
|
||||
if (sockfd_ >= 0) {
|
||||
close(sockfd_);
|
||||
sockfd_ = -1;
|
||||
}
|
||||
if (server_fd_ >= 0) {
|
||||
close(server_fd_);
|
||||
server_fd_ = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool NetSerial::checkAndRestoreConnection(bool throttle)
|
||||
{
|
||||
if (is_stopped_) {
|
||||
return false;
|
||||
}
|
||||
if (sockfd_ < 0 && throttle) {
|
||||
clock_t now = clock();
|
||||
// Only attempt to establish the connection every 5 seconds
|
||||
if (((now - lastConnectAttempt_) / CLOCKS_PER_SEC) < 5) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
lastConnectAttempt_ = clock();
|
||||
if (is_server_) {
|
||||
if (!startServerSocket()) {
|
||||
return false;
|
||||
}
|
||||
if (!acceptClient()) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!startClientSocket()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool NetSerial::startServerSocket()
|
||||
{
|
||||
int fd;
|
||||
struct sockaddr_in server_addr;
|
||||
|
||||
if (server_fd_ < 0) {
|
||||
bzero((char *)&server_addr, sizeof(server_addr));
|
||||
server_addr.sin_family = AF_INET;
|
||||
server_addr.sin_port = htons(port_);
|
||||
server_addr.sin_addr.s_addr = INADDR_ANY;
|
||||
|
||||
int fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (fd < 0) {
|
||||
log_cb(RETRO_LOG_ERROR, "Error opening socket: %s\n", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bind(fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
|
||||
log_cb(RETRO_LOG_ERROR, "Error on binding: %s\n", strerror(errno));
|
||||
close(fd);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (listen(fd, 1) < 0) {
|
||||
log_cb(RETRO_LOG_ERROR, "Error listening: %s\n", strerror(errno));
|
||||
close(fd);
|
||||
return false;
|
||||
}
|
||||
server_fd_ = fd;
|
||||
log_cb(RETRO_LOG_INFO, "GameLink network server started!\n");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
bool NetSerial::acceptClient()
|
||||
{
|
||||
struct sockaddr_in client_addr;
|
||||
struct timeval tv;
|
||||
fd_set rfds;
|
||||
|
||||
if (server_fd_ < 0) {
|
||||
return false;
|
||||
}
|
||||
if (sockfd_ < 0) {
|
||||
int retval;
|
||||
|
||||
FD_ZERO(&rfds);
|
||||
FD_SET(server_fd_, &rfds);
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = 0;
|
||||
|
||||
if (select(server_fd_ + 1, &rfds, NULL, NULL, &tv) <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
socklen_t client_len = sizeof(client_addr);
|
||||
sockfd_ = accept(server_fd_, (struct sockaddr*)&client_addr, &client_len);
|
||||
if (sockfd_ < 0) {
|
||||
log_cb(RETRO_LOG_ERROR, "Error on accept: %s\n", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
log_cb(RETRO_LOG_INFO, "GameLink network server connected to client!\n");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool NetSerial::startClientSocket()
|
||||
{
|
||||
int fd;
|
||||
struct sockaddr_in server_addr;
|
||||
|
||||
if (sockfd_ < 0) {
|
||||
bzero((char *)&server_addr, sizeof(server_addr));
|
||||
server_addr.sin_family = AF_INET;
|
||||
server_addr.sin_port = htons(port_);
|
||||
|
||||
int fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (fd < 0) {
|
||||
log_cb(RETRO_LOG_ERROR, "Error opening socket: %s\n", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
struct hostent* server_hostname = gethostbyname(hostname_.c_str());
|
||||
if (server_hostname == NULL) {
|
||||
log_cb(RETRO_LOG_ERROR, "Error, no such host: %s\n", hostname_.c_str());
|
||||
close(fd);
|
||||
return false;
|
||||
}
|
||||
|
||||
bcopy((char*)server_hostname->h_addr, (char*)&server_addr.sin_addr.s_addr, server_hostname->h_length);
|
||||
if (connect(fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
|
||||
log_cb(RETRO_LOG_ERROR, "Error connecting to server: %s\n", strerror(errno));
|
||||
close(fd);
|
||||
return false;
|
||||
}
|
||||
sockfd_ = fd;
|
||||
log_cb(RETRO_LOG_INFO, "GameLink network client connected to server!\n");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned char NetSerial::send(unsigned char data, bool fastCgb)
|
||||
{
|
||||
unsigned char buffer[2];
|
||||
|
||||
if (is_stopped_) {
|
||||
return 0xFF;
|
||||
}
|
||||
if (sockfd_ < 0) {
|
||||
if (!checkAndRestoreConnection(true)) {
|
||||
return 0xFF;
|
||||
}
|
||||
}
|
||||
// master_txn_cnt++;
|
||||
|
||||
// std::cout << "(" << master_txn_cnt << "," << slave_txn_cnt << ") Master send: " << (int)data << " at " << fastCgb << std::endl;
|
||||
|
||||
buffer[0] = data;
|
||||
buffer[1] = fastCgb;
|
||||
if (write(sockfd_, buffer, 2) <= 0) {
|
||||
log_cb(RETRO_LOG_ERROR, "Error writing to socket: %s\n", strerror(errno));
|
||||
close(sockfd_);
|
||||
sockfd_ = -1;
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
// std::cout << "(" << master_txn_cnt << "," << slave_txn_cnt << ") Master waiting for response... " << std::endl;
|
||||
|
||||
if (read(sockfd_, buffer, 2) <= 0) {
|
||||
log_cb(RETRO_LOG_ERROR, "Error reading from socket: %s\n", strerror(errno));
|
||||
close(sockfd_);
|
||||
sockfd_ = -1;
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
// std::cout << "(" << master_txn_cnt << "," << slave_txn_cnt << ") Master received: " << (int)buffer[0] << " (speed " << (int)buffer[1] << ")" << std::endl;
|
||||
|
||||
// fwrite(&data, 1, 1, fpout);
|
||||
// fwrite(buffer, 1, 1, fpin);
|
||||
// fflush(fpout);
|
||||
// fflush(fpin);
|
||||
|
||||
return buffer[0];
|
||||
}
|
||||
|
||||
bool NetSerial::check(unsigned char out, unsigned char& in, bool& fastCgb)
|
||||
{
|
||||
unsigned char buffer[2];
|
||||
int bytes_avail = 0;
|
||||
if (is_stopped_) {
|
||||
return false;
|
||||
}
|
||||
if (sockfd_ < 0) {
|
||||
if (!checkAndRestoreConnection(true)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (ioctl(sockfd_, FIONREAD, &bytes_avail) < 0) {
|
||||
log_cb(RETRO_LOG_ERROR, "IOCTL Failed: %s\n", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
// No data available yet
|
||||
if (bytes_avail < 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (read(sockfd_, buffer, 2) <= 0) {
|
||||
log_cb(RETRO_LOG_ERROR, "Error reading from socket: %s\n", strerror(errno));
|
||||
close(sockfd_);
|
||||
sockfd_ = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
// slave_txn_cnt++;
|
||||
|
||||
in = buffer[0];
|
||||
fastCgb = buffer[1];
|
||||
// std::cout << "(" << master_txn_cnt << "," << slave_txn_cnt << ") Slave read: " << (int)in << " at " << fastCgb << ", responding with " << (int)out << std::endl;
|
||||
|
||||
buffer[0] = out;
|
||||
buffer[1] = 128;
|
||||
if (write(sockfd_, buffer, 2) <= 0) {
|
||||
log_cb(RETRO_LOG_ERROR, "Error writing to socket: %s\n", strerror(errno));
|
||||
close(sockfd_);
|
||||
sockfd_ = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
// fwrite(&out, 1, 1, fpout);
|
||||
// fwrite(&in, 1, 1, fpin);
|
||||
// fflush(fpout);
|
||||
// fflush(fpin);
|
||||
|
||||
return true;
|
||||
}
|
36
libgambatte/libretro/net_serial.h
Normal file
36
libgambatte/libretro/net_serial.h
Normal file
@ -0,0 +1,36 @@
|
||||
#ifndef _NET_SERIAL_H
|
||||
#define _NET_SERIAL_H
|
||||
|
||||
#include <gambatte.h>
|
||||
#include <time.h>
|
||||
|
||||
class NetSerial : public gambatte::SerialIO
|
||||
{
|
||||
public:
|
||||
NetSerial();
|
||||
~NetSerial();
|
||||
|
||||
bool start(bool is_server, int port, const std::string& hostname);
|
||||
void stop();
|
||||
|
||||
virtual bool check(unsigned char out, unsigned char& in, bool& fastCgb);
|
||||
virtual unsigned char send(unsigned char data, bool fastCgb);
|
||||
|
||||
private:
|
||||
bool startServerSocket();
|
||||
bool startClientSocket();
|
||||
bool acceptClient();
|
||||
bool checkAndRestoreConnection(bool throttle);
|
||||
|
||||
bool is_stopped_;
|
||||
bool is_server_;
|
||||
int port_;
|
||||
std::string hostname_;
|
||||
|
||||
int server_fd_;
|
||||
int sockfd_;
|
||||
|
||||
clock_t lastConnectAttempt_;
|
||||
};
|
||||
|
||||
#endif
|
@ -496,6 +496,8 @@ void CPU::process(unsigned long const cycles) {
|
||||
while (mem_.isActive()) {
|
||||
unsigned short pc = pc_;
|
||||
|
||||
mem_.checkSerial(cycleCounter);
|
||||
|
||||
if (mem_.halted()) {
|
||||
if (cycleCounter < mem_.nextEventTime()) {
|
||||
unsigned long cycles = mem_.nextEventTime() - cycleCounter;
|
||||
|
@ -25,6 +25,8 @@
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
class SerialIO;
|
||||
|
||||
class CPU {
|
||||
public:
|
||||
CPU();
|
||||
@ -57,6 +59,9 @@ public:
|
||||
void setInputGetter(InputGetter *getInput) {
|
||||
mem_.setInputGetter(getInput);
|
||||
}
|
||||
void setSerialIO(SerialIO *serial_io) {
|
||||
mem_.setSerialIO(serial_io);
|
||||
}
|
||||
|
||||
void setSaveDir(std::string const &sdir) {
|
||||
mem_.setSaveDir(sdir);
|
||||
|
@ -26,7 +26,10 @@
|
||||
namespace gambatte {
|
||||
|
||||
Memory::Memory(Interrupter const &interrupter)
|
||||
: getInput_(0)
|
||||
: serialize_value_(0xFF)
|
||||
, serialize_is_fastcgb_(false)
|
||||
, getInput_(0)
|
||||
, serial_io_(0)
|
||||
, divLastUpdate_(0)
|
||||
, lastOamDmaUpdate_(disabled_time)
|
||||
, lcd_(ioamhram_, 0, VideoInterruptRequester(intreq_))
|
||||
@ -62,6 +65,8 @@ unsigned long Memory::saveState(SaveState &state, unsigned long cc) {
|
||||
state.mem.dmaSource = dmaSource_;
|
||||
state.mem.dmaDestination = dmaDestination_;
|
||||
state.mem.oamDmaPos = oamDmaPos_;
|
||||
state.mem.serialize_value = serialize_value_;
|
||||
state.mem.serialize_is_fastcgb = serialize_is_fastcgb_;
|
||||
|
||||
intreq_.saveState(state);
|
||||
cart_.saveState(state);
|
||||
@ -92,9 +97,11 @@ void Memory::loadState(SaveState const &state) {
|
||||
dmaSource_ = state.mem.dmaSource;
|
||||
dmaDestination_ = state.mem.dmaDestination;
|
||||
oamDmaPos_ = state.mem.oamDmaPos;
|
||||
serialize_value_ = state.mem.serialize_value;
|
||||
serialize_is_fastcgb_ = state.mem.serialize_is_fastcgb;
|
||||
serialCnt_ = intreq_.eventTime(intevent_serial) != disabled_time
|
||||
? serialCntFrom(intreq_.eventTime(intevent_serial) - state.cpu.cycleCounter,
|
||||
ioamhram_[0x102] & isCgb() * 2)
|
||||
serialize_is_fastcgb_)
|
||||
: 8;
|
||||
|
||||
cart_.setVrambank(ioamhram_[0x14F] & isCgb());
|
||||
@ -127,20 +134,50 @@ void Memory::setEndtime(unsigned long cc, unsigned long inc) {
|
||||
intreq_.setEventTime<intevent_end>(cc + (inc << isDoubleSpeed()));
|
||||
}
|
||||
|
||||
void Memory::startSerialTransfer(unsigned long cc, unsigned char data, bool fastCgb)
|
||||
{
|
||||
// If serial interrupt is enabled
|
||||
serialCnt_ = 8;
|
||||
|
||||
serialize_value_ = data;
|
||||
serialize_is_fastcgb_ = fastCgb;
|
||||
intreq_.setEventTime<intevent_serial>(serialize_is_fastcgb_
|
||||
? (cc & ~0x07ul) + 0x010 * 8
|
||||
: (cc & ~0xFFul) + 0x200 * 8);
|
||||
}
|
||||
|
||||
void Memory::checkSerial(unsigned long const cc) {
|
||||
// Periodically checks if serial data is received
|
||||
if ((serial_io_ != 0) &&
|
||||
((ioamhram_[0x102] & 0x80) == 0x80) &&
|
||||
(intreq_.eventTime(intevent_serial) == disabled_time)) {
|
||||
unsigned char data;
|
||||
bool fastCgb;
|
||||
if (serial_io_->check(ioamhram_[0x101], data, fastCgb)) {
|
||||
startSerialTransfer(cc, data, fastCgb);
|
||||
}
|
||||
}
|
||||
}
|
||||
void Memory::updateSerial(unsigned long const cc) {
|
||||
if (intreq_.eventTime(intevent_serial) != disabled_time) {
|
||||
if (intreq_.eventTime(intevent_serial) <= cc) {
|
||||
ioamhram_[0x101] = (((ioamhram_[0x101] + 1) << serialCnt_) - 1) & 0xFF;
|
||||
bool fire = ((ioamhram_[0x102] & 0x80) == 0x80);
|
||||
ioamhram_[0x101] = ((ioamhram_[0x101] << serialCnt_) |
|
||||
(serialize_value_ >> (8 - serialCnt_))) & 0xFF;
|
||||
ioamhram_[0x102] &= 0x7F;
|
||||
intreq_.setEventTime<intevent_serial>(disabled_time);
|
||||
intreq_.flagIrq(8);
|
||||
if (fire) {
|
||||
intreq_.flagIrq(8);
|
||||
}
|
||||
} else {
|
||||
int const targetCnt = serialCntFrom(intreq_.eventTime(intevent_serial) - cc,
|
||||
ioamhram_[0x102] & isCgb() * 2);
|
||||
ioamhram_[0x101] = (((ioamhram_[0x101] + 1) << (serialCnt_ - targetCnt)) - 1) & 0xFF;
|
||||
serialize_is_fastcgb_);
|
||||
ioamhram_[0x101] = ((ioamhram_[0x101] << (serialCnt_ - targetCnt)) |
|
||||
(serialize_value_ >> (8 - (serialCnt_ - targetCnt)))) & 0xFF;
|
||||
serialCnt_ = targetCnt;
|
||||
}
|
||||
}
|
||||
checkSerial(cc);
|
||||
}
|
||||
|
||||
void Memory::updateTimaIrq(unsigned long cc) {
|
||||
@ -606,11 +643,12 @@ void Memory::nontrivial_ff_write(unsigned const p, unsigned data, unsigned long
|
||||
serialCnt_ = 8;
|
||||
|
||||
if ((data & 0x81) == 0x81) {
|
||||
intreq_.setEventTime<intevent_serial>((data & isCgb() * 2)
|
||||
? (cc & ~0x07ul) + 0x010 * 8
|
||||
: (cc & ~0xFFul) + 0x200 * 8);
|
||||
} else
|
||||
intreq_.setEventTime<intevent_serial>(disabled_time);
|
||||
unsigned char receivedByte = 0xFF;
|
||||
if (serial_io_ != 0) {
|
||||
receivedByte = serial_io_->send(ioamhram_[0x101], (data & isCgb() * 2));
|
||||
}
|
||||
startSerialTransfer(cc, receivedByte, (data & isCgb() * 2));
|
||||
}
|
||||
|
||||
data |= 0x7E - isCgb() * 2;
|
||||
break;
|
||||
|
@ -28,6 +28,7 @@
|
||||
namespace gambatte {
|
||||
|
||||
class InputGetter;
|
||||
class SerialIO;
|
||||
|
||||
class Memory {
|
||||
public:
|
||||
@ -94,11 +95,13 @@ public:
|
||||
} else
|
||||
nontrivial_ff_write(p, data, cc);
|
||||
}
|
||||
void startSerialTransfer(unsigned long cycleCounter, unsigned char data, bool fastCgb);
|
||||
|
||||
unsigned long event(unsigned long cycleCounter);
|
||||
unsigned long resetCounters(unsigned long cycleCounter);
|
||||
void setSaveDir(std::string const &dir) { cart_.setSaveDir(dir); }
|
||||
void setInputGetter(InputGetter *getInput) { getInput_ = getInput; }
|
||||
void setSerialIO(SerialIO* serial_io) { serial_io_ = serial_io; }
|
||||
void setEndtime(unsigned long cc, unsigned long inc);
|
||||
void setSoundBuffer(uint_least32_t *buf) { psg_.setBuffer(buf); }
|
||||
std::size_t fillSoundBuffer(unsigned long cc);
|
||||
@ -113,6 +116,7 @@ public:
|
||||
|
||||
void setGameGenie(std::string const &codes) { cart_.setGameGenie(codes); }
|
||||
void setGameShark(std::string const &codes) { interrupter_.setGameShark(codes); }
|
||||
void checkSerial(unsigned long cc);
|
||||
void updateInput();
|
||||
|
||||
int loadROM(const void *romdata, unsigned romsize, const bool forceDmg, const bool multicartCompat);
|
||||
@ -120,7 +124,10 @@ public:
|
||||
private:
|
||||
Cartridge cart_;
|
||||
unsigned char ioamhram_[0x200];
|
||||
unsigned char serialize_value_;
|
||||
bool serialize_is_fastcgb_;
|
||||
InputGetter *getInput_;
|
||||
SerialIO *serial_io_;
|
||||
unsigned long divLastUpdate_;
|
||||
unsigned long lastOamDmaUpdate_;
|
||||
InterruptRequester intreq_;
|
||||
|
@ -61,6 +61,9 @@ void GB::reset() {
|
||||
void GB::setInputGetter(InputGetter *getInput) {
|
||||
p_->cpu.setInputGetter(getInput);
|
||||
}
|
||||
void GB::setSerialIO(SerialIO *serial_io) {
|
||||
p_->cpu.setSerialIO(serial_io);
|
||||
}
|
||||
|
||||
void GB::Priv::on_load_succeeded(unsigned flags)
|
||||
{
|
||||
|
@ -75,6 +75,8 @@ struct SaveState {
|
||||
unsigned short dmaDestination;
|
||||
unsigned char rambank;
|
||||
unsigned char oamDmaPos;
|
||||
unsigned char serialize_value;
|
||||
unsigned char serialize_is_fastcgb;
|
||||
bool IME;
|
||||
bool halted;
|
||||
bool enableRam;
|
||||
|
35
libgambatte/src/serial_io.h
Normal file
35
libgambatte/src/serial_io.h
Normal file
@ -0,0 +1,35 @@
|
||||
//
|
||||
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License version 2 for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// version 2 along with this program; if not, write to the
|
||||
// Free Software Foundation, Inc.,
|
||||
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
//
|
||||
|
||||
#ifndef SERIAL_IO_H
|
||||
#define SERIAL_IO_H
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
class SerialIO
|
||||
{
|
||||
public:
|
||||
virtual ~SerialIO() {};
|
||||
|
||||
virtual bool check(unsigned char out, unsigned char& in, bool& fastCgb) = 0;
|
||||
virtual unsigned char send(unsigned char data, bool fastCgb) = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue
Block a user