commit ccb7edff558c97bcb0f4cc008baf2c4c334751ce Author: sergiotarxz Date: Fri Mar 10 23:37:11 2023 +0100 Initial commit. diff --git a/Doxyfile b/Doxyfile new file mode 100644 index 0000000..111cde7 --- /dev/null +++ b/Doxyfile @@ -0,0 +1,7 @@ +GENERATE_HTML=yes +EXTRACT_ALL=yes +RECURSIVE=yes +INPUT=../include +DISABLE_INDEX = YES +GENERATE_TREEVIEW = YES +PROJECT_NAME=msgba diff --git a/include/msgba/client_connection_data.h b/include/msgba/client_connection_data.h new file mode 100644 index 0000000..4037652 --- /dev/null +++ b/include/msgba/client_connection_data.h @@ -0,0 +1,30 @@ +#ifndef MS_CLIENT_CONNECTION_DATA +#define MS_CLIENT_CONNECTION_DATA +#include +#include +#include +#include + +struct msCoreController; + +/** + * Struct representing everything needed while a connection is established. + */ +struct msClientConnectionData { + size_t numberOfThread; + int clientFd; + struct msCoreController *coreController; +}; + +/** + * Creates a new msClientConnectionData. + * + * @param numberOfThread The unique identifier for this connection. + * @param clientFd The client fd from which receive/send messages. + */ +struct msClientConnectionData * +msClientConnectionDataNew(size_t numberOfThread, int clientFd); + +void +msClientConnectionDataDestroy(struct msClientConnectionData **data); +#endif diff --git a/include/msgba/core_controller.h b/include/msgba/core_controller.h new file mode 100644 index 0000000..adf48e0 --- /dev/null +++ b/include/msgba/core_controller.h @@ -0,0 +1,31 @@ +#ifndef MS_CORE_CONTROLLER +#define MS_CORE_CONTROLLER +#include + +#include +#include + +#include + +struct msMultiplayerController; + +struct msCoreController { + struct msMultiplayerController *multiplayer; + struct mCoreThread threadContext; + color_t *outputBuffer; + unsigned int stride; +}; + +struct msCoreController * +msCoreControllerLoadGame (const unsigned char *rom, size_t rom_len, + const unsigned char *state, size_t state_len, + struct msClientConnectionData *const data); + +void +msCoreControllerDestroy(struct msCoreController **controller_ptr); + +struct msCoreController * +msCoreControllerNew (struct mCore *core, struct msClientConnectionData *const data); +void +msCoreControllerThreadStart (struct msCoreController *const core_controller); +#endif diff --git a/include/msgba/global.h b/include/msgba/global.h new file mode 100644 index 0000000..81f4aec --- /dev/null +++ b/include/msgba/global.h @@ -0,0 +1,8 @@ +#ifndef MS_GLOBAL +#define MS_GLOBAL +#include +#include +#include + +extern char *ms_last_error; +#endif diff --git a/include/msgba/multiplayer_controller.h b/include/msgba/multiplayer_controller.h new file mode 100644 index 0000000..fb44bfd --- /dev/null +++ b/include/msgba/multiplayer_controller.h @@ -0,0 +1,30 @@ +#ifndef MS_MULTIPLAYER_CONTROLLER +#define MS_MULTIPLAYER_CONTROLLER +#include +#include +#include +#include +#ifdef M_CORE_GBA +#include +#endif +#ifdef M_CORE_GB +#include +#endif +#include +#include + +#include +struct msMultiplayerController { + struct msPlayer *players; + pthread_mutex_t lock; + union { + struct mLockstep m_lockstep; +#ifdef M_CORE_GB + struct GBSIOLockstep m_gbLockstep; +#endif +#ifdef M_CORE_GBA + struct GBASIOLockstep m_gbaLockstep; +#endif + }; +}; +#endif diff --git a/include/msgba/packet.h b/include/msgba/packet.h new file mode 100644 index 0000000..e22cd7e --- /dev/null +++ b/include/msgba/packet.h @@ -0,0 +1,46 @@ +#pragma once +#include +#include +#include +#include +#include + +/** + * The possible values for a packet id. + */ +extern enum { + PACKET_GET_HELLO, //! Packet id for get hello. + PACKET_SEND_FRAME, //! Packet id for send frame. + PACKETS_NUMBER //! The number of recognized packets. +}; + +/** + * Struct representing a generic packet. + */ +struct msPacket { + //! The id of the packet. + size_t id; + //! The size of the data contained in the packet. + size_t size; + //! The data as a byte array. (Not null terminated.) + char *raw_data; +}; + +/** + * Asks the code to handle a concrete packet comming from a client. + */ +bool +msPacketHandle(struct msPacket *packet, int client_fd, struct msClientConnectionData *const data); + +/** + * When done with a packet it must be destroyed using this method. + */ +void +msPacketDestroy(struct msPacket **packet); + +/** + * Tries to retrieve a packet from the client, returns NULL in case of error. + * Blocks the current thread. + */ +struct msPacket * +msPacketRead(int client_fd); diff --git a/include/msgba/packet/hello.h b/include/msgba/packet/hello.h new file mode 100644 index 0000000..01bfa8a --- /dev/null +++ b/include/msgba/packet/hello.h @@ -0,0 +1,30 @@ +#ifndef MS_PACKET_HELLO +#define MS_PACKET_HELLO +#include +#include +#include +#include +#include +#include + +#include + +struct msPacket; + +struct msPacketHello { + size_t size_rom; + unsigned char *rom; + size_t size_savestate; + unsigned char *savestate; +}; + + +void +msPacketHelloDestroy(struct msPacketHello **hello); +bool +msPacketHelloHandle(const struct msPacket *packet, struct msPacketHello *hello, + struct msClientConnectionData *const data); +bool +msPacketHelloGet(const struct msPacket *packet, int client_fd, + struct msClientConnectionData *const data); +#endif diff --git a/include/msgba/player.h b/include/msgba/player.h new file mode 100644 index 0000000..5f9e2d5 --- /dev/null +++ b/include/msgba/player.h @@ -0,0 +1,26 @@ +#ifndef MS_PLAYER +#define MS_PLAYER +#include +#include +#include +#include +#ifdef M_CORE_GBA +#include +#endif +#ifdef M_CORE_GB +#include +#endif + +union Node { + struct GBSIOLockstepNode* gb; + struct GBASIOLockstepNode* gba; +}; + +struct msPlayer { + struct msCoreController* controller; + union Node node; + int awake; + int32_t cyclesPosted; + unsigned waitMask; +}; +#endif diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..6ea4309 --- /dev/null +++ b/meson.build @@ -0,0 +1,35 @@ +project('tech.owlcode.msgba', 'c') + +inc = include_directories('include') + +sources = [ + 'src/main.c', + 'src/player.c', + 'src/core_controller.c', + 'src/multiplayer_controller.c', + 'src/packet.c', + 'src/packet/hello.c', + 'src/client_connection_data.c', +] + +inc = [ + 'include' +] + +link_arguments = [ + '-lmgba' +] + +executable('msgba', + sources, + include_directories : inc, + install : true, + link_args : link_arguments, +) +doxygen = find_program('doxygen', required : false) +if doxygen.found() + message('Doxygen found') + run_target('docs', command : [doxygen, meson.source_root() + '/Doxyfile']) +else + warning('Documentation disabled without doxygen') +endif diff --git a/src/client_connection_data.c b/src/client_connection_data.c new file mode 100644 index 0000000..6d4b50b --- /dev/null +++ b/src/client_connection_data.c @@ -0,0 +1,19 @@ +#include +#include +struct msClientConnectionData * +msClientConnectionDataNew(size_t numberOfThread, int clientFd) { + struct msClientConnectionData *data = malloc (sizeof *data); + data->numberOfThread = numberOfThread; + data->clientFd = clientFd; + data->coreController = NULL; + return data; +} + +void +msClientConnectionDataDestroy(struct msClientConnectionData **data) { + if ((*data)->coreController) { + msCoreControllerDestroy(&(*data)->coreController); + } + free(*data); + *data = NULL; +} diff --git a/src/core_controller.c b/src/core_controller.c new file mode 100644 index 0000000..96bd315 --- /dev/null +++ b/src/core_controller.c @@ -0,0 +1,93 @@ +#include +#include +#include +#include + +#ifdef M_CORE_GBA +#include +#endif +#ifdef M_CORE_GB +#include +#endif +#include +#include + +#include +#include + + +struct msCoreController * +msCoreControllerLoadGame (const unsigned char *rom, size_t rom_len, + const unsigned char *state, size_t state_len, + struct msClientConnectionData *const data) { + struct VFile* file_rom = VFileMemChunk (NULL, rom_len); + struct VFile* file_state = VFileMemChunk (NULL, state_len); + file_rom->seek (file_rom, 0, SEEK_SET); + file_state->seek (file_state, 0, SEEK_SET); + file_rom->write (file_rom, rom, rom_len); + file_state->write (file_state, state, state_len); + struct mCore *core = mCoreFindVF (file_rom); + struct msCoreController *controller = NULL; + if (!core) { + ms_last_error = "This rom does not appear to be a GBA or GB/C one"; + file_rom->close(file_rom); + file_state->close(file_rom); + goto loadGameReturn; + } + core->init(core); + unsigned int width; + unsigned int height; + mCoreInitConfig(core, NULL); + mCoreConfigSetIntValue(&core->config, "logLevel", mLOG_FATAL & mLOG_ERROR & mLOG_WARN); + + core->desiredVideoDimensions(core, &width, &height); + color_t *outputBuffer = malloc((sizeof *outputBuffer) * width * height); + printf("controller->outputBuffer width: %u\n", width); + core->setVideoBuffer(core, outputBuffer, width); + core->loadROM (core, file_rom); + mCoreLoadStateNamed (core, file_state, SAVESTATE_SAVEDATA & SAVESTATE_RTC); + + controller = msCoreControllerNew (core, data); + controller->outputBuffer = outputBuffer; + controller->stride = width; +loadGameReturn: + return controller; +} + +void +msCoreControllerSetFrameCallback(struct msCoreController *const self, void(*callback)(struct mCoreThread *)) { + self->threadContext.frameCallback = callback; +} + +void +msCoreControllerThreadStart (struct msCoreController *const core_controller) { + struct mCoreThread *thread = &core_controller->threadContext; + mCoreThreadStart(thread); +} + +void +msCoreControllerDestroy(struct msCoreController **controller_ptr) { + if (controller_ptr && *controller_ptr) { + struct msCoreController *controller = *controller_ptr; + + if (mCoreThreadHasStarted(&controller->threadContext) + && !mCoreThreadHasExited(&controller->threadContext)) { + mCoreThreadEnd(&controller->threadContext); + } + if (controller->threadContext.core) { + struct mCore *core = controller->threadContext.core; + core->deinit(core); + } + free(controller->outputBuffer); + free(controller); + *controller_ptr = NULL; + } +} +struct msCoreController * +msCoreControllerNew (struct mCore *core, struct msClientConnectionData *const data) { + struct msCoreController *controller = malloc (sizeof *controller); + controller->multiplayer = NULL; + controller->threadContext.core = core; + controller->threadContext.userData = data; + return controller; +} diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..99f91a3 --- /dev/null +++ b/src/main.c @@ -0,0 +1,105 @@ +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#ifdef M_CORE_GBA +#include +#endif +#ifdef M_CORE_GB +#include +#endif +#include +#include +#include +#include +#include +#include + +char *ms_last_error = ""; + +#define MAX_NUMBER_OF_THREADS 100 +pthread_t pthread_client_connections[MAX_NUMBER_OF_THREADS]; +pthread_cond_t cond_client_number_decreased; +pthread_mutex_t mutex_number_connections; +size_t connected_clients = 0; +size_t number_of_threads = 0; + +void *handleClientConnection(void *user_data) { + struct msClientConnectionData *data = (struct msClientConnectionData *) user_data; + int client_fd = data->clientFd; + size_t number_of_thread = data->numberOfThread; + struct msPacket *packet = NULL; + printf("Connection from client with id %lu and fd %d\n", number_of_thread, client_fd); + while ((packet = msPacketRead(client_fd)) != NULL) { + bool success = msPacketHandle(packet, client_fd, data); + msPacketDestroy(&packet); + if (!success) { + fprintf(stderr, "Failed handling packet for client_fd %d, closing connection\n", client_fd); + break; + } + } + msClientConnectionDataDestroy(&data); + if (close (client_fd) == 1) { + fprintf(stderr, "Error closing socket %d\n", client_fd); + } + printf("Closing connection with id %lu and fd %d\n", number_of_thread, client_fd); + pthread_mutex_lock(&mutex_number_connections); + connected_clients--; + pthread_mutex_unlock(&mutex_number_connections); + pthread_cond_signal(&cond_client_number_decreased); + return NULL; +} + +#define SOCKET_ADDRESS "msgba.sock" +#define BACKLOG 5 +int main(int argc, char **argv) { + struct sockaddr_un address; + memset (&address, 0, sizeof address); + + address.sun_family = AF_UNIX; + unlink(SOCKET_ADDRESS); + size_t max_size_sun_path = sizeof address.sun_path - 1; + if (strlen(SOCKET_ADDRESS) > max_size_sun_path) { + fprintf(stderr, "Too big socket address"); + exit (2); + } + strncpy(address.sun_path, SOCKET_ADDRESS, max_size_sun_path); + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd == -1) { + fprintf (stderr, "Unable to create socket\n"); + exit (1); + } + if (bind (fd, (struct sockaddr *)&address, + strlen (SOCKET_ADDRESS) + sizeof (address.sun_family)) == -1) { + fprintf(stderr, "Unable to bind socket\n"); + exit (1); + } + if (listen (fd, BACKLOG) == -1) { + fprintf(stderr, "Unable to listen in socket\n"); + exit (1); + } + pthread_cond_init(&cond_client_number_decreased, NULL); + pthread_mutex_init(&mutex_number_connections, NULL); + while (1) { + int client_fd = accept(fd, NULL, NULL); + pthread_attr_t attributes; + pthread_attr_init(&attributes); + pthread_attr_setdetachstate(&attributes, 1); + pthread_mutex_lock(&mutex_number_connections); + while (connected_clients == MAX_NUMBER_OF_THREADS) { + pthread_cond_wait(&cond_client_number_decreased, &mutex_number_connections); + } + struct msClientConnectionData *data = msClientConnectionDataNew(number_of_threads, client_fd); + + pthread_create(&pthread_client_connections[connected_clients++], &attributes, + &handleClientConnection, (void *)data); + pthread_mutex_unlock(&mutex_number_connections); + } +} diff --git a/src/multiplayer_controller.c b/src/multiplayer_controller.c new file mode 100644 index 0000000..232aa41 --- /dev/null +++ b/src/multiplayer_controller.c @@ -0,0 +1,23 @@ +#include +#include +#include +#include +#ifdef M_CORE_GBA +#include +#endif +#ifdef M_CORE_GB +#include +#endif +#include +#include + +#include + +struct msMultiplayerController * +msMultiplayerControllerNew (void) { + struct msMultiplayerController *multiplayer = malloc (sizeof *multiplayer); + pthread_mutex_init(&(multiplayer->lock), NULL); + multiplayer->players = NULL; + return multiplayer; +} + diff --git a/src/packet.c b/src/packet.c new file mode 100644 index 0000000..44a394c --- /dev/null +++ b/src/packet.c @@ -0,0 +1,89 @@ +#include + +#include +#include + +#define PRINT_DEBUG(NAME, CLIENT_FD) \ + printf ("Received packet %s from client fd %d\n", #NAME, CLIENT_FD); +bool +msPacketHandle(struct msPacket *packet, int client_fd, + struct msClientConnectionData *const data) { + bool result = false; + switch (packet->id) { + case PACKET_GET_HELLO: + PRINT_DEBUG(PACKET_HELLO, client_fd); + result = msPacketHelloGet(packet, client_fd, data); + break; + } + return result; +} + +void +msPacketSend(const struct msPacket *const packet, int fd) { + write(fd, (const void *)packet->id, sizeof packet->id); + write(fd, (const void *)packet->size, sizeof packet->size); + write(fd, (const void *)packet->raw_data, sizeof *packet->raw_data * packet->size); +} + +struct msPacket * +msPacketNew(const size_t id, const size_t size, char *raw_data) { + struct msPacket *packet = malloc(sizeof *packet); + packet->id = id; + packet->size = size; + packet->raw_data = raw_data; + return packet; +} + + +void +msPacketDestroy(struct msPacket **packet) { + if ((*packet)->raw_data) { + free((*packet)->raw_data); + } + free(*packet); + *packet=NULL; +} + +struct msPacket * +msPacketRead(int client_fd) { + struct msPacket *packet = NULL; + size_t id = 0; + size_t size = 0; + ssize_t result; + char *raw_data = NULL; + + result = read(client_fd, &id, sizeof id); + if (result < sizeof id) { + printf("Unable to read id\n"); + goto return_read_packet; + } + result = read(client_fd, &size, sizeof size); + if (result < sizeof size) { + printf("Unable to read packet\n"); + goto return_read_packet; + } + size = be64toh(size); + + raw_data = malloc(size); + + size_t to_read_size = size; + while (to_read_size > 0) { + result = read(client_fd, &raw_data[size - to_read_size], to_read_size); + if (result == -1) { + printf("Unable to read raw_data\n"); + goto return_read_packet; + } + to_read_size -= result; + } + if (result < size) { + } + packet = calloc (1, sizeof *packet); + packet->id = id; + packet->size = size; + packet->raw_data = raw_data; +return_read_packet: + if (!packet && raw_data) { + free(raw_data); + } + return packet; +} diff --git a/src/packet/hello.c b/src/packet/hello.c new file mode 100644 index 0000000..2f939b2 --- /dev/null +++ b/src/packet/hello.c @@ -0,0 +1,103 @@ +#include +#include +void +msPacketHelloDestroy(struct msPacketHello **hello) { + if ((*hello)->rom) { + free((*hello)->rom); + (*hello)->rom = NULL; + } + if ((*hello)->savestate) { + free((*hello)->savestate); + (*hello)->savestate = NULL; + } + free(*hello); + *hello = NULL; +} + +bool +msPacketHelloHandle(const struct msPacket *packet, struct msPacketHello *hello, + struct msClientConnectionData *const data) { + bool result = false; + if (data->coreController) { + fprintf(stderr, "Attempt to send a second hello, aborting connection.\n"); + goto return_ms_packet_hello_handle; + } + printf("Loading game and save for client_fd %d\n", data->clientFd); + data->coreController = msCoreControllerLoadGame(hello->rom, hello->size_rom, hello->savestate, hello->size_savestate); + msCoreControllerThreadStart(data->coreController); + result = true; +return_ms_packet_hello_handle: + msPacketHelloDestroy(&hello); + return result; +} + +bool +msPacketHelloGet(const struct msPacket *packet, int client_fd, + struct msClientConnectionData *const data) { + bool result = false; + struct msPacketHello *hello = NULL; + unsigned char *rom = NULL; + unsigned char *savestate = NULL; + FILE *fp = fmemopen(packet->raw_data, packet->size, "r"); + if (!fp) { + printf("Unable to fmemopen\n"); + goto return_get_packet_hello; + } + size_t size_rom = 0; + size_t size_savestate = 0; + size_t reads[4] = { 0,0,0,0 }; + size_t total_read = 0; + size_t to_read_size; + +#define FREAD(target, size, count, fp, n, goto_to) \ + to_read_size = size; \ + reads[n] = fread(target, to_read_size, count, fp); \ + reads[n] *= size; \ + if (reads[n] == -1) { \ + printf("Unable to read %s\n", #target); \ + goto goto_to; \ + } + + + FREAD(&size_rom, sizeof size_rom, 1, fp, 0, return_get_packet_hello); + size_rom = be64toh(size_rom); + rom = malloc(sizeof *rom * size_rom); + + FREAD(rom, sizeof *rom, size_rom, fp, 1, return_get_packet_hello); + + FREAD(&size_savestate, sizeof size_savestate, 1, fp, 2, return_get_packet_hello); + size_savestate = be64toh(size_savestate); + savestate = malloc(sizeof *savestate * size_savestate); + + FREAD(savestate, sizeof *savestate, size_savestate, fp, 3, return_get_packet_hello); + fclose(fp); + + for (int i = 0; i<4; i++) { + total_read += reads[i]; + } + if (total_read != packet->size) { + fprintf (stderr, "Total read (%lu) != packet->size (%lu)\n", total_read, packet->size); + goto return_get_packet_hello; + } + hello = calloc(1, sizeof *hello); + hello->size_rom = size_rom; + hello->rom = rom; + hello->size_savestate = size_savestate; + hello->savestate = savestate; + result = true; +return_get_packet_hello: + if (!result) { + fprintf(stderr, "Unable to read the packet hello\n"); + if (rom) { + free (rom); + rom = NULL; + } + if (savestate) { + free (savestate); + savestate = NULL; + } + return result; + } + return msPacketHelloHandle(packet, hello, data); +} + diff --git a/src/player.c b/src/player.c new file mode 100644 index 0000000..0a5b313 --- /dev/null +++ b/src/player.c @@ -0,0 +1,30 @@ +#include +#ifdef M_CORE_GBA +struct msPlayer * +msPlayerNewGBA (struct msCoreController *controller, + struct GBASIOLockstepNode *node) { + struct msPlayer *player = malloc (sizeof *player); + player->controller = controller; + player->node.gba = node; + player->awake = 1; + player->cyclesPosted = 0; + player->waitMask = 0; + return player; +} +#endif + +#ifdef M_CORE_GB +struct msPlayer * +msPlayerNewGB (struct msCoreController *controller, + struct GBSIOLockstepNode *node) { + struct msPlayer *player = malloc (sizeof *player); + player->controller = controller; + player->node.gb = node; + player->awake = 1; + player->cyclesPosted = 0; + player->waitMask = 0; + return player; +} +#endif + + diff --git a/src/try_mem.c b/src/try_mem.c new file mode 100644 index 0000000..febe728 --- /dev/null +++ b/src/try_mem.c @@ -0,0 +1,40 @@ +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#ifdef M_CORE_GBA +#include +#endif +#ifdef M_CORE_GB +#include +#endif + +int main() { + struct mCoreThread *threadContext = malloc(sizeof *threadContext); + char *file = "/home/sergio/ruby.gba"; + struct mCore *core = mCoreFind(file); + core->init(core); + unsigned int width; + unsigned int height; + core->desiredVideoDimensions(core, &width, &height); + color_t *outputBuffer = malloc((sizeof *outputBuffer) * width * height); + core->setVideoBuffer(core, outputBuffer, 240); + mCoreInitConfig(core, NULL); + if (!core) { + fprintf(stderr, "Unable to load core"); + return 1; + } + mCoreLoadFile(core, file); + threadContext->core = core; + threadContext->userData = NULL; + mCoreThreadStart(threadContext); + mCoreThreadJoin(threadContext); + free(threadContext); +} diff --git a/tester.pl b/tester.pl new file mode 100644 index 0000000..7ac9483 --- /dev/null +++ b/tester.pl @@ -0,0 +1,73 @@ +#!/usr/bin/env perl + +use v5.34.1; + +use strict; +use warnings; + +use IO::Socket::UNIX; + +use Path::Tiny qw/path/; + +my $HOME = $ENV{HOME}; +my $PACKET_HELLO = 0; + +my $fh = IO::Socket::UNIX->new( + Type => SOCK_STREAM(), + Peer => 'msgba.sock', +) || die "Can't open socket: $IO::Socket::errstr"; + +my $rom = path($HOME)->child('ruby.gba')->slurp; +my $sav = path($HOME)->child('ruby.ss1')->slurp; + +my $packet_hello = ""; +open my $fh_packet_hello, '>', \$packet_hello; +write_packet_hello($fh_packet_hello, \$rom, \$sav); +close $fh_packet_hello; +my $packet = ""; +open my $fh_packet, '>', \$packet; +write_packet($fh_packet, $PACKET_HELLO, \$packet_hello); +close $fh_packet; +print $fh $packet; +sleep 1 while 1; + +sub write_packet { + my $fh = shift; + my $id = shift; + my $raw_data = ret_scalar(shift); + print $fh pack('Q>', $id); + print $fh pack('Q>', length $raw_data); + print $fh $raw_data; +} + +sub write_packet_hello { + my $fh = shift or die "No file handle"; + my $rom = ret_scalar(shift); + my $save = ret_scalar(shift); + my $rom_length = length $rom; + my $save_length = length $save; + say "Packet size = @{[16 + $rom_length + $save_length]}"; + + + print $fh pack 'Q>', $rom_length; + print $fh $rom; + print $fh pack 'Q>', $save_length; + print $fh $save; +} + +sub ret_scalar { + my $arg = shift; + if (check_scalar_ref($arg)) { + return ${$arg}; + } + die "No scalar ref"; +} + +sub check_scalar_ref { + my $arg = shift or die "Undefined scalar ref"; + + if ((ref $arg) eq 'SCALAR') { + return 1; + } + return 0; +}