Initial commit.

This commit is contained in:
sergiotarxz 2023-03-10 23:37:11 +01:00
commit ccb7edff55
18 changed files with 818 additions and 0 deletions

7
Doxyfile Normal file
View File

@ -0,0 +1,7 @@
GENERATE_HTML=yes
EXTRACT_ALL=yes
RECURSIVE=yes
INPUT=../include
DISABLE_INDEX = YES
GENERATE_TREEVIEW = YES
PROJECT_NAME=msgba

View File

@ -0,0 +1,30 @@
#ifndef MS_CLIENT_CONNECTION_DATA
#define MS_CLIENT_CONNECTION_DATA
#include <stddef.h>
#include <stdbool.h>
#include <stdlib.h>
#include <unistd.h>
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

View File

@ -0,0 +1,31 @@
#ifndef MS_CORE_CONTROLLER
#define MS_CORE_CONTROLLER
#include <unistd.h>
#include <mgba/core/core.h>
#include <mgba/core/thread.h>
#include <msgba/client_connection_data.h>
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

8
include/msgba/global.h Normal file
View File

@ -0,0 +1,8 @@
#ifndef MS_GLOBAL
#define MS_GLOBAL
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
extern char *ms_last_error;
#endif

View File

@ -0,0 +1,30 @@
#ifndef MS_MULTIPLAYER_CONTROLLER
#define MS_MULTIPLAYER_CONTROLLER
#include <mgba/flags.h>
#include <mgba/core/core.h>
#include <mgba/core/thread.h>
#include <mgba/core/interface.h>
#ifdef M_CORE_GBA
#include <mgba/internal/gba/sio/lockstep.h>
#endif
#ifdef M_CORE_GB
#include <mgba/internal/gb/sio/lockstep.h>
#endif
#include <mgba/core/serialize.h>
#include <mgba-util/vfs.h>
#include <pthread.h>
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

46
include/msgba/packet.h Normal file
View File

@ -0,0 +1,46 @@
#pragma once
#include <stddef.h>
#include <stdbool.h>
#include <stdlib.h>
#include <unistd.h>
#include <msgba/client_connection_data.h>
/**
* 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);

View File

@ -0,0 +1,30 @@
#ifndef MS_PACKET_HELLO
#define MS_PACKET_HELLO
#include <stdio.h>
#include <sys/un.h>
#include <stddef.h>
#include <stdbool.h>
#include <stdlib.h>
#include <unistd.h>
#include <msgba/client_connection_data.h>
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

26
include/msgba/player.h Normal file
View File

@ -0,0 +1,26 @@
#ifndef MS_PLAYER
#define MS_PLAYER
#include <mgba/flags.h>
#include <mgba/core/core.h>
#include <mgba/core/thread.h>
#include <mgba/core/interface.h>
#ifdef M_CORE_GBA
#include <mgba/internal/gba/sio/lockstep.h>
#endif
#ifdef M_CORE_GB
#include <mgba/internal/gb/sio/lockstep.h>
#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

35
meson.build Normal file
View File

@ -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

View File

@ -0,0 +1,19 @@
#include <msgba/core_controller.h>
#include <msgba/client_connection_data.h>
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;
}

93
src/core_controller.c Normal file
View File

@ -0,0 +1,93 @@
#include <mgba/flags.h>
#include <mgba/core/core.h>
#include <mgba/core/thread.h>
#include <mgba/core/interface.h>
#ifdef M_CORE_GBA
#include <mgba/internal/gba/sio/lockstep.h>
#endif
#ifdef M_CORE_GB
#include <mgba/internal/gb/sio/lockstep.h>
#endif
#include <mgba/core/serialize.h>
#include <mgba-util/vfs.h>
#include <msgba/global.h>
#include <msgba/core_controller.h>
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;
}

105
src/main.c Normal file
View File

@ -0,0 +1,105 @@
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <mgba/flags.h>
#include <mgba/core/core.h>
#include <mgba/core/thread.h>
#include <mgba/core/interface.h>
#ifdef M_CORE_GBA
#include <mgba/internal/gba/sio/lockstep.h>
#endif
#ifdef M_CORE_GB
#include <mgba/internal/gb/sio/lockstep.h>
#endif
#include <mgba/core/serialize.h>
#include <mgba-util/vfs.h>
#include <msgba/core_controller.h>
#include <msgba/client_connection_data.h>
#include <msgba/global.h>
#include <msgba/packet.h>
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);
}
}

View File

@ -0,0 +1,23 @@
#include <mgba/flags.h>
#include <mgba/core/core.h>
#include <mgba/core/thread.h>
#include <mgba/core/interface.h>
#ifdef M_CORE_GBA
#include <mgba/internal/gba/sio/lockstep.h>
#endif
#ifdef M_CORE_GB
#include <mgba/internal/gb/sio/lockstep.h>
#endif
#include <mgba/core/serialize.h>
#include <mgba-util/vfs.h>
#include <msgba/multiplayer_controller.h>
struct msMultiplayerController *
msMultiplayerControllerNew (void) {
struct msMultiplayerController *multiplayer = malloc (sizeof *multiplayer);
pthread_mutex_init(&(multiplayer->lock), NULL);
multiplayer->players = NULL;
return multiplayer;
}

89
src/packet.c Normal file
View File

@ -0,0 +1,89 @@
#include <endian.h>
#include <msgba/packet.h>
#include <msgba/packet/hello.h>
#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;
}

103
src/packet/hello.c Normal file
View File

@ -0,0 +1,103 @@
#include <msgba/packet.h>
#include <msgba/packet/hello.h>
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);
}

30
src/player.c Normal file
View File

@ -0,0 +1,30 @@
#include <msgba/player.h>
#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

40
src/try_mem.c Normal file
View File

@ -0,0 +1,40 @@
#include <pthread.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <mgba/flags.h>
#include <mgba/core/core.h>
#include <mgba/core/thread.h>
#include <mgba/core/interface.h>
#include <mgba-util/vfs.h>
#ifdef M_CORE_GBA
#include <mgba/internal/gba/sio/lockstep.h>
#endif
#ifdef M_CORE_GB
#include <mgba/internal/gb/sio/lockstep.h>
#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);
}

73
tester.pl Normal file
View File

@ -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;
}