// VisualBoyAdvance - Nintendo Gameboy/GameboyAdvance (TM) emulator.
// Copyright (C) 1999-2003 Forgotten
// Copyright (C) 2004 Forgotten and the VBA development team
// This file was written by denopqrihg

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2, or(at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software Foundation,
// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

// Link.cpp : Emulation of GBA link accessories
//

#include "GBA.h"
#include <stdio.h>
#include "../win32/stdafx.h"
#include "../port.h"
#include "GBALink.h"
#include "../win32/vba.h"
#include "../win32/MainWnd.h"
#include "../win32/LinkOptions.h"
#include "../win32/Reg.h"

#define UPDATE_REG(address, value) WRITE16LE(((u16 *)&ioMem[address]),value)
#define GBLINK_READY 8

int linktime = 0;
u8 tspeed=3;
u8 transfer=0;
LINKDATA *linkmem=NULL;
int linkid = 0, vbaid = 0;
HANDLE linksync[4];
int savedlinktime=0;
HANDLE mmf=NULL;
char linkevent[] = "VBA link event  ";
static int i, j;
int linktimeout = 1000;
int linklog = 0;
FILE *linklogfile = NULL;
LANLINKDATA lanlink;
u16 linkdata[4];
int lspeed = 0;
lserver ls;
lclient lc;
bool oncewait = false, after = false;
bool adapter = false;
bool linkenable = false;
u8 rfu_cmd, rfu_qsend, rfu_qrecv;
int rfu_state, rfu_polarity, linktime2, counter, rfu_masterq;
int transferend, numtransfers = 0;
u32 rfu_masterdata[32];

extern unsigned char *gbMemory;
extern int gbInterrupt;

int trtimedata[4][4] = {{34080, 8520, 5680, 2840}, {65536, 16384, 10923, 5461}, {99609, 24903, 16602, 8301}, {133692, 33423, 22282, 11141}};
int trtimeend[3][4] = {{72527, 18132, 12088, 6044}, {106608, 26652, 17768, 8884}, {133692, 33423, 22282, 11141}};
int gbtime = 1024;

DWORD WINAPI LinkClientThread(void *);
DWORD WINAPI LinkServerThread(void *);
int StartServer(void);
int GetSioMode(u16, u16);
u16 StartRFU(u16);

char *MakeInstanceFilename(const char *Input)
{
	if (vbaid == 0)
		return (char *)Input;

	static char *result=NULL;
	if (result!=NULL)
		free(result);

	result = (char *)malloc(strlen(Input)+3);
	char *p = strrchr((char *)Input, '.');
	sprintf(result, "%.*s-%d.%s", (int)(p-Input), Input, vbaid+1, p+1);
	return result;
}


void StartLink(WORD value){
	if(ioMem==NULL)	return;
		if(adapter){
			UPDATE_REG(0x128, StartRFU(value));
			return;
		}
		switch(GetSioMode(value, READ16LE(&ioMem[0x134]))){
		case MULTIPLAYER:
			if(value & 0x80){
				if(!linkid){
					if(!transfer){
						if(lanlink.active){
							if(lanlink.connected){
								linkdata[0] = READ16LE(&ioMem[0x12a]);
								savedlinktime = linktime;
								tspeed = value & 3;
								ls.Send();
								transfer = 1;
								linktime = 0;
								UPDATE_REG(0x120, linkdata[0]);
								UPDATE_REG(0x122, 0xffff);
								WRITE32LE(&ioMem[0x124], 0xffffffff);
								if(lanlink.speed&&oncewait==false) ls.howmanytimes++;
								after = false;
							}
						} else if(linkmem->numgbas>1){
							ResetEvent(linksync[0]);
							linkmem->linkcmd[0] = ('M'<<8)+(value&3);
							linkmem->linkdata[0] = READ16LE(&ioMem[0x12a]);
							if(linkmem->numtransfers!=0) linkmem->lastlinktime = linktime;
							else linkmem->lastlinktime = 0;
							if((++linkmem->numtransfers)==0) linkmem->numtransfers=2;
							transfer = 1;
							linktime = 0;
							tspeed = value & 3;
							WRITE32LE(&ioMem[0x120], 0xffffffff);
							WRITE32LE(&ioMem[0x124], 0xffffffff);
						}
					}
				}
				value &= 0xff7f;
				value |= (transfer!=0)<<7;
			}
			value &= 0xff8b;
			value |= (linkid ? 0xc : 8);
			value |= linkid<<4;
			UPDATE_REG(0x128, value);
			if(linkid) UPDATE_REG(0x134, 7);
			else UPDATE_REG(0x134, 3);
			break;
		case NORMAL8:
			if(linklog) fprintf(linklogfile, "Attempt to use 8-bit Normal mode %04x\n", value);
			UPDATE_REG(0x128, value);
			break;
		case NORMAL32:
			if(linklog) fprintf(linklogfile, "Attempt to use 32-bit Normal mode %04x %x%x\n", value, READ16LE(&ioMem[0x122]), READ16LE(&ioMem[0x120]));
			UPDATE_REG(0x128, value);
			break;
		case UART:
			if(linklog) fprintf(linklogfile, "Attempt to use UART mode %04x\n", value);
			UPDATE_REG(0x128, value);
			break;
		default:
			UPDATE_REG(0x128, value);
			break;
		}
}

void StartGPLink(u16 value){
	if(!value){
		UPDATE_REG(0x134, 0);
		return;
	}
	switch(GetSioMode(READ16LE(&ioMem[0x128]), value)){
	case MULTIPLAYER:
		value &= 0xc0f0;
		value |= 3;
		if(linkid) value |= 4;
		UPDATE_REG(0x134, value);
		UPDATE_REG(0x128, ((READ16LE(&ioMem[0x128])&0xff8b)|(linkid ? 0xc : 8)|(linkid<<4)));
		return;
		break;
	case GP:
		if(linklog){
			if(value==0x8000) fprintf(linklogfile, "Circuit reset\n");
			else if(!adapter) fprintf(linklogfile, "Attempt to use General-purpose mode %04x\n", value);
		}
		if(adapter) rfu_state = RFU_INIT;
		// This was not there, but sonic games won't start if it's not here.
		UPDATE_REG(0x134, value);
		break;
	case JOYBUS:
		UPDATE_REG(0x134, value);
		break;
	default:
		UPDATE_REG(0x134, value);
		break;
	}
	return;
}

void StartJOYLink(u16 value){
	if(!value){
		UPDATE_REG(0x140, 0);
		return;
	}
	if(GetSioMode(READ16LE(&ioMem[0x128]), READ16LE(&ioMem[0x134]))==JOYBUS&&linklog) fprintf(linklogfile, "Attempt to use JOY-BUS mode %04x\n", value);
	return;
}

void LinkUpdate(int ticks){
	linktime += ticks;
	if(adapter){
		linktime2 += ticks;
		transferend -= ticks;
		if(transfer&&transferend<=0){
			transfer = 0;
			if(READ16LE(&ioMem[0x128])&0x4000){
				IF |= 0x80;
				UPDATE_REG(0x202, IF);
			}
			UPDATE_REG(0x128, READ16LE(&ioMem[0x128]) & 0xff7f);
		}
		return;
	}

	if(lanlink.active){
		if(lanlink.connected){
			if(after){
				if(linkid&&linktime>6044){
					lc.Recv();
					oncewait = true;
				} else return;
			}
			if(linkid&&!transfer&&lc.numtransfers>0&&linktime>=savedlinktime){
				linkdata[linkid] = READ16LE(&ioMem[0x12a]);
				if(!lc.oncesend) lc.Send();
				lc.oncesend = false;
				UPDATE_REG(0x120, linkdata[0]);
				UPDATE_REG(0x128, READ16LE(&ioMem[0x128]) | 0x80);
				transfer = 1;
				if(lc.numtransfers==1) linktime = 0;
				else linktime -= savedlinktime;
			}
			if(transfer&&linktime>=trtimeend[lanlink.numgbas-1][tspeed]){
				if(READ16LE(&ioMem[0x128]) & 0x4000){
					IF |= 0x80;
					UPDATE_REG(0x202, IF);
				}
				UPDATE_REG(0x128, (READ16LE(&ioMem[0x128]) & 0xff0f) | (linkid << 4));
				transfer = 0;
				linktime -= trtimeend[lanlink.numgbas-1][tspeed];
				oncewait = false;
				if(!lanlink.speed){
					if(linkid) lc.Recv();
					else ls.Recv();
					UPDATE_REG(0x122, linkdata[1]);
					UPDATE_REG(0x124, linkdata[2]);
					UPDATE_REG(0x126, linkdata[3]);
					if(linklog) fprintf(linklogfile, "%04x %04x %04x %04x %10u\n", linkdata[0], linkdata[1], linkdata[2], linkdata[3], savedlinktime);
					oncewait = true;
				} else  {
					after = true;
					if(lanlink.numgbas==1){
						UPDATE_REG(0x122, linkdata[1]);
						UPDATE_REG(0x124, linkdata[2]);
						UPDATE_REG(0x126, linkdata[3]);
						if(linklog) fprintf(linklogfile, "%04x %04x %04x %04x %10u\n", linkdata[0], linkdata[1], linkdata[2], linkdata[3], savedlinktime);
					}

				}
			}
		}
		return;
	}
	// ** CRASH ** linkmem is NULL, todo investigate why, added null check
	if(linkid&&!transfer&&linkmem&&linktime>=linkmem->lastlinktime&&linkmem->numtransfers){
		linkmem->linkdata[linkid] = READ16LE(&ioMem[0x12a]);

		if(linkmem->numtransfers==1){
			linktime = 0;
			if(WaitForSingleObject(linksync[linkid], linktimeout)==WAIT_TIMEOUT) linkmem->numtransfers=0;
		} else linktime -= linkmem->lastlinktime;

		switch((linkmem->linkcmd[0])>>8){
		case 'M':
			tspeed = (linkmem->linkcmd[0]) & 3;
			transfer = 1;
			WRITE32LE(&ioMem[0x120], 0xffffffff);
			WRITE32LE(&ioMem[0x124], 0xffffffff);
			UPDATE_REG(0x128, READ16LE(&ioMem[0x128]) | 0x80);
			break;
		}
	}

	if(!transfer) return;

	if(transfer&&linktime>=trtimedata[transfer-1][tspeed]&&transfer<=linkmem->numgbas){
		if(transfer-linkid==2){
			SetEvent(linksync[linkid+1]);
			if(WaitForSingleObject(linksync[linkid], linktimeout)==WAIT_TIMEOUT)
				linkmem->numtransfers=0;
			ResetEvent(linksync[linkid]);
			if(linklog)	fprintf(linklogfile, "%04x %04x %04x %04x %10u\n",
				linkmem->linkdata[0], linkmem->linkdata[1], linkmem->linkdata[2], linkmem->linkdata[3], linkmem->lastlinktime);
		}


		UPDATE_REG(0x11e + (transfer<<1), linkmem->linkdata[transfer-1]);
		transfer++;
	}

	if(transfer&&linktime>=trtimeend[linkmem->numgbas-2][tspeed]){
		if(linkid==linkmem->numgbas-1){
			SetEvent(linksync[0]);
			if(WaitForSingleObject(linksync[linkid], linktimeout)==WAIT_TIMEOUT)
				linkmem->numtransfers=0;
			ResetEvent(linksync[linkid]);
			if(linklog)	fprintf(linklogfile, "%04x %04x %04x %04x %10u\n",
				linkmem->linkdata[0], linkmem->linkdata[1], linkmem->linkdata[2], linkmem->linkdata[3], linkmem->lastlinktime);
		}
		transfer = 0;
		linktime -= trtimeend[0][tspeed];
		if(READ16LE(&ioMem[0x128]) & 0x4000){
			IF |= 0x80;
			UPDATE_REG(0x202, IF);
		}
		UPDATE_REG(0x128, (READ16LE(&ioMem[0x128]) & 0xff0f) | (linkid << 4));
		linkmem->linkdata[linkid] = 0xffff;
	}

	return;
}

inline int GetSioMode(u16 reg1, u16 reg2){
	if(!(reg2&0x8000)){
		switch(reg1&0x3000){
		case 0x0000:
			return NORMAL8;
		case 0x1000:
			return NORMAL32;
		case 0x2000:
			return MULTIPLAYER;
		case 0x3000:
			return UART;
		}
	}
	if(reg2&0x4000) return JOYBUS;
	return GP;
}

u16 StartRFU(u16 value){
	switch(GetSioMode(value, READ16LE(&ioMem[0x134]))){
	case NORMAL8:
		rfu_polarity = 0;
		return value;
		break;
	case NORMAL32:
		if(value&8)	value &= 0xfffb;	// A kind of acknowledge procedure
		else value |= 4;
		if(value&0x80){
			if((value&3)==1) transferend = 2048;
			else transferend = 256;
			u16 a = READ16LE(&ioMem[0x122]);
			switch(rfu_state){
			case RFU_INIT:
				if(READ32LE(&ioMem[0x120])==0xb0bb8001){
					rfu_state = RFU_COMM;	// end of startup
				}
				UPDATE_REG(0x122, READ16LE(&ioMem[0x120]));
				UPDATE_REG(0x120, a);
				break;
			case RFU_COMM:
				if(a==0x9966){
					rfu_cmd = ioMem[0x120];
					if((rfu_qsend=ioMem[0x121])!=0){
						rfu_state = RFU_SEND;
						counter = 0;
					}
					if(rfu_cmd==0x25||rfu_cmd==0x24){
						linkmem->rfu_q[vbaid] = rfu_qsend;
					}
					UPDATE_REG(0x120, 0);
					UPDATE_REG(0x122, 0x8000);
				} else if(a==0x8000){
					switch(rfu_cmd){
					case 0x1a:	// check if someone joined
						if(linkmem->rfu_request[vbaid]!=0){
							rfu_state = RFU_RECV;
							rfu_qrecv = 1;
						}
						linkid = -1;
						rfu_cmd |= 0x80;
						break;
					case 0x1e:	// receive broadcast data
					case 0x1d:	// no visible difference
						rfu_polarity = 0;
						rfu_state = RFU_RECV;
						rfu_qrecv = 7;
						counter = 0;
						rfu_cmd |= 0x80;
						break;
					case 0x30:
						linkmem->rfu_request[vbaid] = 0;
						linkmem->rfu_q[vbaid] = 0;
						linkid = 0;
						numtransfers = 0;
						rfu_cmd |= 0x80;
						if(linkmem->numgbas==2) SetEvent(linksync[1-vbaid]);
						break;
					case 0x11:	// ? always receives 0xff - I suspect it's something for 3+ players
					case 0x13:	// unknown
					case 0x20:	// this has something to do with 0x1f
					case 0x21:	// this too
						rfu_cmd |= 0x80;
						rfu_polarity = 0;
						rfu_state = 3;
						rfu_qrecv = 1;
						break;
					case 0x26:
						if(linkid>0){
							rfu_qrecv = rfu_masterq;
						}
						if((rfu_qrecv=linkmem->rfu_q[1-vbaid])!=0){
							rfu_state = RFU_RECV;
							counter = 0;
						}
						rfu_cmd |= 0x80;
						break;
					case 0x24:	// send data
						if((numtransfers++)==0) linktime = 1;
						linkmem->rfu_linktime[vbaid] = linktime;
						if(linkmem->numgbas==2){
							SetEvent(linksync[1-vbaid]);
							WaitForSingleObject(linksync[vbaid], linktimeout);
							ResetEvent(linksync[vbaid]);
						}
						rfu_cmd |= 0x80;
						linktime = 0;
						linkid = -1;
						break;
					case 0x25:	// send & wait for data
					case 0x1f:	// pick a server
					case 0x10:	// init
					case 0x16:	// send broadcast data
					case 0x17:	// setup or something ?
					case 0x27:	// wait for data ?
					case 0x3d:	// init
					default:
						rfu_cmd |= 0x80;
						break;
					case 0xa5:	//	2nd part of send&wait function 0x25
					case 0xa7:	//	2nd part of wait function 0x27
						if(linkid==-1){
							linkid++;
							linkmem->rfu_linktime[vbaid] = 0;
						}
						if(linkid&&linkmem->rfu_request[1-vbaid]==0){
							linkmem->rfu_q[1-vbaid] = 0;
							transferend = 256;
							rfu_polarity = 1;
							rfu_cmd = 0x29;
							linktime = 0;
							break;
						}
						if((numtransfers++)==0) linktime = 0;
						linkmem->rfu_linktime[vbaid] = linktime;
						if(linkmem->numgbas==2){
							if(!linkid||(linkid&&numtransfers)) SetEvent(linksync[1-vbaid]);
							WaitForSingleObject(linksync[vbaid], linktimeout);
							ResetEvent(linksync[vbaid]);
						}
						if(linkid>0){
							memcpy(rfu_masterdata, linkmem->rfu_data[1-vbaid], 128);
							rfu_masterq = linkmem->rfu_q[1-vbaid];
						}
						transferend = linkmem->rfu_linktime[1-vbaid] - linktime + 256;
						if(transferend<256) transferend = 256;
						linktime = -transferend;
						rfu_polarity = 1;
						rfu_cmd = 0x28;
						break;
					}
					UPDATE_REG(0x122, 0x9966);
					UPDATE_REG(0x120, (rfu_qrecv<<8) | rfu_cmd);
				} else {
					UPDATE_REG(0x120, 0);
					UPDATE_REG(0x122, 0x8000);
				}
				break;
			case RFU_SEND:
				if(--rfu_qsend==0) rfu_state = RFU_COMM;
				switch(rfu_cmd){
				case 0x16:
					linkmem->rfu_bdata[vbaid][counter++] = READ32LE(&ioMem[0x120]);
					break;
				case 0x17:
					linkid = 1;
					break;
				case 0x1f:
					linkmem->rfu_request[1-vbaid] = 1;
					break;
				case 0x24:
				case 0x25:
					linkmem->rfu_data[vbaid][counter++] = READ32LE(&ioMem[0x120]);
					break;
				}
				UPDATE_REG(0x120, 0);
				UPDATE_REG(0x122, 0x8000);
				break;
			case RFU_RECV:
				if(--rfu_qrecv==0) rfu_state = RFU_COMM;
				switch(rfu_cmd){
				case 0x9d:
				case 0x9e:
					if(counter==0){
						UPDATE_REG(0x120, 0x61f1);
						UPDATE_REG(0x122, 0);
						counter++;
						break;
					}
					UPDATE_REG(0x120, linkmem->rfu_bdata[1-vbaid][counter-1]&0xffff);
					UPDATE_REG(0x122, linkmem->rfu_bdata[1-vbaid][counter-1]>>16);
					counter++;
					break;
				case 0xa6:
					if(linkid>0){
						UPDATE_REG(0x120, rfu_masterdata[counter]&0xffff);
						UPDATE_REG(0x122, rfu_masterdata[counter++]>>16);
					} else {
						UPDATE_REG(0x120, linkmem->rfu_data[1-vbaid][counter]&0xffff);
						UPDATE_REG(0x122, linkmem->rfu_data[1-vbaid][counter++]>>16);
					}
					break;
				case 0x93:	// it seems like the game doesn't care about this value
					UPDATE_REG(0x120, 0x1234);	// put anything in here
					UPDATE_REG(0x122, 0x0200);	// also here, but it should be 0200
					break;
				case 0xa0:
				case 0xa1:
					UPDATE_REG(0x120, 0x641b);
					UPDATE_REG(0x122, 0x0000);
					break;
				case 0x9a:
					UPDATE_REG(0x120, 0x61f9);
					UPDATE_REG(0x122, 0);
					break;
				case 0x91:
					UPDATE_REG(0x120, 0x00ff);
					UPDATE_REG(0x122, 0x0000);
					break;
				default:
					UPDATE_REG(0x120, 0x0173);
					UPDATE_REG(0x122, 0x0000);
					break;
				}
				break;
			}
			transfer = 1;
		}
		if(rfu_polarity) value ^= 4;	// sometimes it's the other way around
	default:
		return value;
	}
}

void gbLinkStart(u8 value){
// Not in this version :-)
}


void gbLinkUpdate(void){
}

int InitLink(void){
	WSADATA wsadata;
	BOOL disable = true;

	linkid = 0;

	if(WSAStartup(MAKEWORD(1,1), &wsadata)!=0){
		WSACleanup();
		return 0;
	}

	if((lanlink.tcpsocket=socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))==INVALID_SOCKET){
		MessageBox(NULL, "Couldn't create socket.", "Error!", MB_OK);
		WSACleanup();
		return 0;
	}

	setsockopt(lanlink.tcpsocket, IPPROTO_TCP, TCP_NODELAY, (char*)&disable, sizeof(BOOL));

	if((mmf=CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(LINKDATA), "VBA link memory"))==NULL){
		closesocket(lanlink.tcpsocket);
		WSACleanup();
		MessageBox(NULL, "Error creating file mapping", "Error", MB_OK|MB_ICONEXCLAMATION);
		return 0;
	}

	if(GetLastError() == ERROR_ALREADY_EXISTS)
		vbaid = 1;
	else
 		vbaid = 0;

	if((linkmem=(LINKDATA *)MapViewOfFile(mmf, FILE_MAP_WRITE, 0, 0, sizeof(LINKDATA)))==NULL){
		closesocket(lanlink.tcpsocket);
		WSACleanup();
		CloseHandle(mmf);
		MessageBox(NULL, "Error mapping file", "Error", MB_OK|MB_ICONEXCLAMATION);
		return 0;
	}

	if(linkmem->linkflags&LINK_PARENTLOST)
		vbaid = 0;

	if(vbaid==0){
		linkid = 0;
		if(linkmem->linkflags&LINK_PARENTLOST){
			linkmem->numgbas++;
			linkmem->linkflags &= ~LINK_PARENTLOST;
		}
		else
			linkmem->numgbas=1;

		for(i=0;i<4;i++){
			linkevent[15]=(char)i+'1';
			if((linksync[i]=CreateEvent(NULL, true, false, linkevent))==NULL){
				closesocket(lanlink.tcpsocket);
				WSACleanup();
				UnmapViewOfFile(linkmem);
				CloseHandle(mmf);
				for(j=0;j<i;j++){
					CloseHandle(linksync[j]);
				}
				MessageBox(NULL, "Error opening event", "Error", MB_OK|MB_ICONEXCLAMATION);
				return 0;
			}
		}
	} else {
		vbaid=linkmem->numgbas;
		linkid = vbaid;
		linkmem->numgbas++;

		linklog = 0;
		if(linkmem->numgbas>4){
			linkmem->numgbas=4;
			closesocket(lanlink.tcpsocket);
			WSACleanup();
			MessageBox(NULL, "5 or more GBAs not supported.", "Error!", MB_OK|MB_ICONEXCLAMATION);
			UnmapViewOfFile(linkmem);
			CloseHandle(mmf);
			return 0;
		}
		for(i=0;i<4;i++){
			linkevent[15]=(char)i+'1';
			if((linksync[i]=OpenEvent(EVENT_ALL_ACCESS, false, linkevent))==NULL){
				closesocket(lanlink.tcpsocket);
				WSACleanup();
				CloseHandle(mmf);
				UnmapViewOfFile(linkmem);
				for(j=0;j<i;j++){
					CloseHandle(linksync[j]);
				}
				MessageBox(NULL, "Error opening event", "Error", MB_OK|MB_ICONEXCLAMATION);
				return 0;
			}
		}
	}

	linkmem->lastlinktime=0xffffffff;
	linkmem->numtransfers=0;
	linkmem->linkflags=0;
	lanlink.connected = false;
	lanlink.thread = NULL;
	lanlink.speed = false;
	for(i=0;i<4;i++){
		linkmem->linkdata[i] = 0xffff;
		linkdata[i] = 0xffff;
	}
return 1;
}

int openLinkLog(void){
	char filename[20];
	if(linklog){
		sprintf(filename, "vbalog%1d.txt", vbaid+1);
		if((linklogfile=fopen(filename, "at"))==NULL){
			linklog=false;
			return 0;
		}
		fprintf(linklogfile, "----- Log opened -----\n");
	}
	return 1;
}

void closeLinkLog()
{
	if(linklogfile)
	{
		fclose(linklogfile);
		linklogfile=NULL;
	}
}

void CloseLink(void){
	if(lanlink.connected){
		if(linkid){
			char outbuffer[4];
			outbuffer[0] = 4;
			outbuffer[1] = -32;
			if(lanlink.type==0) send(lanlink.tcpsocket, outbuffer, 4, 0);
		} else {
			char outbuffer[12];
			int i;
			outbuffer[0] = 12;
			outbuffer[1] = -32;
			for(i=1;i<=lanlink.numgbas;i++){
				if(lanlink.type==0){
					send(ls.tcpsocket[i], outbuffer, 12, 0);
				}
				closesocket(ls.tcpsocket[i]);
			}
		}
	}
	linkmem->numgbas--;
	if(!linkid&&linkmem->numgbas!=0)
		linkmem->linkflags|=LINK_PARENTLOST;
	CloseHandle(mmf);
	UnmapViewOfFile(linkmem);

	for(i=0;i<4;i++){
		if(linksync[i]!=NULL){
			PulseEvent(linksync[i]);
			CloseHandle(linksync[i]);
		}
	}
	regSetDwordValue("LAN", lanlink.active);
	if(linklog) closeLinkLog();
	closesocket(lanlink.tcpsocket);
	WSACleanup();
return;
}

lserver::lserver(void){
	intinbuffer = (int*)inbuffer;
	u16inbuffer = (u16*)inbuffer;
	intoutbuffer = (int*)outbuffer;
	u16outbuffer = (u16*)outbuffer;
	oncewait = false;
}

int lserver::Init(void *serverdlg){
	SOCKADDR_IN info;
	DWORD nothing;
	char str[100];

	info.sin_family = AF_INET;
	info.sin_addr.S_un.S_addr = INADDR_ANY;
	info.sin_port = htons(5738);

	if(bind(lanlink.tcpsocket, (LPSOCKADDR)&info, sizeof(SOCKADDR_IN))==SOCKET_ERROR){
		closesocket(lanlink.tcpsocket);
		if((lanlink.tcpsocket=socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))==INVALID_SOCKET)
			return WSAGetLastError();
		if(bind(lanlink.tcpsocket, (LPSOCKADDR)&info, sizeof(SOCKADDR_IN))==SOCKET_ERROR)
			return WSAGetLastError();
	}

	if(listen(lanlink.tcpsocket, lanlink.numgbas)==SOCKET_ERROR)
		return WSAGetLastError();

	if(lanlink.thread!=NULL){
		lanlink.terminate = true;
		WaitForSingleObject(linksync[vbaid], 500);
		lanlink.thread = NULL;
	}
	lanlink.terminate = false;
	linkid = 0;

	gethostname(str, 100);
	((ServerWait*)serverdlg)->m_serveraddress.Format("Server IP address is: %s", inet_ntoa(*(LPIN_ADDR)(gethostbyname(str)->h_addr_list[0])));

	lanlink.thread = CreateThread(NULL, 0, LinkServerThread, serverdlg, 0, &nothing);

	return 0;

}

DWORD WINAPI LinkServerThread(void *serverdlg){
	fd_set fdset;
	timeval wsocktimeout;
	char inbuffer[256], outbuffer[256];
	int *intinbuffer = (int*)inbuffer;
	u16 *u16inbuffer = (u16*)inbuffer;
	int *intoutbuffer = (int*)outbuffer;
	u16 *u16outbuffer = (u16*)outbuffer;
	BOOL disable = true;

	wsocktimeout.tv_sec = 1;
	wsocktimeout.tv_usec = 0;
	i = 0;

	while(i<lanlink.numgbas){
		fdset.fd_count = 1;
		fdset.fd_array[0] = lanlink.tcpsocket;
		if(select(0, &fdset, NULL, NULL, &wsocktimeout)==1){
			if(lanlink.terminate){
				SetEvent(linksync[vbaid]);
				return 0;
			}
			if((ls.tcpsocket[i+1]=accept(lanlink.tcpsocket, NULL, NULL))==INVALID_SOCKET){
				for(int j=1;j<i;j++) closesocket(ls.tcpsocket[j]);
				MessageBox(NULL, "Network error.", "Error", MB_OK);
				return 1;
			} else {
				setsockopt(ls.tcpsocket[i+1], IPPROTO_TCP, TCP_NODELAY, (char*)&disable, sizeof(BOOL));
				u16outbuffer[0] = i+1;
				u16outbuffer[1] = lanlink.numgbas;
				send(ls.tcpsocket[i+1], outbuffer, 4, 0);
				((ServerWait*)serverdlg)->m_plconn[i].Format("Player %d connected", i+1);
				((ServerWait*)serverdlg)->UpdateData(false);
				i++;
			}
		}
		((ServerWait*)serverdlg)->m_prgctrl.StepIt();
	}
	MessageBox(NULL, "All players connected", "Link", MB_OK);
	((ServerWait*)serverdlg)->SendMessage(WM_CLOSE, 0, 0);

	for(i=1;i<=lanlink.numgbas;i++){
		outbuffer[0] = 4;
		send(ls.tcpsocket[i], outbuffer, 4, 0);
	}

	lanlink.connected = true;

	return 0;
}

void lserver::Send(void){
	if(lanlink.type==0){	// TCP
		if(savedlinktime==-1){
			outbuffer[0] = 4;
			outbuffer[1] = -32;	//0xe0
			for(i=1;i<=lanlink.numgbas;i++){
				send(tcpsocket[i], outbuffer, 4, 0);
				recv(tcpsocket[i], inbuffer, 4, 0);
			}
		}
		outbuffer[1] = tspeed;
		u16outbuffer[1] = linkdata[0];
		intoutbuffer[1] = savedlinktime;
		if(lanlink.numgbas==1){
			if(lanlink.type==0){
				outbuffer[0] = 8;
				send(tcpsocket[1], outbuffer, 8, 0);
			}
		}
		else if(lanlink.numgbas==2){
			u16outbuffer[4] = linkdata[2];
			if(lanlink.type==0){
				outbuffer[0] = 10;
				send(tcpsocket[1], outbuffer, 10, 0);
				u16outbuffer[4] = linkdata[1];
				send(tcpsocket[2], outbuffer, 10, 0);
			}
		} else {
			if(lanlink.type==0){
				outbuffer[0] = 12;
				u16outbuffer[4] = linkdata[2];
				u16outbuffer[5] = linkdata[3];
				send(tcpsocket[1], outbuffer, 12, 0);
				u16outbuffer[4] = linkdata[1];
				send(tcpsocket[2], outbuffer, 12, 0);
				u16outbuffer[5] = linkdata[2];
				send(tcpsocket[3], outbuffer, 12, 0);
			}
		}
	}
	return;
}

void lserver::Recv(void){
	int numbytes;
	if(lanlink.type==0){	// TCP
		wsocktimeout.tv_usec =  0;
		wsocktimeout.tv_sec = linktimeout / 1000;
		fdset.fd_count = lanlink.numgbas;
		for(i=0;i<lanlink.numgbas;i++) fdset.fd_array[i] = tcpsocket[i+1];
		if(select(0, &fdset, NULL, NULL, &wsocktimeout)==0){
			return;
		}
		howmanytimes++;
		for(i=0;i<lanlink.numgbas;i++){
			numbytes = 0;
			inbuffer[0] = 1;
			while(numbytes<howmanytimes*inbuffer[0])
				numbytes += recv(tcpsocket[i+1], inbuffer+numbytes, 256-numbytes, 0);
			if(howmanytimes>1) memcpy(inbuffer, inbuffer+inbuffer[0]*(howmanytimes-1), inbuffer[0]);
			if(inbuffer[1]==-32){
				char message[30];
				lanlink.connected = false;
				sprintf(message, "Player %d disconnected.", i+2);
				MessageBox(NULL, message, "Link", MB_OK);
				outbuffer[0] = 4;
				outbuffer[1] = -32;
				for(i=1;i<lanlink.numgbas;i++){
					send(tcpsocket[i], outbuffer, 12, 0);
					recv(tcpsocket[i], inbuffer, 256, 0);
					closesocket(tcpsocket[i]);
				}
				return;
			}
			linkdata[i+1] = u16inbuffer[1];
		}
		howmanytimes = 0;
	}
	after = false;
	return;
}

lclient::lclient(void){
	intinbuffer = (int*)inbuffer;
	u16inbuffer = (u16*)inbuffer;
	intoutbuffer = (int*)outbuffer;
	u16outbuffer = (u16*)outbuffer;
	numtransfers = 0;
	oncesend = false;
	return;
}

int lclient::Init(LPHOSTENT hostentry, void *waitdlg){
	unsigned long notblock = 1;
	DWORD nothing;

	serverinfo.sin_family = AF_INET;
	serverinfo.sin_port = htons(5738);
	serverinfo.sin_addr = *((LPIN_ADDR)*hostentry->h_addr_list);

	if(ioctlsocket(lanlink.tcpsocket, FIONBIO, &notblock)==SOCKET_ERROR)
		return WSAGetLastError();

	if(lanlink.thread!=NULL){
		lanlink.terminate = true;
		WaitForSingleObject(linksync[vbaid], 500);
		lanlink.thread = NULL;
	}

	((ServerWait*)waitdlg)->SetWindowText("Connecting...");
	lanlink.terminate = false;
	lanlink.thread = CreateThread(NULL, 0, LinkClientThread, waitdlg, 0, &nothing);
	return 0;
}

DWORD WINAPI LinkClientThread(void *waitdlg){
	fd_set fdset;
	timeval wsocktimeout;
	int numbytes;
	char inbuffer[16];
	u16 *u16inbuffer = (u16*)inbuffer;
	unsigned long block = 0;

	if(connect(lanlink.tcpsocket, (LPSOCKADDR)&lc.serverinfo, sizeof(SOCKADDR_IN))==SOCKET_ERROR){
		if(WSAGetLastError()!=WSAEWOULDBLOCK){
			MessageBox(NULL, "Couldn't connect to server.", "Link", MB_OK);
			return 1;
		}
		wsocktimeout.tv_sec = 1;
		wsocktimeout.tv_usec = 0;
		do{
			if(lanlink.terminate) return 0;
			fdset.fd_count = 1;
			fdset.fd_array[0] = lanlink.tcpsocket;
			((ServerWait*)waitdlg)->m_prgctrl.StepIt();
		} while(select(0, NULL, &fdset, NULL, &wsocktimeout)!=1&&connect(lanlink.tcpsocket, (LPSOCKADDR)&lc.serverinfo, sizeof(SOCKADDR_IN))!=0);
	}

	ioctlsocket(lanlink.tcpsocket, FIONBIO, &block);

	numbytes = 0;
	while(numbytes<4)
		numbytes += recv(lanlink.tcpsocket, inbuffer+numbytes, 16, 0);
	linkid = (int)u16inbuffer[0];
	lanlink.numgbas = (int)u16inbuffer[1];

	((ServerWait*)waitdlg)->m_serveraddress.Format("Connected as #%d", linkid+1);
	if(lanlink.numgbas!=linkid)	((ServerWait*)waitdlg)->m_plconn[0].Format("Waiting for %d players to join", lanlink.numgbas-linkid);
	else ((ServerWait*)waitdlg)->m_plconn[0].Format("All players joined.");

	numbytes = 0;
	inbuffer[0] = 1;
	while(numbytes<inbuffer[0])
		numbytes += recv(lanlink.tcpsocket, inbuffer+numbytes, 16, 0);

	MessageBox(NULL, "Connected.", "Link", MB_OK);
	((ServerWait*)waitdlg)->SendMessage(WM_CLOSE, 0, 0);

	block = 1;

	ioctlsocket(lanlink.tcpsocket, FIONBIO, &block);

	lanlink.connected = true;
	return 0;
}

void lclient::CheckConn(void){
	if((numbytes=recv(lanlink.tcpsocket, inbuffer, 256, 0))>0){
		while(numbytes<inbuffer[0])
			numbytes += recv(lanlink.tcpsocket, inbuffer+numbytes, 256, 0);
		if(inbuffer[1]==-32){
				outbuffer[0] = 4;
				send(lanlink.tcpsocket, outbuffer, 4, 0);
				lanlink.connected = false;
				MessageBox(NULL, "Server disconnected.", "Link", MB_OK);
				return;
		}
		numtransfers = 1;
		savedlinktime = 0;
		linkdata[0] = u16inbuffer[1];
		tspeed = inbuffer[1] & 3;
		for(i=1, numbytes=4;i<=lanlink.numgbas;i++)
			if(i!=linkid) linkdata[i] = u16inbuffer[numbytes++];
		after = false;
		oncewait = true;
		oncesend = true;
	}
	return;
}


void lclient::Recv(void){
	fdset.fd_count = 1;
	fdset.fd_array[0] = lanlink.tcpsocket;
	wsocktimeout.tv_sec = linktimeout / 1000;
	wsocktimeout.tv_usec = 0;
	if(select(0, &fdset, NULL, NULL, &wsocktimeout)==0){
		numtransfers = 0;
		return;
	}
	numbytes = 0;
	inbuffer[0] = 1;
	while(numbytes<inbuffer[0])
		numbytes += recv(lanlink.tcpsocket, inbuffer+numbytes, 256, 0);
	if(inbuffer[1]==-32){
		outbuffer[0] = 4;
		send(lanlink.tcpsocket, outbuffer, 4, 0);
		lanlink.connected = false;
		MessageBox(NULL, "Server disconnected.", "Link", MB_OK);
		return;
	}
	tspeed = inbuffer[1] & 3;
	linkdata[0] = u16inbuffer[1];
	savedlinktime = intinbuffer[1];
	for(i=1, numbytes=4;i<lanlink.numgbas+1;i++)
		if(i!=linkid) linkdata[i] = u16inbuffer[numbytes++];
	numtransfers++;
	if(numtransfers==0) numtransfers = 2;
	after = false;
}

void lclient::Send(){
	outbuffer[0] = 4;
	outbuffer[1] = linkid<<2;
	u16outbuffer[1] = linkdata[linkid];
	send(lanlink.tcpsocket, outbuffer, 4, 0);
	return;
}

void LinkSStop(void){
	if(!oncewait){
		if(linkid){
			if(lanlink.numgbas==1) return;
			lc.Recv();
		}
		else ls.Recv();

		oncewait = true;
		UPDATE_REG(0x122, linkdata[1]);
		UPDATE_REG(0x124, linkdata[2]);
		UPDATE_REG(0x126, linkdata[3]);
		if(linklog) fprintf(linklogfile, "%04x %04x %04x %04x %10u\n", linkdata[0], linkdata[1], linkdata[2], linkdata[3], savedlinktime);
	}
	return;
}

void LinkSSend(u16 value){
	if(linkid&&!lc.oncesend){
		linkdata[linkid] = value;
		lc.Send();
		lc.oncesend = true;
	}
}
