mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-08 07:28:22 -10:00
1168 lines
31 KiB
C++
1168 lines
31 KiB
C++
/*
|
|
* Copyright (C) Volition, Inc. 1999. All rights reserved.
|
|
*
|
|
* All source code herein is the property of Volition, Inc. You may not sell
|
|
* or otherwise commercially exploit the source or things you created based on the
|
|
* source.
|
|
*
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "network/multi_xfer.h"
|
|
#include "network/multi.h"
|
|
#include "network/multimsgs.h"
|
|
#include "network/psnet2.h"
|
|
#include "io/timer.h"
|
|
#include "cfile/cfile.h"
|
|
|
|
#ifndef NDEBUG
|
|
#include "playerman/player.h"
|
|
#include "network/multiutil.h"
|
|
#include "network/multi_log.h"
|
|
#endif
|
|
|
|
|
|
|
|
|
|
// ------------------------------------------------------------------------------------------
|
|
// MULTI XFER DEFINES/VARS
|
|
//
|
|
|
|
#define MULTI_XFER_VERBOSE // keep this defined for verbose debug output
|
|
|
|
#define MULTI_XFER_INVALID_HANDLE(handle) ( (handle < 0) || (handle > (MAX_XFER_ENTRIES-1)) || !(Multi_xfer_entry[handle].flags & MULTI_XFER_FLAG_USED) || (strlen(Multi_xfer_entry[handle].filename) <= 0) )
|
|
|
|
// packet codes
|
|
#define MULTI_XFER_CODE_ACK 0 // simple response to the last request
|
|
#define MULTI_XFER_CODE_NAK 1 // simple response to the last request
|
|
#define MULTI_XFER_CODE_HEADER 2 // file xfer header information follows, requires a HEADER_RESPONSE
|
|
#define MULTI_XFER_CODE_DATA 3 // data block follows, requires an ack
|
|
#define MULTI_XFER_CODE_FINAL 4 // indication from sender that xfer is complete, requires an ack
|
|
|
|
// entry flags
|
|
#define MULTI_XFER_FLAG_USED (1<<0) // this entry is in use
|
|
#define MULTI_XFER_FLAG_SEND (1<<1) // this entry is sending a file
|
|
#define MULTI_XFER_FLAG_RECV (1<<2) // this entry is receiving a file
|
|
#define MULTI_XFER_FLAG_PENDING (1<<3) // this entry is ready to send a header and start the process
|
|
#define MULTI_XFER_FLAG_WAIT_ACK (1<<4) // waiting for an ack/nak
|
|
#define MULTI_XFER_FLAG_WAIT_DATA (1<<5) // waiting for another block of data
|
|
#define MULTI_XFER_FLAG_UNKNOWN (1<<6) // xfer final has been sent, and we are waiting for a response
|
|
#define MULTI_XFER_FLAG_SUCCESS (1<<7) // finished xfer
|
|
#define MULTI_XFER_FLAG_FAIL (1<<8) // xfer failed
|
|
#define MULTI_XFER_FLAG_TIMEOUT (1<<9) // xfer has timed-out
|
|
#define MULTI_XFER_FLAG_QUEUE_CURRENT (1<<10) // for a set of XFER_FLAG_QUEUE'd files, this is the current one sending
|
|
|
|
// packet size for file xfer
|
|
#define MULTI_XFER_MAX_DATA_SIZE 490 // this will keep us within the MULTI_XFER_MAX_SIZE_LIMIT
|
|
|
|
// timeout for a given xfer operation
|
|
#define MULTI_XFER_TIMEOUT 10000
|
|
|
|
//XSTR:OFF
|
|
|
|
// temp filename header for xferring files
|
|
#define MULTI_XFER_FNAME_PREFIX "_fsx_"
|
|
|
|
//XSTR:ON
|
|
|
|
// xfer entries/events
|
|
#define MAX_XFER_ENTRIES 60 // the max allowed file xfer entries
|
|
typedef struct xfer_entry {
|
|
int flags; // status flags for this entry
|
|
char filename[MAX_FILENAME_LEN+1]; // filename of the currently xferring file
|
|
char ex_filename[MAX_FILENAME_LEN+10]; // filename with xfer prefix tacked on to the front
|
|
CFILE *file; // file handle of the current xferring file
|
|
int file_size; // total size of the file being xferred
|
|
int file_ptr; // total bytes we're received so far
|
|
ushort file_chksum; // used for checking successfully xferred files
|
|
PSNET_SOCKET_RELIABLE file_socket; // socket used to xfer the file
|
|
int xfer_stamp; // timestamp for the current operation
|
|
int force_dir; // force the file to go to this directory on receive (will override Multi_xfer_force_dir)
|
|
ushort sig; // identifying sig - sender specifies this
|
|
} xfer_entry;
|
|
xfer_entry Multi_xfer_entry[MAX_XFER_ENTRIES]; // the file xfer entries themselves
|
|
|
|
// callback function pointer for when we start receiving a file
|
|
void (*Multi_xfer_recv_notify)(int handle);
|
|
|
|
// lock for the xfer system
|
|
int Multi_xfer_locked;
|
|
|
|
// force directory type for receives
|
|
int Multi_xfer_force_dir;
|
|
|
|
// unique file signature - this along with a socket # is enough to identify all xfers
|
|
ushort Multi_xfer_sig = 0;
|
|
|
|
|
|
// ------------------------------------------------------------------------------------------
|
|
// MULTI XFER FORWARD DECLARATIONS
|
|
//
|
|
|
|
// evaluate the condition of the entry
|
|
void multi_xfer_eval_entry(xfer_entry *xe);
|
|
|
|
// set an entry to be "failed"
|
|
void multi_xfer_fail_entry(xfer_entry *xe);
|
|
|
|
// get a valid xfer entry handle
|
|
int multi_xfer_get_free_handle();
|
|
|
|
// process an ack for this entry
|
|
void multi_xfer_process_ack(xfer_entry *xe);
|
|
|
|
// process a nak for this entry
|
|
void multi_xfer_process_nak(xfer_entry *xe);
|
|
|
|
// process a "final" packet
|
|
void multi_xfer_process_final(xfer_entry *xe);
|
|
|
|
// process a data packet
|
|
void multi_xfer_process_data(xfer_entry *xe, ubyte *data, int data_size);
|
|
|
|
// process a header
|
|
void multi_xfer_process_header(ubyte *data, PSNET_SOCKET_RELIABLE who, ushort sig, char *filename, int file_size, ushort file_checksum);
|
|
|
|
// send the next block of outgoing data or a "final" packet if we're done
|
|
void multi_xfer_send_next(xfer_entry *xe);
|
|
|
|
// send an ack to the sender
|
|
void multi_xfer_send_ack(PSNET_SOCKET_RELIABLE socket, ushort sig);
|
|
|
|
// send a nak to the sender
|
|
void multi_xfer_send_nak(PSNET_SOCKET_RELIABLE socket, ushort sig);
|
|
|
|
// send a "final" packet
|
|
void multi_xfer_send_final(xfer_entry *xe);
|
|
|
|
// send the header to begin a file transfer
|
|
void multi_xfer_send_header(xfer_entry *xe);
|
|
|
|
// convert the filename into the prefixed ex_filename
|
|
void multi_xfer_conv_prefix(char *filename, char *ex_filename);
|
|
|
|
// get a new xfer sig
|
|
ushort multi_xfer_get_sig();
|
|
|
|
// ------------------------------------------------------------------------------------------
|
|
// MULTI XFER FUNCTIONS
|
|
//
|
|
|
|
// initialize all file xfer transaction stuff, call in multi_level_init()
|
|
void multi_xfer_init(void (*multi_xfer_recv_callback)(int handle))
|
|
{
|
|
// blast all the entries
|
|
memset(Multi_xfer_entry,0,sizeof(xfer_entry) * MAX_XFER_ENTRIES);
|
|
|
|
// assign the receive callback function pointer
|
|
Multi_xfer_recv_notify = multi_xfer_recv_callback;
|
|
|
|
// unlocked
|
|
Multi_xfer_locked = 0;
|
|
|
|
// no forced directory
|
|
Multi_xfer_force_dir = CF_TYPE_MULTI_CACHE;
|
|
}
|
|
|
|
// do frame for all file xfers, call in multi_do_frame()
|
|
void multi_xfer_do()
|
|
{
|
|
int idx;
|
|
|
|
// process all valid xfer entries
|
|
for(idx=0;idx<MAX_XFER_ENTRIES;idx++){
|
|
// if this one is actually in use and has not finished for one reason or another
|
|
if((Multi_xfer_entry[idx].flags & MULTI_XFER_FLAG_USED) && !(Multi_xfer_entry[idx].flags & (MULTI_XFER_FLAG_SUCCESS | MULTI_XFER_FLAG_FAIL | MULTI_XFER_FLAG_TIMEOUT))){
|
|
// evaluate the condition of this entry (fail, timeout, etc)
|
|
multi_xfer_eval_entry(&Multi_xfer_entry[idx]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// reset the xfer system, including shutting down/killing all active xfers
|
|
void multi_xfer_reset()
|
|
{
|
|
int idx;
|
|
|
|
// shut down all active xfers
|
|
for(idx=0;idx<MAX_XFER_ENTRIES;idx++){
|
|
if(Multi_xfer_entry[idx].flags & MULTI_XFER_FLAG_USED){
|
|
multi_xfer_abort(idx);
|
|
}
|
|
}
|
|
|
|
// blast all the memory clean
|
|
memset(Multi_xfer_entry,0,sizeof(xfer_entry) * MAX_XFER_ENTRIES);
|
|
}
|
|
|
|
// send a file to the specified player, return a handle
|
|
int multi_xfer_send_file(PSNET_SOCKET_RELIABLE who, char *filename, int cfile_flags, int flags)
|
|
{
|
|
xfer_entry temp_entry;
|
|
int handle;
|
|
|
|
// if the system is locked, return -1
|
|
if(Multi_xfer_locked){
|
|
return -1;
|
|
}
|
|
|
|
// attempt to get a free handle
|
|
handle = multi_xfer_get_free_handle();
|
|
if(handle == -1){
|
|
return -1;
|
|
}
|
|
|
|
// clear the temp entry
|
|
memset(&temp_entry,0,sizeof(xfer_entry));
|
|
|
|
// set the filename
|
|
strcpy_s(temp_entry.filename,filename);
|
|
|
|
// attempt to open the file
|
|
temp_entry.file = NULL;
|
|
temp_entry.file = cfopen(filename,"rb",CFILE_NORMAL,cfile_flags);
|
|
if(temp_entry.file == NULL){
|
|
#ifdef MULTI_XFER_VERBOSE
|
|
nprintf(("Network","MULTI XFER : Could not open file %s on xfer send!\n",filename));
|
|
#endif
|
|
|
|
return -1;
|
|
}
|
|
|
|
// set the file size
|
|
temp_entry.file_size = -1;
|
|
temp_entry.file_size = cfilelength(temp_entry.file);
|
|
if(temp_entry.file_size == -1){
|
|
#ifdef MULTI_XFER_VERBOSE
|
|
nprintf(("Network","MULTI XFER : Could not get file length for file %s on xfer send\n",filename));
|
|
#endif
|
|
return -1;
|
|
}
|
|
temp_entry.file_ptr = 0;
|
|
|
|
// get the file checksum
|
|
if(!cf_chksum_short(temp_entry.file,&temp_entry.file_chksum)){
|
|
#ifdef MULTI_XFER_VERBOSE
|
|
nprintf(("Network","MULTI XFER : Could not get file checksum for file %s on xfer send\n",filename));
|
|
#endif
|
|
return -1;
|
|
}
|
|
#ifdef MULTI_XFER_VERBOSE
|
|
nprintf(("Network","MULTI XFER : Got file %s checksum of %d\n",temp_entry.filename,(int)temp_entry.file_chksum));
|
|
#endif
|
|
// rewind the file pointer to the beginning of the file
|
|
cfseek(temp_entry.file,0,CF_SEEK_SET);
|
|
|
|
// set the flags
|
|
temp_entry.flags |= (MULTI_XFER_FLAG_USED | MULTI_XFER_FLAG_SEND | MULTI_XFER_FLAG_PENDING);
|
|
temp_entry.flags |= flags;
|
|
|
|
// set the socket
|
|
temp_entry.file_socket = who;
|
|
|
|
// set the signature
|
|
temp_entry.sig = multi_xfer_get_sig();
|
|
|
|
// copy to the global array
|
|
memset(&Multi_xfer_entry[handle],0,sizeof(xfer_entry));
|
|
memcpy(&Multi_xfer_entry[handle],&temp_entry,sizeof(xfer_entry));
|
|
|
|
return handle;
|
|
}
|
|
|
|
// get the status of the current file xfer
|
|
int multi_xfer_get_status(int handle)
|
|
{
|
|
// if this is an invalid or an unused handle, notify as such
|
|
if((handle < 0) || (handle > (MAX_XFER_ENTRIES-1)) || !(Multi_xfer_entry[handle].flags & MULTI_XFER_FLAG_USED) ){
|
|
return MULTI_XFER_NONE;
|
|
}
|
|
|
|
// if the xfer has timed-out
|
|
if(Multi_xfer_entry[handle].flags & MULTI_XFER_FLAG_TIMEOUT){
|
|
return MULTI_XFER_TIMEDOUT;
|
|
}
|
|
|
|
// if the xfer has failed for one reason or another (not timeout)
|
|
if(Multi_xfer_entry[handle].flags & MULTI_XFER_FLAG_FAIL){
|
|
return MULTI_XFER_FAIL;
|
|
}
|
|
|
|
// if the xfer has succeeded
|
|
if(Multi_xfer_entry[handle].flags & MULTI_XFER_FLAG_SUCCESS){
|
|
return MULTI_XFER_SUCCESS;
|
|
}
|
|
|
|
// if the xfer is queued
|
|
if((Multi_xfer_entry[handle].flags & MULTI_XFER_FLAG_QUEUE) && !(Multi_xfer_entry[handle].flags & MULTI_XFER_FLAG_QUEUE_CURRENT)){
|
|
return MULTI_XFER_QUEUED;
|
|
}
|
|
|
|
// otherwise the xfer is still in progress
|
|
return MULTI_XFER_IN_PROGRESS;
|
|
}
|
|
|
|
// abort a transferring file
|
|
void multi_xfer_abort(int handle)
|
|
{
|
|
xfer_entry *xe;
|
|
|
|
// don't do anything if this is an invalid handle
|
|
if(MULTI_XFER_INVALID_HANDLE(handle)){
|
|
return;
|
|
}
|
|
|
|
// get e handle to the entry
|
|
xe = &Multi_xfer_entry[handle];
|
|
|
|
// close any open file and delete it
|
|
if(xe->file != NULL){
|
|
cfclose(xe->file);
|
|
xe->file = NULL;
|
|
|
|
// delete it if there isn't some problem with the filename
|
|
if((xe->flags & MULTI_XFER_FLAG_RECV) && (xe->filename[0] != '\0')){
|
|
cf_delete(xe->ex_filename, xe->force_dir);
|
|
}
|
|
}
|
|
|
|
// zero the socket
|
|
xe->file_socket = PSNET_INVALID_SOCKET;
|
|
|
|
// blast the entry
|
|
memset(xe,0,sizeof(xfer_entry));
|
|
}
|
|
|
|
// release an xfer handle
|
|
void multi_xfer_release_handle(int handle)
|
|
{
|
|
xfer_entry *xe;
|
|
|
|
// don't do anything if this is an invalid handle
|
|
if(MULTI_XFER_INVALID_HANDLE(handle)){
|
|
return;
|
|
}
|
|
|
|
// get e handle to the entry
|
|
xe = &Multi_xfer_entry[handle];
|
|
|
|
// close any open file and delete it
|
|
if(xe->file != NULL){
|
|
cfclose(xe->file);
|
|
xe->file = NULL;
|
|
|
|
// delete it if the file was not successfully received
|
|
if(!(xe->flags & MULTI_XFER_FLAG_SUCCESS) && (xe->flags & MULTI_XFER_FLAG_RECV) && (xe->filename[0] != '\0')){
|
|
cf_delete(xe->ex_filename,xe->force_dir);
|
|
}
|
|
}
|
|
|
|
// zero the socket
|
|
xe->file_socket = PSNET_INVALID_SOCKET;
|
|
|
|
// blast the entry
|
|
memset(xe,0,sizeof(xfer_entry));
|
|
}
|
|
|
|
// get the filename of the xfer for the given handle
|
|
char *multi_xfer_get_filename(int handle)
|
|
{
|
|
// if this is an invalid handle, return NULL
|
|
if(MULTI_XFER_INVALID_HANDLE(handle)){
|
|
return NULL;
|
|
}
|
|
|
|
// otherwise return the string
|
|
return Multi_xfer_entry[handle].filename;
|
|
}
|
|
|
|
// lock the xfer system (don't accept incoming files, don't allow outgoing files)
|
|
void multi_xfer_lock()
|
|
{
|
|
Multi_xfer_locked = 1;
|
|
}
|
|
|
|
// unlock the xfer system
|
|
void multi_xfer_unlock()
|
|
{
|
|
Multi_xfer_locked = 0;
|
|
}
|
|
|
|
// force all receives to go into the specified directory by cfile type
|
|
void multi_xfer_force_dir(int cf_type)
|
|
{
|
|
Multi_xfer_force_dir = cf_type;
|
|
Assert(Multi_xfer_force_dir > CF_TYPE_ANY);
|
|
}
|
|
|
|
// forces the given xfer entry to the specified directory type (only valid when called from the recv_callback function)
|
|
void multi_xfer_handle_force_dir(int handle,int cf_type)
|
|
{
|
|
// if this is an invalid handle, return NULL
|
|
if(MULTI_XFER_INVALID_HANDLE(handle)){
|
|
return;
|
|
}
|
|
|
|
// force to go to the given directory
|
|
Multi_xfer_entry[handle].force_dir = cf_type;
|
|
Assert(Multi_xfer_entry[handle].force_dir > CF_TYPE_ANY);
|
|
}
|
|
|
|
// or the flag on a given entry
|
|
void multi_xfer_xor_flags(int handle,int flags)
|
|
{
|
|
// if this is an invalid handle, return NULL
|
|
if(MULTI_XFER_INVALID_HANDLE(handle)){
|
|
return;
|
|
}
|
|
|
|
// xor the flags
|
|
Multi_xfer_entry[handle].flags ^= flags;
|
|
}
|
|
|
|
// get the flags for a given entry
|
|
int multi_xfer_get_flags(int handle)
|
|
{
|
|
// if this is an invalid handle, return NULL
|
|
if(MULTI_XFER_INVALID_HANDLE(handle)){
|
|
return -1;
|
|
}
|
|
|
|
// return the flags
|
|
return Multi_xfer_entry[handle].flags;
|
|
}
|
|
|
|
// if the passed filename is being xferred, return the xfer handle, otherwise return -1
|
|
int multi_xfer_lookup(char *filename)
|
|
{
|
|
int idx;
|
|
|
|
// if we have an invalid filename, do nothing
|
|
if((filename == NULL) || (strlen(filename) <= 0)){
|
|
return 0;
|
|
}
|
|
|
|
// otherwise, perform a lookup
|
|
for(idx=0;idx<MAX_XFER_ENTRIES;idx++){
|
|
// if we found a matching filename
|
|
if((Multi_xfer_entry[idx].flags & MULTI_XFER_FLAG_USED) && !stricmp(filename,Multi_xfer_entry[idx].filename)){
|
|
return idx;
|
|
}
|
|
}
|
|
|
|
// did not find a match
|
|
return -1;
|
|
}
|
|
|
|
// get the % of completion of the passed file handle, return < 0 if invalid
|
|
float multi_xfer_pct_complete(int handle)
|
|
{
|
|
// if this is an invalid handle, return invalid
|
|
if(MULTI_XFER_INVALID_HANDLE(handle)){
|
|
return -1.0f;
|
|
}
|
|
|
|
// if the file size is 0, return invalid
|
|
if(Multi_xfer_entry[handle].file_size == 0){
|
|
return -1.0f;
|
|
}
|
|
|
|
// return the pct completion
|
|
return (float)Multi_xfer_entry[handle].file_ptr / (float)Multi_xfer_entry[handle].file_size;
|
|
}
|
|
|
|
// get the socket of the file xfer (useful for identifying players)
|
|
uint multi_xfer_get_sock(int handle)
|
|
{
|
|
// if this is an invalid handle, return NULL
|
|
if(MULTI_XFER_INVALID_HANDLE(handle)){
|
|
return PSNET_INVALID_SOCKET;
|
|
}
|
|
|
|
return Multi_xfer_entry[handle].file_socket;
|
|
}
|
|
|
|
// get the CF_TYPE of the directory this file is going to
|
|
int multi_xfer_get_force_dir(int handle)
|
|
{
|
|
// if this is an invalid handle, return NULL
|
|
if(MULTI_XFER_INVALID_HANDLE(handle)){
|
|
return PSNET_INVALID_SOCKET;
|
|
}
|
|
|
|
return Multi_xfer_entry[handle].force_dir;
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------------------------------
|
|
// MULTI XFER FORWARD DECLARATIONS
|
|
//
|
|
|
|
// evaluate the condition of the entry
|
|
void multi_xfer_eval_entry(xfer_entry *xe)
|
|
{
|
|
int idx;
|
|
int found;
|
|
xfer_entry *xe_c;
|
|
|
|
// if the entry is marked as successful, then don't do anything
|
|
if(xe->flags & MULTI_XFER_FLAG_SUCCESS){
|
|
return;
|
|
}
|
|
|
|
// if the entry is queued
|
|
if(xe->flags & MULTI_XFER_FLAG_QUEUE){
|
|
// if the entry is not current
|
|
if(!(xe->flags & MULTI_XFER_FLAG_QUEUE_CURRENT)){
|
|
// see if there are any other queued up xfers to this target. if not, make me current and start sending
|
|
found = 0;
|
|
for(idx=0; idx<MAX_XFER_ENTRIES; idx++){
|
|
xe_c = &Multi_xfer_entry[idx];
|
|
|
|
// if this is a valid entry and is a queued entry and is going to the same target
|
|
if((xe_c->flags & MULTI_XFER_FLAG_USED) && (xe_c->file_socket == xe->file_socket) && (xe_c->flags & MULTI_XFER_FLAG_SEND) &&
|
|
(xe_c->flags & MULTI_XFER_FLAG_QUEUE) && (xe_c->flags & MULTI_XFER_FLAG_QUEUE_CURRENT)){
|
|
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// if we found no other entries, make this guy current and pending
|
|
if(!found){
|
|
xe->flags |= MULTI_XFER_FLAG_QUEUE_CURRENT;
|
|
xe->flags |= MULTI_XFER_FLAG_PENDING;
|
|
|
|
#ifdef MULTI_XFER_VERBOSE
|
|
nprintf(("Network","MULTI_XFER : Starting xfer send for queued entry %s\n", xe->filename));
|
|
#endif
|
|
}
|
|
// otherwise, do nothing for him - he has to still wait
|
|
else {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// if the entry is marked as pending - send out the header to get the ball rolling
|
|
if(xe->flags & MULTI_XFER_FLAG_PENDING){
|
|
// send the header to begin the transfer
|
|
multi_xfer_send_header(xe);
|
|
|
|
// set the timestamp
|
|
xe->xfer_stamp = timestamp(MULTI_XFER_TIMEOUT);
|
|
|
|
// unset the pending flag
|
|
xe->flags &= ~(MULTI_XFER_FLAG_PENDING);
|
|
|
|
// set the ack/wait flag
|
|
xe->flags |= MULTI_XFER_FLAG_WAIT_ACK;
|
|
}
|
|
|
|
// see if the entry has timed-out for one reason or another
|
|
if((xe->xfer_stamp != -1) && timestamp_elapsed(xe->xfer_stamp)){
|
|
xe->flags |= MULTI_XFER_FLAG_TIMEOUT;
|
|
|
|
// if we should be auto-destroying this entry, do so
|
|
if(xe->flags & MULTI_XFER_FLAG_AUTODESTROY){
|
|
multi_xfer_fail_entry(xe);
|
|
}
|
|
}
|
|
}
|
|
|
|
// lookup a file xfer entry by player
|
|
xfer_entry *multi_xfer_find_entry(PSNET_SOCKET_RELIABLE who, ushort sig, int sender_side)
|
|
{
|
|
int idx;
|
|
|
|
// look through all valid xfer entries
|
|
for(idx=0;idx<MAX_XFER_ENTRIES;idx++){
|
|
// if we're looking for sending entries
|
|
if(sender_side && !(Multi_xfer_entry[idx].flags & MULTI_XFER_FLAG_SEND)){
|
|
continue;
|
|
}
|
|
// if we're looking for recv entries
|
|
if(!sender_side && !(Multi_xfer_entry[idx].flags & MULTI_XFER_FLAG_RECV)){
|
|
continue;
|
|
}
|
|
|
|
// if we found a match
|
|
if((Multi_xfer_entry[idx].file_socket == who) && (Multi_xfer_entry[idx].sig == sig)){
|
|
return &Multi_xfer_entry[idx];
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
// set an entry to be "failed"
|
|
void multi_xfer_fail_entry(xfer_entry *xe)
|
|
{
|
|
// set its flags appropriately
|
|
xe->flags &= ~(MULTI_XFER_FLAG_WAIT_ACK | MULTI_XFER_FLAG_WAIT_DATA | MULTI_XFER_FLAG_UNKNOWN);
|
|
xe->flags |= MULTI_XFER_FLAG_FAIL;
|
|
|
|
// close the file pointer
|
|
if(xe->file != NULL){
|
|
cfclose(xe->file);
|
|
xe->file = NULL;
|
|
}
|
|
|
|
// delete the file
|
|
if((xe->flags & MULTI_XFER_FLAG_RECV) && (xe->filename[0] != '\0')){
|
|
cf_delete(xe->ex_filename,xe->force_dir);
|
|
}
|
|
|
|
// null the timestamp
|
|
xe->xfer_stamp = -1;
|
|
|
|
// if we should be auto-destroying this entry, do so
|
|
if(xe->flags & MULTI_XFER_FLAG_AUTODESTROY){
|
|
multi_xfer_release_handle((int)std::distance(Multi_xfer_entry, xe));
|
|
}
|
|
|
|
// blast the memory clean
|
|
memset(xe,0,sizeof(xfer_entry));
|
|
}
|
|
|
|
// get a valid xfer entry handle
|
|
int multi_xfer_get_free_handle()
|
|
{
|
|
int idx;
|
|
|
|
// look for a free entry
|
|
for(idx=0;idx<MAX_XFER_ENTRIES;idx++){
|
|
if(!(Multi_xfer_entry[idx].flags & MULTI_XFER_FLAG_USED)){
|
|
return idx;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------------------------------
|
|
// MULTI XFER PACKET HANDLERS
|
|
//
|
|
|
|
// process an incoming file xfer data packet, return bytes processed, guaranteed to process the entire
|
|
// packet regardless of error conditions
|
|
int multi_xfer_process_packet(unsigned char *data, PSNET_SOCKET_RELIABLE who)
|
|
{
|
|
ubyte val;
|
|
xfer_entry *xe;
|
|
char filename[255];
|
|
ushort data_size = 0;
|
|
int file_size = -1;
|
|
ushort file_checksum = 0;
|
|
int offset = 0;
|
|
ubyte xfer_data[600];
|
|
ushort sig;
|
|
int sender_side = 1;
|
|
|
|
// read in all packet data
|
|
GET_DATA(val);
|
|
GET_USHORT(sig);
|
|
switch(val){
|
|
// RECV side
|
|
case MULTI_XFER_CODE_DATA:
|
|
GET_USHORT(data_size);
|
|
memcpy(xfer_data, data + offset, data_size);
|
|
offset += data_size;
|
|
sender_side = 0;
|
|
break;
|
|
|
|
// RECV side
|
|
case MULTI_XFER_CODE_HEADER:
|
|
GET_STRING(filename);
|
|
GET_INT(file_size);
|
|
GET_USHORT(file_checksum);
|
|
sender_side = 0;
|
|
break;
|
|
|
|
// SEND side
|
|
case MULTI_XFER_CODE_ACK:
|
|
case MULTI_XFER_CODE_NAK:
|
|
break;
|
|
|
|
// RECV side
|
|
case MULTI_XFER_CODE_FINAL:
|
|
sender_side = 0;
|
|
break;
|
|
|
|
default:
|
|
Int3();
|
|
}
|
|
|
|
// at this point we've read all the data in the packet
|
|
|
|
// at this point, we should process code-specific data
|
|
xe = NULL;
|
|
if(val != MULTI_XFER_CODE_HEADER){
|
|
// if the code is not a request or a header, we need to look up the existing xfer_entry
|
|
xe = NULL;
|
|
|
|
xe = multi_xfer_find_entry(who, sig, sender_side);
|
|
if(xe == NULL){
|
|
#ifdef MULTI_XFER_VERBOSE
|
|
nprintf(("Network","MULTI XFER : Could not find xfer entry for incoming data!\n"));
|
|
|
|
// this is a rare case - I'm not overly concerned about it. But it _does_ happen. So blech
|
|
#ifndef NDEBUG
|
|
int np_index = find_player_socket(who);
|
|
ml_string("MULTI XFER : Could not find xfer entry for incoming data :");
|
|
ml_printf(": sig == %d", sig);
|
|
ml_printf(": xfer header == %d", val);
|
|
if(np_index < 0){
|
|
ml_string(": player == unknown");
|
|
} else {
|
|
ml_printf(": player == %s", Net_players[np_index].m_player->callsign);
|
|
}
|
|
if(sender_side){
|
|
ml_string(": sending");
|
|
} else {
|
|
ml_string(": receiving");
|
|
}
|
|
#endif
|
|
#endif
|
|
return offset;
|
|
}
|
|
}
|
|
|
|
switch((int)val){
|
|
// process an ack for this entry
|
|
case MULTI_XFER_CODE_ACK :
|
|
Assert(xe != NULL);
|
|
multi_xfer_process_ack(xe);
|
|
break;
|
|
|
|
// process a nak for this entry
|
|
case MULTI_XFER_CODE_NAK :
|
|
Assert(xe != NULL);
|
|
multi_xfer_process_nak(xe);
|
|
break;
|
|
|
|
// process a "final" packet
|
|
case MULTI_XFER_CODE_FINAL :
|
|
Assert(xe != NULL);
|
|
multi_xfer_process_final(xe);
|
|
break;
|
|
|
|
// process a data packet
|
|
case MULTI_XFER_CODE_DATA :
|
|
Assert(xe != NULL);
|
|
multi_xfer_process_data(xe, xfer_data, data_size);
|
|
break;
|
|
|
|
// process a header
|
|
case MULTI_XFER_CODE_HEADER :
|
|
// send on my reliable socket
|
|
multi_xfer_process_header(xfer_data, who, sig, filename, file_size, file_checksum);
|
|
break;
|
|
}
|
|
return offset;
|
|
}
|
|
|
|
// process an ack for this entry
|
|
void multi_xfer_process_ack(xfer_entry *xe)
|
|
{
|
|
// if we are a sender
|
|
if(xe->flags & MULTI_XFER_FLAG_SEND){
|
|
// if we are waiting on a final ack, then the transfer has completed successfully
|
|
if(xe->flags & MULTI_XFER_FLAG_UNKNOWN){
|
|
xe->flags &= ~(MULTI_XFER_FLAG_UNKNOWN);
|
|
xe->flags |= MULTI_XFER_FLAG_SUCCESS;
|
|
|
|
#ifdef MULTI_XFER_VERBOSE
|
|
nprintf(("Network", "MULTI XFER : Successfully sent file %s\n", xe->filename));
|
|
#endif
|
|
|
|
// if we should be auto-destroying this entry, do so
|
|
if(xe->flags & MULTI_XFER_FLAG_AUTODESTROY){
|
|
multi_xfer_release_handle((int)std::distance(Multi_xfer_entry, xe));
|
|
}
|
|
}
|
|
// otherwise if we're waiting for an ack, we should send the next chunk of data or a "final" packet if we're done
|
|
else if(xe->flags & MULTI_XFER_FLAG_WAIT_ACK){
|
|
multi_xfer_send_next(xe);
|
|
}
|
|
}
|
|
}
|
|
|
|
// process a nak for this entry
|
|
void multi_xfer_process_nak(xfer_entry *xe)
|
|
{
|
|
// if we get an ack at any time we should simply set the xfer to failed
|
|
multi_xfer_fail_entry(xe);
|
|
}
|
|
|
|
// process a "final" packet
|
|
void multi_xfer_process_final(xfer_entry *xe)
|
|
{
|
|
ushort chksum;
|
|
|
|
// make sure we skip a line
|
|
nprintf(("Network","\n"));
|
|
|
|
// close the file
|
|
if(xe->file != NULL){
|
|
cflush(xe->file);
|
|
cfclose(xe->file);
|
|
xe->file = NULL;
|
|
}
|
|
|
|
// check to make sure the file checksum is the same
|
|
chksum = 0;
|
|
if(!cf_chksum_short(xe->ex_filename, &chksum, -1, xe->force_dir) || (chksum != xe->file_chksum)){
|
|
// mark as failed
|
|
xe->flags |= MULTI_XFER_FLAG_FAIL;
|
|
|
|
#ifdef MULTI_XFER_VERBOSE
|
|
nprintf(("Network","MULTI XFER : file %s failed checksum %d %d!\n",xe->ex_filename, (int)xe->file_chksum, (int)chksum));
|
|
#endif
|
|
|
|
// abort the xfer
|
|
multi_xfer_abort((int)std::distance(Multi_xfer_entry, xe));
|
|
return;
|
|
}
|
|
// checksums check out, so rename the file and be done with it
|
|
else {
|
|
#ifdef MULTI_XFER_VERBOSE
|
|
nprintf(("Network","MULTI XFER : renaming xferred file from %s to %s (chksum %d %d)\n", xe->ex_filename, xe->filename, (int)xe->file_chksum, (int)chksum));
|
|
#endif
|
|
// rename the file properly
|
|
if(cf_rename(xe->ex_filename,xe->filename, xe->force_dir) == CF_RENAME_SUCCESS){
|
|
// mark the xfer as being successful
|
|
xe->flags |= MULTI_XFER_FLAG_SUCCESS;
|
|
|
|
nprintf(("Network","MULTI XFER : SUCCESSFULLY TRANSFERRED FILE %s (%d bytes)\n", xe->filename, xe->file_size));
|
|
|
|
// send an ack to the sender
|
|
multi_xfer_send_ack(xe->file_socket, xe->sig);
|
|
} else {
|
|
// mark it as failing
|
|
xe->flags |= MULTI_XFER_FLAG_FAIL;
|
|
nprintf(("Network","FAILED TO TRANSFER FILE (could not rename temp file %s)\n", xe->ex_filename));
|
|
|
|
// delete the tempfile
|
|
cf_delete(xe->ex_filename, xe->force_dir);
|
|
|
|
// send an nak to the sender
|
|
multi_xfer_send_nak(xe->file_socket, xe->sig);
|
|
}
|
|
|
|
// if we should be auto-destroying this entry, do so
|
|
if(xe->flags & MULTI_XFER_FLAG_AUTODESTROY){
|
|
multi_xfer_release_handle((int)std::distance(Multi_xfer_entry, xe));
|
|
}
|
|
}
|
|
}
|
|
|
|
// process a data packet
|
|
void multi_xfer_process_data(xfer_entry *xe, ubyte *data, int data_size)
|
|
{
|
|
// print out a crude progress indicator
|
|
nprintf(("Network","."));
|
|
|
|
// attempt to write the rest of the data string to the file
|
|
if((xe->file == NULL) || !cfwrite(data, data_size, 1, xe->file)){
|
|
// inform the sender we had a problem
|
|
multi_xfer_send_nak(xe->file_socket, xe->sig);
|
|
|
|
// fail this entry
|
|
multi_xfer_fail_entry(xe);
|
|
|
|
xe->file_ptr += data_size;
|
|
return;
|
|
}
|
|
|
|
// increment the file pointer
|
|
xe->file_ptr += data_size;
|
|
|
|
// send an ack to the sender
|
|
multi_xfer_send_ack(xe->file_socket, xe->sig);
|
|
|
|
// set the timestmp
|
|
xe->xfer_stamp = timestamp(MULTI_XFER_TIMEOUT);
|
|
}
|
|
|
|
// process a header, return bytes processed
|
|
void multi_xfer_process_header(ubyte * /*data*/, PSNET_SOCKET_RELIABLE who, ushort sig, char *filename, int file_size, ushort file_checksum)
|
|
{
|
|
xfer_entry *xe;
|
|
int handle;
|
|
|
|
// if the xfer system is locked, send a nak
|
|
if(Multi_xfer_locked){
|
|
multi_xfer_send_nak(who, sig);
|
|
return;
|
|
}
|
|
|
|
// try and get a free xfer handle
|
|
handle = multi_xfer_get_free_handle();
|
|
if(handle == -1){
|
|
multi_xfer_send_nak(who, sig);
|
|
return;
|
|
} else {
|
|
xe = &Multi_xfer_entry[handle];
|
|
memset(xe,0,sizeof(xfer_entry));
|
|
}
|
|
|
|
// set the recv and used flags
|
|
xe->flags |= (MULTI_XFER_FLAG_USED | MULTI_XFER_FLAG_RECV);
|
|
|
|
// get the header data
|
|
xe->file_size = file_size;
|
|
|
|
// get the file chksum
|
|
xe->file_chksum = file_checksum;
|
|
|
|
// set the socket
|
|
xe->file_socket = who;
|
|
|
|
// set the timeout timestamp
|
|
xe->xfer_stamp = timestamp(MULTI_XFER_TIMEOUT);
|
|
|
|
// set the sig
|
|
xe->sig = sig;
|
|
|
|
// copy the filename and get the prefixed xfer filename
|
|
strcpy_s(xe->filename, filename);
|
|
multi_xfer_conv_prefix(xe->filename, xe->ex_filename);
|
|
#ifdef MULTI_XFER_VERBOSE
|
|
nprintf(("Network","MULTI XFER : converted filename %s to %s\n",xe->filename, xe->ex_filename));
|
|
#endif
|
|
|
|
// determine what directory to place the file in
|
|
// individual xfer entries take precedence over the global multi xfer force entry
|
|
xe->force_dir = Multi_xfer_force_dir;
|
|
|
|
// call the callback function
|
|
Multi_xfer_recv_notify(handle);
|
|
|
|
// if the notify function invalidated this xfer handle, then cancel the whole thing
|
|
if(xe->flags & MULTI_XFER_FLAG_REJECT){
|
|
multi_xfer_send_nak(who, sig);
|
|
|
|
// clear the data
|
|
memset(xe, 0, sizeof(xfer_entry));
|
|
return;
|
|
}
|
|
|
|
// delete the old file (if it exists)
|
|
cf_delete( xe->filename, CF_TYPE_MULTI_CACHE );
|
|
cf_delete( xe->filename, CF_TYPE_MISSIONS );
|
|
|
|
// attempt to open the file (using the prefixed filename)
|
|
xe->file = NULL;
|
|
xe->file = cfopen(xe->ex_filename, "wb", CFILE_NORMAL, xe->force_dir);
|
|
if(xe->file == NULL){
|
|
multi_xfer_send_nak(who, sig);
|
|
|
|
// clear the data
|
|
memset(xe, 0, sizeof(xfer_entry));
|
|
return;
|
|
}
|
|
|
|
// set the waiting for data flag
|
|
xe->flags |= MULTI_XFER_FLAG_WAIT_DATA;
|
|
|
|
// send an ack to the server
|
|
multi_xfer_send_ack(who, sig);
|
|
|
|
#ifdef MULTI_XFER_VERBOSE
|
|
nprintf(("Network","MULTI XFER : AFTER HEADER %s\n",xe->filename));
|
|
#endif
|
|
}
|
|
|
|
// send the next block of outgoing data or a "final" packet if we're done
|
|
void multi_xfer_send_next(xfer_entry *xe)
|
|
{
|
|
ubyte data[MAX_PACKET_SIZE],code;
|
|
ushort data_size;
|
|
int packet_size = 0;
|
|
|
|
// print out a crude progress indicator
|
|
nprintf(("Network", "+"));
|
|
|
|
// if we've sent all the data, then we should send a "final" packet
|
|
if(xe->file_ptr >= xe->file_size){
|
|
// mark the entry as unknown
|
|
xe->flags |= MULTI_XFER_FLAG_UNKNOWN;
|
|
|
|
// set the timestmp
|
|
xe->xfer_stamp = timestamp(MULTI_XFER_TIMEOUT);
|
|
|
|
// send the packet
|
|
multi_xfer_send_final(xe);
|
|
|
|
return;
|
|
}
|
|
|
|
// build the header
|
|
BUILD_HEADER(XFER_PACKET);
|
|
|
|
// length of the added string
|
|
auto flen = strlen(xe->filename) + 4;
|
|
|
|
// determine how much data we are going to send with this packet and add it in
|
|
if((size_t)(xe->file_size - xe->file_ptr) >= (MULTI_XFER_MAX_DATA_SIZE - flen)){
|
|
data_size = (ushort)(MULTI_XFER_MAX_DATA_SIZE - flen);
|
|
} else {
|
|
data_size = (unsigned short)(xe->file_size - xe->file_ptr);
|
|
}
|
|
// increment the file pointer
|
|
xe->file_ptr += data_size;
|
|
|
|
// add the opcode
|
|
code = MULTI_XFER_CODE_DATA;
|
|
ADD_DATA(code);
|
|
|
|
// add the sig
|
|
ADD_USHORT(xe->sig);
|
|
|
|
// add in the size of the rest of the packet
|
|
ADD_USHORT(data_size);
|
|
|
|
// copy in the data
|
|
if(cfread(data+packet_size,1,(int)data_size,xe->file) == 0){
|
|
// send a nack to the receiver
|
|
multi_xfer_send_nak(xe->file_socket, xe->sig);
|
|
|
|
// fail this send
|
|
multi_xfer_fail_entry(xe);
|
|
return;
|
|
}
|
|
|
|
// increment the packet size
|
|
packet_size += (int)data_size;
|
|
|
|
// set the timestmp
|
|
xe->xfer_stamp = timestamp(MULTI_XFER_TIMEOUT);
|
|
|
|
// otherwise send the data
|
|
psnet_rel_send(xe->file_socket, data, packet_size);
|
|
}
|
|
|
|
// send an ack to the sender
|
|
void multi_xfer_send_ack(PSNET_SOCKET_RELIABLE socket, ushort sig)
|
|
{
|
|
ubyte data[MAX_PACKET_SIZE],code;
|
|
int packet_size = 0;
|
|
|
|
// build the header and add
|
|
BUILD_HEADER(XFER_PACKET);
|
|
|
|
// add the opcode
|
|
code = MULTI_XFER_CODE_ACK;
|
|
ADD_DATA(code);
|
|
|
|
// add the sig
|
|
ADD_USHORT(sig);
|
|
|
|
// send the data
|
|
psnet_rel_send(socket, data, packet_size);
|
|
}
|
|
|
|
// send a nak to the sender
|
|
void multi_xfer_send_nak(PSNET_SOCKET_RELIABLE socket, ushort sig)
|
|
{
|
|
ubyte data[MAX_PACKET_SIZE],code;
|
|
int packet_size = 0;
|
|
|
|
// build the header and add the code
|
|
BUILD_HEADER(XFER_PACKET);
|
|
|
|
// add the opcode
|
|
code = MULTI_XFER_CODE_NAK;
|
|
ADD_DATA(code);
|
|
|
|
// add the sig
|
|
ADD_USHORT(sig);
|
|
|
|
// send the data
|
|
psnet_rel_send(socket, data, packet_size);
|
|
}
|
|
|
|
// send a "final" packet
|
|
void multi_xfer_send_final(xfer_entry *xe)
|
|
{
|
|
ubyte data[MAX_PACKET_SIZE],code;
|
|
int packet_size = 0;
|
|
|
|
// build the header
|
|
BUILD_HEADER(XFER_PACKET);
|
|
|
|
// add the code
|
|
code = MULTI_XFER_CODE_FINAL;
|
|
ADD_DATA(code);
|
|
|
|
// add the sig
|
|
ADD_USHORT(xe->sig);
|
|
|
|
// send the data
|
|
psnet_rel_send(xe->file_socket, data, packet_size);
|
|
}
|
|
|
|
// send the header to begin a file transfer
|
|
void multi_xfer_send_header(xfer_entry *xe)
|
|
{
|
|
ubyte data[MAX_PACKET_SIZE],code;
|
|
int packet_size = 0;
|
|
|
|
// build the header and add the opcode
|
|
BUILD_HEADER(XFER_PACKET);
|
|
code = MULTI_XFER_CODE_HEADER;
|
|
ADD_DATA(code);
|
|
|
|
// add the sig
|
|
ADD_USHORT(xe->sig);
|
|
|
|
// add the filename
|
|
ADD_STRING(xe->filename);
|
|
|
|
// add the id #
|
|
ADD_INT(xe->file_size);
|
|
|
|
// add the file checksum
|
|
ADD_USHORT(xe->file_chksum);
|
|
|
|
// send the packet
|
|
psnet_rel_send(xe->file_socket, data, packet_size);
|
|
}
|
|
|
|
// convert the filename into the prefixed ex_filename
|
|
void multi_xfer_conv_prefix(char *filename,char *ex_filename)
|
|
{
|
|
char temp[MAX_FILENAME_LEN+50];
|
|
|
|
// blast the memory clean
|
|
memset(temp, 0, MAX_FILENAME_LEN+50);
|
|
|
|
// copy in the prefix
|
|
strcpy_s(temp, MULTI_XFER_FNAME_PREFIX);
|
|
|
|
// stick on the original name
|
|
strcat_s(temp, filename);
|
|
|
|
// copy the whole thing to the outgoing filename
|
|
strcpy(ex_filename, temp);
|
|
}
|
|
|
|
// get a new xfer sig
|
|
ushort multi_xfer_get_sig()
|
|
{
|
|
ushort ret = Multi_xfer_sig;
|
|
|
|
// new one
|
|
if(Multi_xfer_sig == 0xffff){
|
|
Multi_xfer_sig = 0;
|
|
} else {
|
|
Multi_xfer_sig++;
|
|
}
|
|
|
|
return ret;
|
|
}
|