1084 lines
28 KiB
C++
1084 lines
28 KiB
C++
// 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 "Link.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, ¬hing);
|
|
|
|
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, ¬block)==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, ¬hing);
|
|
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;
|
|
}
|
|
}
|