/* | |
Copyright (C) 1996-1997 Id Software, Inc. | |
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 | |
of the License, 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. | |
*/ | |
// net_ipx.c | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <dpmi.h> | |
#include "quakedef.h" | |
#include "dosisms.h" | |
#include "net_ipx.h" | |
#define EIO 5 /* I/O error */ | |
#define AF_NETWARE 64 | |
#define IPX_OPEN 0 | |
#define IPX_CLOSE 1 | |
#define IPX_GETROUTE 2 | |
#define IPX_SEND 3 | |
#define IPX_LISTEN 4 | |
#define IPX_SCHEDULEEVENT 5 | |
#define IPX_CANCEL 6 | |
#define IPX_SCHEDULESPECIALEVENT 7 | |
#define IPX_GETINTERVALMARKER 8 | |
#define IPX_GETADDRESS 9 | |
#define IPX_RELINQUISH 10 | |
#define PTYPE_UNKNOWN 0 | |
#define PTYPE_RIP 1 | |
#define PTYPE_ECHO 2 | |
#define PTYPE_ERROR 3 | |
#define PTYPE_IPX 4 | |
#define PTYPE_SPX 5 | |
#pragma pack(1) | |
typedef struct | |
{ | |
byte network[4]; | |
byte node[6]; | |
short socket; | |
} IPXaddr; | |
struct sockaddr_ipx | |
{ | |
short sipx_family; | |
IPXaddr sipx_addr; | |
char sipx_zero[2]; | |
}; | |
#define sipx_port sipx_addr.socket | |
typedef struct | |
{ | |
short checkSum; | |
short length; | |
byte transportControl; | |
byte type; | |
IPXaddr destination; | |
IPXaddr source; | |
} IPXheader; | |
typedef struct ECBStructure | |
{ | |
struct ECBStructure *link; | |
unsigned short ESR_off; | |
unsigned short ESR_seg; | |
byte inUse; | |
byte completionCode; | |
short socket; | |
byte IPXWorkspace[4]; | |
byte driverWorkspace[12]; | |
byte immediateAddress[6]; | |
short fragCount; | |
short fragOff; | |
short fragSeg; | |
short fragSize; | |
} ECB; | |
#pragma pack() | |
typedef struct | |
{ | |
ECB ecb; | |
IPXheader header; | |
int sequence; | |
char data[NET_DATAGRAMSIZE]; | |
} ipx_lowmem_buffer_t; | |
#define LOWMEMSIZE (100 * 1024) | |
#define LOWMEMSAVE 256 | |
#define IPXBUFFERS ((LOWMEMSIZE - LOWMEMSAVE)/ sizeof(ipx_lowmem_buffer_t)) | |
#define IPXSOCKBUFFERS 5 | |
#define IPXSOCKETS (IPXBUFFERS / IPXSOCKBUFFERS) | |
// each socket's socketbuffer 0 is used for sending, the others for listening | |
typedef struct | |
{ | |
char reserved[LOWMEMSAVE]; | |
ipx_lowmem_buffer_t socketbuffer[IPXSOCKETS][IPXSOCKBUFFERS]; | |
} ipx_lowmem_area_t; | |
static int ipxsocket[IPXSOCKETS]; | |
static ECB *readlist[IPXSOCKETS]; | |
static int sequence[IPXSOCKETS]; | |
static int handlesInUse; | |
static ipx_lowmem_area_t *lma; | |
static char *lowmem_buffer; | |
static int lowmem_bufseg; | |
static int lowmem_bufoff; | |
static unsigned short ipx_cs; | |
static unsigned short ipx_ip; | |
static int net_acceptsocket = -1; | |
static int net_controlsocket; | |
static void IPX_PollProcedure(void); | |
static PollProcedure pollProcedure = {NULL, 0.0, IPX_PollProcedure}; | |
//============================================================================= | |
static void IPX_GetLocalAddress(IPXaddr *addr) | |
{ | |
regs.x.cs = ipx_cs; | |
regs.x.ip = ipx_ip; | |
regs.x.bx = IPX_GETADDRESS; | |
regs.x.es = lowmem_bufseg; | |
regs.x.si = lowmem_bufoff; | |
__dpmi_simulate_real_mode_procedure_retf((__dpmi_regs *)®s); | |
Q_memcpy(addr, lowmem_buffer, 10); | |
} | |
//============================================================================= | |
static int IPX_GetLocalTarget(IPXaddr *addr, byte *localTarget) | |
{ | |
regs.x.cs = ipx_cs; | |
regs.x.ip = ipx_ip; | |
regs.x.bx = IPX_GETROUTE; | |
regs.x.es = lowmem_bufseg; | |
regs.x.si = lowmem_bufoff; | |
regs.x.di = lowmem_bufoff + sizeof(IPXaddr); | |
Q_memcpy(lowmem_buffer, addr, sizeof(IPXaddr)); | |
__dpmi_simulate_real_mode_procedure_retf((__dpmi_regs *)®s); | |
if (regs.h.al) | |
return -1; | |
Q_memcpy(localTarget, lowmem_buffer + sizeof(IPXaddr), 6); | |
return 0; | |
} | |
//============================================================================= | |
static void IPX_ListenForPacket(ECB *ecb) | |
{ | |
regs.x.cs = ipx_cs; | |
regs.x.ip = ipx_ip; | |
regs.x.bx = IPX_LISTEN; | |
regs.x.es = ptr2real(ecb) >> 4; | |
regs.x.si = ptr2real(ecb) & 0xf; | |
__dpmi_simulate_real_mode_procedure_retf((__dpmi_regs *)®s); | |
} | |
//============================================================================= | |
static void IPX_RelinquishControl(void) | |
{ | |
regs.x.cs = ipx_cs; | |
regs.x.ip = ipx_ip; | |
regs.x.bx = IPX_RELINQUISH; | |
__dpmi_simulate_real_mode_procedure_retf((__dpmi_regs *)®s); | |
} | |
void IPX_PollProcedure(void) | |
{ | |
IPX_RelinquishControl(); | |
SchedulePollProcedure(&pollProcedure, 0.01); | |
} | |
//============================================================================= | |
static void ProcessReadyList(int s) | |
{ | |
int n; | |
ECB *ecb; | |
ECB *prev; | |
for (n = 1; n < IPXSOCKBUFFERS; n++) | |
{ | |
if (lma->socketbuffer[s][n].ecb.inUse == 0) | |
{ | |
for (ecb = readlist[s], prev = NULL; ecb; ecb = ecb->link) | |
{ | |
if (lma->socketbuffer[s][n].sequence < ((ipx_lowmem_buffer_t *) ecb)->sequence) | |
break; | |
prev = ecb; | |
} | |
if (ecb) | |
lma->socketbuffer[s][n].ecb.link = ecb; | |
else | |
lma->socketbuffer[s][n].ecb.link = NULL; | |
if (prev) | |
prev->link = &lma->socketbuffer[s][n].ecb; | |
else | |
readlist[s] = &lma->socketbuffer[s][n].ecb; | |
lma->socketbuffer[s][n].ecb.inUse = 0xff; | |
} | |
} | |
} | |
//============================================================================= | |
int IPX_Init(void) | |
{ | |
int s; | |
int n; | |
struct qsockaddr addr; | |
char *colon; | |
if (COM_CheckParm ("-noipx")) | |
return -1; | |
// find the IPX far call entry point | |
regs.x.ax = 0x7a00; | |
__dpmi_simulate_real_mode_interrupt (0x2f, (__dpmi_regs *)®s); | |
if (regs.h.al != 0xff) | |
{ | |
Con_Printf("IPX not detected\n"); | |
return -1; | |
} | |
ipx_cs = regs.x.es; | |
ipx_ip = regs.x.di; | |
// grab a chunk of memory down in DOS land | |
lowmem_buffer = dos_getmemory(LOWMEMSIZE); | |
if (!lowmem_buffer) | |
{ | |
Con_Printf("IPX_Init: Not enough low memory\n"); | |
return -1; | |
} | |
lowmem_bufoff = ptr2real(lowmem_buffer) & 0xf; | |
lowmem_bufseg = ptr2real(lowmem_buffer) >> 4; | |
// init socket handles & buffers | |
handlesInUse = 0; | |
lma = (ipx_lowmem_area_t *)lowmem_buffer; | |
for (s = 0; s < IPXSOCKETS; s++) | |
{ | |
ipxsocket[s] = 0; | |
for (n = 0; n < IPXSOCKBUFFERS; n++) | |
{ | |
lma->socketbuffer[s][n].ecb.link = NULL; | |
lma->socketbuffer[s][n].ecb.ESR_off = 0; | |
lma->socketbuffer[s][n].ecb.ESR_seg = 0; | |
lma->socketbuffer[s][n].ecb.socket = 0; | |
lma->socketbuffer[s][n].ecb.inUse = 0xff; | |
lma->socketbuffer[s][n].ecb.completionCode = 0; | |
lma->socketbuffer[s][n].ecb.fragCount = 1; | |
lma->socketbuffer[s][n].ecb.fragOff = ptr2real(&lma->socketbuffer[s][n].header) & 0xf; | |
lma->socketbuffer[s][n].ecb.fragSeg = ptr2real(&lma->socketbuffer[s][n].header) >> 4; | |
lma->socketbuffer[s][n].ecb.fragSize = sizeof(IPXheader) + sizeof(int) + NET_DATAGRAMSIZE; | |
} | |
} | |
if ((net_controlsocket = IPX_OpenSocket (0)) == -1) | |
{ | |
dos_freememory(lowmem_buffer); | |
Con_DPrintf ("IPX_Init: Unable to open control socket\n"); | |
return -1; | |
} | |
SchedulePollProcedure(&pollProcedure, 0.01); | |
IPX_GetSocketAddr (net_controlsocket, &addr); | |
Q_strcpy(my_ipx_address, IPX_AddrToString (&addr)); | |
colon = Q_strrchr (my_ipx_address, ':'); | |
if (colon) | |
*colon = 0; | |
Con_Printf("IPX initialized\n"); | |
ipxAvailable = true; | |
return net_controlsocket; | |
} | |
//============================================================================= | |
void IPX_Shutdown(void) | |
{ | |
IPX_Listen (false); | |
IPX_CloseSocket (net_controlsocket); | |
dos_freememory(lowmem_buffer); | |
} | |
//============================================================================= | |
void IPX_Listen (qboolean state) | |
{ | |
// enable listening | |
if (state) | |
{ | |
if (net_acceptsocket != -1) | |
return; | |
if ((net_acceptsocket = IPX_OpenSocket (net_hostport)) == -1) | |
Sys_Error ("IPX_Listen: Unable to open accept socket\n"); | |
return; | |
} | |
// disable listening | |
if (net_acceptsocket == -1) | |
return; | |
IPX_CloseSocket (net_acceptsocket); | |
net_acceptsocket = -1; | |
} | |
//============================================================================= | |
int IPX_OpenSocket(int port) | |
{ | |
int handle; | |
int n; | |
unsigned short socket; | |
if (handlesInUse == IPXSOCKETS) | |
return -1; | |
// open the IPX socket | |
regs.x.cs = ipx_cs; | |
regs.x.ip = ipx_ip; | |
regs.x.bx = IPX_OPEN; | |
regs.h.al = 0; | |
regs.x.dx = htons(port); | |
__dpmi_simulate_real_mode_procedure_retf((__dpmi_regs *)®s); | |
if (regs.h.al == 0xfe) | |
{ | |
Con_DPrintf("IPX_OpenSocket: all sockets in use\n"); | |
return -1; | |
} | |
if (regs.h.al == 0xff) | |
{ | |
Con_DPrintf("IPX_OpenSocket: socket already open\n"); | |
return -1; | |
} | |
if (regs.h.al != 0) | |
{ | |
Con_DPrintf("IPX_OpenSocket: error %02x\n", regs.h.al); | |
return -1; | |
} | |
socket = regs.x.dx; | |
// grab a handle; fill in the ECBs, and get them listening | |
for (handle = 0; handle < IPXSOCKETS; handle++) | |
{ | |
if (ipxsocket[handle] == 0) | |
{ | |
ipxsocket[handle] = socket; | |
readlist[handle] = NULL; | |
sequence[handle] = 0; | |
for (n = 0; n < IPXSOCKBUFFERS; n ++) | |
{ | |
lma->socketbuffer[handle][n].ecb.socket = socket; | |
lma->socketbuffer[handle][n].ecb.inUse = 0; | |
if (n) | |
IPX_ListenForPacket(&lma->socketbuffer[handle][n].ecb); | |
} | |
handlesInUse++; | |
return handle; | |
} | |
} | |
// "this will NEVER happen" | |
Sys_Error("IPX_OpenSocket: handle allocation failed\n"); | |
return -1; | |
} | |
//============================================================================= | |
int IPX_CloseSocket(int handle) | |
{ | |
// if there's a send in progress, give it one last chance | |
if (lma->socketbuffer[handle][0].ecb.inUse != 0) | |
IPX_RelinquishControl(); | |
// close the socket (all pending sends/received are cancelled) | |
regs.x.cs = ipx_cs; | |
regs.x.ip = ipx_ip; | |
regs.x.bx = IPX_CLOSE; | |
regs.x.dx = ipxsocket[handle]; | |
__dpmi_simulate_real_mode_procedure_retf((__dpmi_regs *)®s); | |
ipxsocket[handle] = 0; | |
handlesInUse--; | |
return 0; | |
} | |
//============================================================================= | |
int IPX_Connect (int handle, struct qsockaddr *addr) | |
{ | |
IPXaddr ipxaddr; | |
Q_memcpy(&ipxaddr, &((struct sockaddr_ipx *)addr)->sipx_addr, sizeof(IPXaddr)); | |
if (IPX_GetLocalTarget(&ipxaddr, lma->socketbuffer[handle][0].ecb.immediateAddress) != 0) | |
{ | |
Con_Printf("Get Local Target failed\n"); | |
return -1; | |
} | |
return 0; | |
} | |
//============================================================================= | |
int IPX_CheckNewConnections (void) | |
{ | |
int n; | |
if (net_acceptsocket == -1) | |
return -1; | |
for (n = 1; n < IPXSOCKBUFFERS; n ++) | |
if (lma->socketbuffer[net_acceptsocket][n].ecb.inUse == 0) | |
return net_acceptsocket; | |
return -1; | |
} | |
//============================================================================= | |
int IPX_Read (int handle, byte *buf, int len, struct qsockaddr *addr) | |
{ | |
ECB *ecb; | |
ipx_lowmem_buffer_t *rcvbuf; | |
int copylen; | |
ProcessReadyList(handle); | |
tryagain: | |
if (readlist[handle] == NULL) | |
return 0; | |
ecb = readlist[handle]; | |
readlist[handle] = ecb->link; | |
if (ecb->completionCode != 0) | |
{ | |
Con_Printf("Warning: IPX_Read error %02x\n", ecb->completionCode); | |
ecb->fragSize = sizeof(IPXheader) + sizeof(int) + NET_DATAGRAMSIZE; | |
IPX_ListenForPacket(ecb); | |
goto tryagain; | |
} | |
rcvbuf = (ipx_lowmem_buffer_t *)ecb; | |
// copy the data up to the buffer | |
copylen = ntohs(rcvbuf->header.length) - (sizeof(int) + sizeof(IPXheader)); | |
if (len < copylen) | |
Sys_Error("IPX_Read: buffer too small (%d vs %d)\n", len, copylen); | |
Q_memcpy(buf, rcvbuf->data, copylen); | |
// fill in the addr if they want it | |
if (addr) | |
{ | |
((struct sockaddr_ipx *)addr)->sipx_family = AF_NETWARE; | |
Q_memcpy(&((struct sockaddr_ipx *)addr)->sipx_addr, rcvbuf->header.source.network, sizeof(IPXaddr)); | |
((struct sockaddr_ipx *)addr)->sipx_zero[0] = 0; | |
((struct sockaddr_ipx *)addr)->sipx_zero[1] = 0; | |
} | |
// update the send ecb's immediate address | |
Q_memcpy(lma->socketbuffer[handle][0].ecb.immediateAddress, rcvbuf->ecb.immediateAddress, 6); | |
// get this ecb listening again | |
rcvbuf->ecb.fragSize = sizeof(IPXheader) + sizeof(int) + NET_DATAGRAMSIZE; | |
IPX_ListenForPacket(&rcvbuf->ecb); | |
return copylen; | |
} | |
//============================================================================= | |
int IPX_Broadcast (int handle, byte *buf, int len) | |
{ | |
struct sockaddr_ipx addr; | |
int ret; | |
Q_memset(addr.sipx_addr.network, 0x00, 4); | |
Q_memset(addr.sipx_addr.node, 0xff, 6); | |
addr.sipx_port = htons(net_hostport); | |
Q_memset(lma->socketbuffer[handle][0].ecb.immediateAddress, 0xff, 6); | |
ret = IPX_Write (handle, buf, len, (struct qsockaddr *)&addr); | |
return ret; | |
} | |
//============================================================================= | |
int IPX_Write (int handle, byte *buf, int len, struct qsockaddr *addr) | |
{ | |
// has the previous send completed? | |
while (lma->socketbuffer[handle][0].ecb.inUse != 0) | |
IPX_RelinquishControl(); | |
switch (lma->socketbuffer[handle][0].ecb.completionCode) | |
{ | |
case 0x00: // success | |
case 0xfc: // request cancelled | |
break; | |
case 0xfd: // malformed packet | |
default: | |
Con_Printf("IPX driver send failure: %02x\n", lma->socketbuffer[handle][0].ecb.completionCode); | |
break; | |
case 0xfe: // packet undeliverable | |
case 0xff: // unable to send packet | |
Con_Printf("IPX lost route, trying to re-establish\n"); | |
// look for a new route | |
if (IPX_GetLocalTarget (&lma->socketbuffer[handle][0].header.destination, lma->socketbuffer[handle][0].ecb.immediateAddress) != 0) | |
return -1; | |
// re-send the one that failed | |
regs.x.cs = ipx_cs; | |
regs.x.ip = ipx_ip; | |
regs.x.bx = IPX_SEND; | |
regs.x.es = ptr2real(&lma->socketbuffer[handle][0].ecb) >> 4; | |
regs.x.si = ptr2real(&lma->socketbuffer[handle][0].ecb) & 0xf; | |
__dpmi_simulate_real_mode_procedure_retf((__dpmi_regs *)®s); | |
// report that we did not send the current one | |
return 0; | |
} | |
// ecb : length | |
lma->socketbuffer[handle][0].ecb.fragSize = sizeof(IPXheader) + sizeof(int) + len; | |
// ipx header : type | |
lma->socketbuffer[handle][0].header.type = PTYPE_IPX; | |
// ipx header : destination | |
Q_memcpy(&lma->socketbuffer[handle][0].header.destination, &((struct sockaddr_ipx *)addr)->sipx_addr, sizeof(IPXaddr)); | |
// sequence number | |
lma->socketbuffer[handle][0].sequence = sequence[handle]; | |
sequence[handle]++; | |
// copy down the data | |
Q_memcpy(lma->socketbuffer[handle][0].data, buf, len); | |
regs.x.cs = ipx_cs; | |
regs.x.ip = ipx_ip; | |
regs.x.bx = IPX_SEND; | |
regs.x.es = ptr2real(&lma->socketbuffer[handle][0].ecb) >> 4; | |
regs.x.si = ptr2real(&lma->socketbuffer[handle][0].ecb) & 0xf; | |
__dpmi_simulate_real_mode_procedure_retf((__dpmi_regs *)®s); | |
return len; | |
} | |
//============================================================================= | |
char *IPX_AddrToString (struct qsockaddr *addr) | |
{ | |
static char buf[28]; | |
sprintf(buf, "%02x%02x%02x%02x:%02x%02x%02x%02x%02x%02x:%u", | |
((struct sockaddr_ipx *)addr)->sipx_addr.network[0], | |
((struct sockaddr_ipx *)addr)->sipx_addr.network[1], | |
((struct sockaddr_ipx *)addr)->sipx_addr.network[2], | |
((struct sockaddr_ipx *)addr)->sipx_addr.network[3], | |
((struct sockaddr_ipx *)addr)->sipx_addr.node[0], | |
((struct sockaddr_ipx *)addr)->sipx_addr.node[1], | |
((struct sockaddr_ipx *)addr)->sipx_addr.node[2], | |
((struct sockaddr_ipx *)addr)->sipx_addr.node[3], | |
((struct sockaddr_ipx *)addr)->sipx_addr.node[4], | |
((struct sockaddr_ipx *)addr)->sipx_addr.node[5], | |
ntohs(((struct sockaddr_ipx *)addr)->sipx_port) | |
); | |
return buf; | |
} | |
//============================================================================= | |
int IPX_StringToAddr (char *string, struct qsockaddr *addr) | |
{ | |
int val; | |
char buf[3]; | |
buf[2] = 0; | |
Q_memset(addr, 0, sizeof(struct qsockaddr)); | |
addr->sa_family = AF_NETWARE; | |
#define DO(src,dest) \ | |
buf[0] = string[src]; \ | |
buf[1] = string[src + 1]; \ | |
if (sscanf (buf, "%x", &val) != 1) \ | |
return -1; \ | |
((struct sockaddr_ipx *)addr)->sipx_addr.dest = val | |
DO(0, network[0]); | |
DO(2, network[1]); | |
DO(4, network[2]); | |
DO(6, network[3]); | |
DO(9, node[0]); | |
DO(11, node[1]); | |
DO(13, node[2]); | |
DO(15, node[3]); | |
DO(17, node[4]); | |
DO(19, node[5]); | |
#undef DO | |
sscanf (&string[22], "%u", &val); | |
((struct sockaddr_ipx *)addr)->sipx_port = htons(val); | |
return 0; | |
} | |
//============================================================================= | |
int IPX_GetSocketAddr (int handle, struct qsockaddr *addr) | |
{ | |
Q_memset(addr, 0, sizeof(struct qsockaddr)); | |
addr->sa_family = AF_NETWARE; | |
IPX_GetLocalAddress(&((struct sockaddr_ipx *)addr)->sipx_addr); | |
((struct sockaddr_ipx *)addr)->sipx_port = ipxsocket[handle]; | |
return 0; | |
} | |
//============================================================================= | |
int IPX_GetNameFromAddr (struct qsockaddr *addr, char *name) | |
{ | |
Q_strcpy(name, IPX_AddrToString(addr)); | |
return 0; | |
} | |
//============================================================================= | |
int IPX_GetAddrFromName (char *name, struct qsockaddr *addr) | |
{ | |
int n; | |
char buf[32]; | |
n = Q_strlen(name); | |
if (n == 12) | |
{ | |
sprintf(buf, "00000000:%s:%u", name, net_hostport); | |
return IPX_StringToAddr (buf, addr); | |
} | |
if (n == 21) | |
{ | |
sprintf(buf, "%s:%u", name, net_hostport); | |
return IPX_StringToAddr (buf, addr); | |
} | |
if (n > 21 && n <= 27) | |
return IPX_StringToAddr (name, addr); | |
return -1; | |
} | |
//============================================================================= | |
int IPX_AddrCompare (struct qsockaddr *addr1, struct qsockaddr *addr2) | |
{ | |
if (addr1->sa_family != addr2->sa_family) | |
return -1; | |
if(Q_memcmp(&((struct sockaddr_ipx *)addr1)->sipx_addr, &((struct sockaddr_ipx *)addr2)->sipx_addr, 10)) | |
return -1; | |
if (((struct sockaddr_ipx *)addr1)->sipx_port != ((struct sockaddr_ipx *)addr2)->sipx_port) | |
return 1; | |
return 0; | |
} | |
//============================================================================= | |
int IPX_GetSocketPort (struct qsockaddr *addr) | |
{ | |
return ntohs(((struct sockaddr_ipx *)addr)->sipx_port); | |
} | |
int IPX_SetSocketPort (struct qsockaddr *addr, int port) | |
{ | |
((struct sockaddr_ipx *)addr)->sipx_port = htons(port); | |
return 0; | |
} | |
//============================================================================= |