File: multi_xfer.cpp

package info (click to toggle)
freespace2 24.2.0%2Brepack-1
  • links: PTS, VCS
  • area: non-free
  • in suites: forky, sid
  • size: 43,716 kB
  • sloc: cpp: 595,001; ansic: 21,741; python: 1,174; sh: 457; makefile: 248; xml: 181
file content (1182 lines) | stat: -rw-r--r-- 31,407 bytes parent folder | download | duplicates (2)
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
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
/*
 * 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	
	UI_TIMESTAMP 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

	void init() {
		flags = 0;
		filename[0] = '\0';
		ex_filename[0] = '\0';
		file = nullptr;
		file_size = 0;
		file_ptr = 0;
		file_chksum = 0;
		file_socket = PSNET_INVALID_SOCKET;
		force_dir = 0;
		sig = 0;
	}

	xfer_entry() { init(); }
} 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
	for (auto &entry : Multi_xfer_entry) {
		entry.init();
	}

	// 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
	for (auto &entry : Multi_xfer_entry) {
		entry.init();
	}
}

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

	// 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
	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
	xe->init();
}

// 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
	xe->init();
}

// 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 = ui_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.isValid() && ui_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 = UI_TIMESTAMP::invalid();

	// 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
	xe->init();
}

// 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 = ui_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];
		xe->init();
	}		

	// 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 = ui_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
		xe->init();
		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
		xe->init();
		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 = ui_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 = ui_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;
}