From bbd43ed509dcbfeafb08646691805707ac6edd2d Mon Sep 17 00:00:00 2001 From: UnimatrixX01 Date: Sat, 6 Aug 2016 12:09:14 -0500 Subject: [PATCH] Adding support for linking 2 Game boy emulation sessions over a network. --- libgambatte/Makefile.common | 1 + libgambatte/include/gambatte.h | 4 + libgambatte/libretro/libretro.cpp | 68 ++++++- libgambatte/libretro/net_serial.cpp | 288 ++++++++++++++++++++++++++++ libgambatte/libretro/net_serial.h | 36 ++++ libgambatte/src/cpu.cpp | 2 + libgambatte/src/cpu.h | 5 + libgambatte/src/gambatte-memory.cpp | 60 ++++-- libgambatte/src/gambatte-memory.h | 7 + libgambatte/src/gambatte.cpp | 3 + libgambatte/src/savestate.h | 2 + libgambatte/src/serial_io.h | 35 ++++ 12 files changed, 499 insertions(+), 12 deletions(-) create mode 100644 libgambatte/libretro/net_serial.cpp create mode 100644 libgambatte/libretro/net_serial.h create mode 100644 libgambatte/src/serial_io.h diff --git a/libgambatte/Makefile.common b/libgambatte/Makefile.common index a484e10..f6520a7 100644 --- a/libgambatte/Makefile.common +++ b/libgambatte/Makefile.common @@ -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 diff --git a/libgambatte/include/gambatte.h b/libgambatte/include/gambatte.h index f45454e..cf502ef 100644 --- a/libgambatte/include/gambatte.h +++ b/libgambatte/include/gambatte.h @@ -20,6 +20,7 @@ #define GAMBATTE_H #include "inputgetter.h" +#include "serial_io.h" #include "gbint.h" #include #include @@ -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); diff --git a/libgambatte/libretro/libretro.cpp b/libgambatte/libretro/libretro.cpp index 37504f3..ec5a5b3 100644 --- a/libgambatte/libretro/libretro.cpp +++ b/libgambatte/libretro/libretro.cpp @@ -2,6 +2,7 @@ #include "blipper.h" #include #include "gbcpalettes.h" +#include "net_serial.h" #include #include @@ -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) diff --git a/libgambatte/libretro/net_serial.cpp b/libgambatte/libretro/net_serial.cpp new file mode 100644 index 0000000..8dfe896 --- /dev/null +++ b/libgambatte/libretro/net_serial.cpp @@ -0,0 +1,288 @@ +#include "net_serial.h" +#include "libretro.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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; +} diff --git a/libgambatte/libretro/net_serial.h b/libgambatte/libretro/net_serial.h new file mode 100644 index 0000000..744521a --- /dev/null +++ b/libgambatte/libretro/net_serial.h @@ -0,0 +1,36 @@ +#ifndef _NET_SERIAL_H +#define _NET_SERIAL_H + +#include +#include + +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 diff --git a/libgambatte/src/cpu.cpp b/libgambatte/src/cpu.cpp index 0e0abe5..25c4cef 100644 --- a/libgambatte/src/cpu.cpp +++ b/libgambatte/src/cpu.cpp @@ -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; diff --git a/libgambatte/src/cpu.h b/libgambatte/src/cpu.h index df19281..c6bae7d 100644 --- a/libgambatte/src/cpu.h +++ b/libgambatte/src/cpu.h @@ -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); diff --git a/libgambatte/src/gambatte-memory.cpp b/libgambatte/src/gambatte-memory.cpp index 46d1fdd..08e9b32 100644 --- a/libgambatte/src/gambatte-memory.cpp +++ b/libgambatte/src/gambatte-memory.cpp @@ -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(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(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(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((data & isCgb() * 2) - ? (cc & ~0x07ul) + 0x010 * 8 - : (cc & ~0xFFul) + 0x200 * 8); - } else - intreq_.setEventTime(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; diff --git a/libgambatte/src/gambatte-memory.h b/libgambatte/src/gambatte-memory.h index 738f8ab..d0ca95a 100644 --- a/libgambatte/src/gambatte-memory.h +++ b/libgambatte/src/gambatte-memory.h @@ -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_; diff --git a/libgambatte/src/gambatte.cpp b/libgambatte/src/gambatte.cpp index 3070c32..3a3a1a8 100644 --- a/libgambatte/src/gambatte.cpp +++ b/libgambatte/src/gambatte.cpp @@ -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) { diff --git a/libgambatte/src/savestate.h b/libgambatte/src/savestate.h index 338f758..42dec60 100644 --- a/libgambatte/src/savestate.h +++ b/libgambatte/src/savestate.h @@ -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; diff --git a/libgambatte/src/serial_io.h b/libgambatte/src/serial_io.h new file mode 100644 index 0000000..ad94255 --- /dev/null +++ b/libgambatte/src/serial_io.h @@ -0,0 +1,35 @@ +// +// Copyright (C) 2007 by sinamas +// +// 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