1 /*
2 * Copyright (C) 2002-2010 The DOSBox Team
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17 */
18
19 /* $Id: ipx.cpp,v 1.17 2009-05-27 09:15:41 qbix79 Exp $ */
20
21 #include "dosbox.h"
22
23 #if C_IPX
24
25 #include <string.h>
26 #include <time.h>
27 #include <stdio.h>
28 #include "cross.h"
29 #include "support.h"
30 #include "cpu.h"
31 #include "regs.h"
32 #include "inout.h"
33 #include "setup.h"
34 #include "debug.h"
35 #include "callback.h"
36 #include "dos_system.h"
37 #include "mem.h"
38 #include "ipx.h"
39 #include "ipxserver.h"
40 #include "timer.h"
41 #include "SDL_net.h"
42 #include "programs.h"
43 #include "pic.h"
44
45 #define SOCKTABLESIZE 150 // DOS IPX driver was limited to 150 open sockets
46
47 struct ipxnetaddr {
48 Uint8 netnum[4]; // Both are big endian
49 Uint8 netnode[6];
50 } localIpxAddr;
51
52 Bit32u udpPort;
53 bool isIpxServer;
54 bool isIpxConnected;
55 IPaddress ipxServConnIp; // IPAddress for client connection to server
56 UDPsocket ipxClientSocket;
57 int UDPChannel; // Channel used by UDP connection
58 Bit8u recvBuffer[IPXBUFFERSIZE]; // Incoming packet buffer
59
60 static RealPt ipx_callback;
61
62 SDLNet_SocketSet clientSocketSet;
63
64 packetBuffer incomingPacket;
65
66 static Bit16u socketCount;
67 static Bit16u opensockets[SOCKTABLESIZE];
68
swapByte(Bit16u sockNum)69 static Bit16u swapByte(Bit16u sockNum) {
70 return (((sockNum>> 8)) | (sockNum << 8));
71 }
72
UnpackIP(PackedIP ipPack,IPaddress * ipAddr)73 void UnpackIP(PackedIP ipPack, IPaddress * ipAddr) {
74 ipAddr->host = ipPack.host;
75 ipAddr->port = ipPack.port;
76 }
77
PackIP(IPaddress ipAddr,PackedIP * ipPack)78 void PackIP(IPaddress ipAddr, PackedIP *ipPack) {
79 ipPack->host = ipAddr.host;
80 ipPack->port = ipAddr.port;
81 }
82
83 ECBClass *ECBList; // Linked list of ECB's
84 ECBClass* ESRList; // ECBs waiting to be ESR notified
85
86 #ifdef IPX_DEBUGMSG
87 Bitu ECBSerialNumber = 0;
88 Bitu ECBAmount = 0;
89 #endif
90
91
ECBClass(Bit16u segment,Bit16u offset)92 ECBClass::ECBClass(Bit16u segment, Bit16u offset) {
93 ECBAddr = RealMake(segment, offset);
94 databuffer = 0;
95
96 #ifdef IPX_DEBUGMSG
97 SerialNumber = ECBSerialNumber;
98 ECBSerialNumber++;
99 ECBAmount++;
100
101 LOG_IPX("ECB: SN%7d created. Number of ECBs: %3d, ESR %4x:%4x, ECB %4x:%4x",
102 SerialNumber,ECBAmount,
103 real_readw(RealSeg(ECBAddr),
104 RealOff(ECBAddr)+6),
105 real_readw(RealSeg(ECBAddr),
106 RealOff(ECBAddr)+4),segment,offset);
107 #endif
108 isInESRList = false;
109 prevECB = NULL;
110 nextECB = NULL;
111
112 if (ECBList == NULL)
113 ECBList = this;
114 else {
115 // Transverse the list until we hit the end
116 ECBClass *useECB = ECBList;
117
118 while(useECB->nextECB != NULL)
119 useECB = useECB->nextECB;
120
121 useECB->nextECB = this;
122 this->prevECB = useECB;
123 }
124
125 iuflag = getInUseFlag();
126 mysocket = getSocket();
127 }
writeDataBuffer(Bit8u * buffer,Bit16u length)128 void ECBClass::writeDataBuffer(Bit8u* buffer, Bit16u length) {
129 if(databuffer!=0) delete [] databuffer;
130 databuffer = new Bit8u[length];
131 memcpy(databuffer,buffer,length);
132 buflen=length;
133
134 }
writeData()135 bool ECBClass::writeData() {
136 Bitu length=buflen;
137 Bit8u* buffer = databuffer;
138 fragmentDescriptor tmpFrag;
139 setInUseFlag(USEFLAG_AVAILABLE);
140 Bitu fragCount = getFragCount();
141 Bitu bufoffset = 0;
142 for(Bitu i = 0;i < fragCount;i++) {
143 getFragDesc(i,&tmpFrag);
144 for(Bitu t = 0;t < tmpFrag.size;t++) {
145 real_writeb(tmpFrag.segment, tmpFrag.offset + t, buffer[bufoffset]);
146 bufoffset++;
147 if(bufoffset >= length) {
148 setCompletionFlag(COMP_SUCCESS);
149 setImmAddress(&buffer[22]); // Write in source node
150 return true;
151 }
152 }
153 }
154 if(bufoffset < length) {
155 setCompletionFlag(COMP_MALFORMED);
156 return false;
157 }
158 return false;
159 }
160
getSocket(void)161 Bit16u ECBClass::getSocket(void) {
162 return swapByte(real_readw(RealSeg(ECBAddr), RealOff(ECBAddr) + 0xa));
163 }
164
getInUseFlag(void)165 Bit8u ECBClass::getInUseFlag(void) {
166 return real_readb(RealSeg(ECBAddr), RealOff(ECBAddr) + 0x8);
167 }
168
setInUseFlag(Bit8u flagval)169 void ECBClass::setInUseFlag(Bit8u flagval) {
170 iuflag = flagval;
171 real_writeb(RealSeg(ECBAddr), RealOff(ECBAddr) + 0x8, flagval);
172 }
173
setCompletionFlag(Bit8u flagval)174 void ECBClass::setCompletionFlag(Bit8u flagval) {
175 real_writeb(RealSeg(ECBAddr), RealOff(ECBAddr) + 0x9, flagval);
176 }
177
getFragCount(void)178 Bit16u ECBClass::getFragCount(void) {
179 return real_readw(RealSeg(ECBAddr), RealOff(ECBAddr) + 34);
180 }
181
getFragDesc(Bit16u descNum,fragmentDescriptor * fragDesc)182 void ECBClass::getFragDesc(Bit16u descNum, fragmentDescriptor *fragDesc) {
183 Bit16u memoff = RealOff(ECBAddr) + 30 + ((descNum+1) * 6);
184 fragDesc->offset = real_readw(RealSeg(ECBAddr), memoff);
185 memoff += 2;
186 fragDesc->segment = real_readw(RealSeg(ECBAddr), memoff);
187 memoff += 2;
188 fragDesc->size = real_readw(RealSeg(ECBAddr), memoff);
189 }
190
getESRAddr(void)191 RealPt ECBClass::getESRAddr(void) {
192 return RealMake(real_readw(RealSeg(ECBAddr),
193 RealOff(ECBAddr)+6),
194 real_readw(RealSeg(ECBAddr),
195 RealOff(ECBAddr)+4));
196 }
197
NotifyESR(void)198 void ECBClass::NotifyESR(void) {
199 Bit32u ESRval = real_readd(RealSeg(ECBAddr), RealOff(ECBAddr)+4);
200 if(ESRval || databuffer) { // databuffer: write data at realmode/v86 time
201 // LOG_IPX("ECB: SN%7d to be notified.", SerialNumber);
202 // take the ECB out of the current list
203 if(prevECB == NULL) { // was the first in the list
204 ECBList = nextECB;
205 if(ECBList != NULL) ECBList->prevECB = NULL;
206 } else { // not the first
207 prevECB->nextECB = nextECB;
208 if(nextECB != NULL) nextECB->prevECB = prevECB;
209 }
210
211 nextECB = NULL;
212 // put it to the notification queue
213 if(ESRList==NULL) {
214 ESRList = this;
215 prevECB = NULL;
216 } else {// put to end of ESR list
217 ECBClass* useECB = ESRList;
218
219 while(useECB->nextECB != NULL)
220 useECB = useECB->nextECB;
221
222 useECB->nextECB = this;
223 prevECB = useECB;
224 }
225 isInESRList = true;
226 PIC_ActivateIRQ(11);
227 }
228 // this one does not want to be notified, delete it right away
229 else delete this;
230 }
231
setImmAddress(Bit8u * immAddr)232 void ECBClass::setImmAddress(Bit8u *immAddr) {
233 for(Bitu i=0;i<6;i++)
234 real_writeb(RealSeg(ECBAddr), RealOff(ECBAddr)+28+i, immAddr[i]);
235 }
236
getImmAddress(Bit8u * immAddr)237 void ECBClass::getImmAddress(Bit8u* immAddr) {
238 for(Bitu i=0;i<6;i++)
239 immAddr[i] = real_readb(RealSeg(ECBAddr), RealOff(ECBAddr)+28+i);
240 }
241
~ECBClass()242 ECBClass::~ECBClass() {
243 #ifdef IPX_DEBUGMSG
244 ECBAmount--;
245 LOG_IPX("ECB: SN%7d destroyed. Remaining ECBs: %3d", SerialNumber,ECBAmount);
246 #endif
247
248 if(isInESRList) {
249 // in ESR list, always the first element is deleted.
250 ESRList=nextECB;
251 } else {
252 if(prevECB == NULL) { // was the first in the list
253 ECBList = nextECB;
254 if(ECBList != NULL) ECBList->prevECB = NULL;
255 } else { // not the first
256 prevECB->nextECB = nextECB;
257 if(nextECB != NULL) nextECB->prevECB = prevECB;
258 }
259 }
260 if(databuffer!=0) delete [] databuffer;
261 }
262
263
264
sockInUse(Bit16u sockNum)265 static bool sockInUse(Bit16u sockNum) {
266 for(Bitu i=0;i<socketCount;i++) {
267 if (opensockets[i] == sockNum) return true;
268 }
269 return false;
270 }
271
OpenSocket(void)272 static void OpenSocket(void) {
273 Bit16u sockNum, sockAlloc;
274 sockNum = swapByte(reg_dx);
275
276 if(socketCount >= SOCKTABLESIZE) {
277 reg_al = 0xfe; // Socket table full
278 return;
279 }
280
281 if(sockNum == 0x0000) {
282 // Dynamic socket allocation
283 sockAlloc = 0x4002;
284 while(sockInUse(sockAlloc) && (sockAlloc < 0x7fff)) sockAlloc++;
285 if(sockAlloc > 0x7fff) {
286 // I have no idea how this could happen if the IPX driver
287 // is limited to 150 open sockets at a time
288 LOG_MSG("IPX: Out of dynamic sockets");
289 }
290 sockNum = sockAlloc;
291 } else {
292 if(sockInUse(sockNum)) {
293 reg_al = 0xff; // Socket already open
294 return;
295 }
296 }
297
298 opensockets[socketCount] = sockNum;
299 socketCount++;
300
301 reg_al = 0x00; // Success
302 reg_dx = swapByte(sockNum); // Convert back to big-endian
303 }
304
CloseSocket(void)305 static void CloseSocket(void) {
306 Bit16u sockNum, i;
307 ECBClass* tmpECB = ECBList;
308 ECBClass* tmp2ECB = ECBList;
309
310 sockNum = swapByte(reg_dx);
311 if(!sockInUse(sockNum)) return;
312
313 for(i=0;i<socketCount-1;i++) {
314 if (opensockets[i] == sockNum) {
315 // Realign list of open sockets
316 memcpy(&opensockets[i], &opensockets[i+1], SOCKTABLESIZE - (i + 1));
317 break;
318 }
319 }
320 --socketCount;
321
322 // delete all ECBs of that socket
323 while(tmpECB!=0) {
324 tmp2ECB = tmpECB->nextECB;
325 if(tmpECB->getSocket()==sockNum) {
326 tmpECB->setCompletionFlag(COMP_CANCELLED);
327 tmpECB->setInUseFlag(USEFLAG_AVAILABLE);
328 delete tmpECB;
329 }
330 tmpECB = tmp2ECB;
331 }
332 }
333
334 //static RealPt IPXVERpointer;
335
IPX_Multiplex(void)336 static bool IPX_Multiplex(void) {
337 if(reg_ax != 0x7a00) return false;
338 reg_al = 0xff;
339 SegSet16(es,RealSeg(ipx_callback));
340 reg_di = RealOff(ipx_callback);
341
342 //reg_bx = RealOff(IPXVERpointer);
343 //reg_cx = RealSeg(ipx_callback);
344 return true;
345 }
346
IPX_AES_EventHandler(Bitu param)347 static void IPX_AES_EventHandler(Bitu param)
348 {
349 ECBClass* tmpECB = ECBList;
350 ECBClass* tmp2ECB;
351 while(tmpECB!=0) {
352 tmp2ECB = tmpECB->nextECB;
353 if(tmpECB->iuflag==USEFLAG_AESCOUNT && param==(Bitu)tmpECB->ECBAddr) {
354 tmpECB->setCompletionFlag(COMP_SUCCESS);
355 tmpECB->setInUseFlag(USEFLAG_AVAILABLE);
356 tmpECB->NotifyESR();
357 // LOG_IPX("AES Notification: ECB S/N %d",tmpECB->SerialNumber);
358 return;
359 }
360 tmpECB = tmp2ECB;
361 }
362 LOG_MSG("!!!! Rouge AES !!!!" );
363 }
364
365 static void sendPacket(ECBClass* sendecb);
366
handleIpxRequest(void)367 static void handleIpxRequest(void) {
368 ECBClass *tmpECB;
369
370 switch (reg_bx) {
371 case 0x0000: // Open socket
372 OpenSocket();
373 LOG_IPX("IPX: Open socket %4x", swapByte(reg_dx));
374 break;
375 case 0x0001: // Close socket
376 LOG_IPX("IPX: Close socket %4x", swapByte(reg_dx));
377 CloseSocket();
378 break;
379 case 0x0002: // get local target
380 // es:si
381 // Currently no support for multiple networks
382
383 for(Bitu i = 0; i < 6; i++)
384 real_writeb(SegValue(es),reg_di+i,real_readb(SegValue(es),reg_si+i+4));
385
386 reg_cx=1; // time ticks expected
387 reg_al=0x00; //success
388 break;
389
390 case 0x0003: // Send packet
391 tmpECB = new ECBClass(SegValue(es),reg_si);
392 if(!incomingPacket.connected) {
393 tmpECB->setInUseFlag(USEFLAG_AVAILABLE);
394 tmpECB->setCompletionFlag(COMP_UNDELIVERABLE);
395 delete tmpECB; // not notify?
396 reg_al = 0xff; // Failure
397 } else {
398 tmpECB->setInUseFlag(USEFLAG_SENDING);
399 //LOG_IPX("IPX: Sending packet on %4x", tmpECB->getSocket());
400 reg_al = 0x00; // Success
401 sendPacket(tmpECB);
402 }
403
404 break;
405 case 0x0004: // Listen for packet
406 tmpECB = new ECBClass(SegValue(es),reg_si);
407 // LOG_IPX("ECB: SN%7d RECEIVE.", tmpECB->SerialNumber);
408 if(!sockInUse(tmpECB->getSocket())) { // Socket is not open
409 reg_al = 0xff;
410 tmpECB->setInUseFlag(USEFLAG_AVAILABLE);
411 tmpECB->setCompletionFlag(COMP_HARDWAREERROR);
412 delete tmpECB;
413 } else {
414 reg_al = 0x00; // Success
415 tmpECB->setInUseFlag(USEFLAG_LISTENING);
416 /*LOG_IPX("IPX: Listen for packet on 0x%4x - ESR address %4x:%4x",
417 tmpECB->getSocket(),
418 RealSeg(tmpECB->getESRAddr()),
419 RealOff(tmpECB->getESRAddr()));*/
420 }
421 break;
422
423 case 0x0005: // SCHEDULE IPX EVENT
424 case 0x0007: // SCHEDULE SPECIAL IPX EVENT
425 {
426 tmpECB = new ECBClass(SegValue(es),reg_si);
427 // LOG_IPX("ECB: SN%7d AES. T=%fms.", tmpECB->SerialNumber,
428 // (1000.0f/(1193182.0f/65536.0f))*(float)reg_ax);
429 PIC_AddEvent(IPX_AES_EventHandler,
430 (1000.0f/(1193182.0f/65536.0f))*(float)reg_ax,(Bitu)tmpECB->ECBAddr);
431 tmpECB->setInUseFlag(USEFLAG_AESCOUNT);
432 break;
433 }
434 case 0x0006: // cancel operation
435 {
436 RealPt ecbaddress = RealMake(SegValue(es),reg_si);
437 ECBClass* tmpECB= ECBList;
438 ECBClass* tmp2ECB;
439 while(tmpECB) {
440 tmp2ECB=tmpECB->nextECB;
441 if(tmpECB->ECBAddr == ecbaddress) {
442 if(tmpECB->getInUseFlag()==USEFLAG_AESCOUNT)
443 PIC_RemoveSpecificEvents(IPX_AES_EventHandler,(Bitu)ecbaddress);
444 tmpECB->setInUseFlag(USEFLAG_AVAILABLE);
445 tmpECB->setCompletionFlag(COMP_CANCELLED);
446 delete tmpECB;
447 reg_al=0; // Success
448 LOG_IPX("IPX: ECB canceled.");
449 return;
450 }
451 tmpECB=tmp2ECB;
452 }
453 reg_al=0xff; // Fail
454 break;
455 }
456 case 0x0008: // Get interval marker
457 reg_ax = mem_readw(0x46c); // BIOS_TIMER
458 break;
459 case 0x0009: // Get internetwork address
460 {
461 LOG_IPX("IPX: Get internetwork address %2x:%2x:%2x:%2x:%2x:%2x",
462 localIpxAddr.netnode[5], localIpxAddr.netnode[4],
463 localIpxAddr.netnode[3], localIpxAddr.netnode[2],
464 localIpxAddr.netnode[1], localIpxAddr.netnode[0]);
465
466 Bit8u * addrptr = (Bit8u *)&localIpxAddr;
467 for(Bit16u i=0;i<10;i++)
468 real_writeb(SegValue(es),reg_si+i,addrptr[i]);
469 break;
470 }
471 case 0x000a: // Relinquish control
472 break; // Idle thingy
473
474 case 0x000b: // Disconnect from Target
475 break; // We don't even connect
476
477 case 0x000d: // get packet size
478 reg_cx=0; // retry count
479 reg_ax=1024; // real implementation returns 1024
480 break;
481
482 case 0x0010: // SPX install check
483 reg_al=0; // SPX not installed
484 break;
485
486 case 0x001a: // get driver maximum packet size
487 reg_cx=0; // retry count
488 reg_ax=IPXBUFFERSIZE; // max packet size: something near the
489 // ethernet packet size
490 break;
491
492 default:
493 LOG_MSG("Unhandled IPX function: %4x", reg_bx);
494 break;
495 }
496 }
497
498 // Entrypoint handler
IPX_Handler(void)499 Bitu IPX_Handler(void) {
500 handleIpxRequest();
501 return CBRET_NONE;
502 }
503
504 // INT 7A handler
IPX_IntHandler(void)505 Bitu IPX_IntHandler(void) {
506 handleIpxRequest();
507 return CBRET_NONE;
508 }
509
pingAck(IPaddress retAddr)510 static void pingAck(IPaddress retAddr) {
511 IPXHeader regHeader;
512 UDPpacket regPacket;
513 Bits result;
514
515 SDLNet_Write16(0xffff, regHeader.checkSum);
516 SDLNet_Write16(sizeof(regHeader), regHeader.length);
517
518 SDLNet_Write32(0, regHeader.dest.network);
519 PackIP(retAddr, ®Header.dest.addr.byIP);
520 SDLNet_Write16(0x2, regHeader.dest.socket);
521
522 SDLNet_Write32(0, regHeader.src.network);
523 memcpy(regHeader.src.addr.byNode.node, localIpxAddr.netnode, sizeof(regHeader.src.addr.byNode.node));
524 SDLNet_Write16(0x2, regHeader.src.socket);
525 regHeader.transControl = 0;
526 regHeader.pType = 0x0;
527
528 regPacket.data = (Uint8 *)®Header;
529 regPacket.len = sizeof(regHeader);
530 regPacket.maxlen = sizeof(regHeader);
531 regPacket.channel = UDPChannel;
532
533 result = SDLNet_UDP_Send(ipxClientSocket, regPacket.channel, ®Packet);
534 }
535
pingSend(void)536 static void pingSend(void) {
537 IPXHeader regHeader;
538 UDPpacket regPacket;
539 Bits result;
540
541 SDLNet_Write16(0xffff, regHeader.checkSum);
542 SDLNet_Write16(sizeof(regHeader), regHeader.length);
543
544 SDLNet_Write32(0, regHeader.dest.network);
545 regHeader.dest.addr.byIP.host = 0xffffffff;
546 regHeader.dest.addr.byIP.port = 0xffff;
547 SDLNet_Write16(0x2, regHeader.dest.socket);
548
549 SDLNet_Write32(0, regHeader.src.network);
550 memcpy(regHeader.src.addr.byNode.node, localIpxAddr.netnode, sizeof(regHeader.src.addr.byNode.node));
551 SDLNet_Write16(0x2, regHeader.src.socket);
552 regHeader.transControl = 0;
553 regHeader.pType = 0x0;
554
555 regPacket.data = (Uint8 *)®Header;
556 regPacket.len = sizeof(regHeader);
557 regPacket.maxlen = sizeof(regHeader);
558 regPacket.channel = UDPChannel;
559
560 result = SDLNet_UDP_Send(ipxClientSocket, regPacket.channel, ®Packet);
561 if(!result) {
562 LOG_MSG("IPX: SDLNet_UDP_Send: %s\n", SDLNet_GetError());
563 }
564 }
565
receivePacket(Bit8u * buffer,Bit16s bufSize)566 static void receivePacket(Bit8u *buffer, Bit16s bufSize) {
567 ECBClass *useECB;
568 ECBClass *nextECB;
569 Bit16u *bufword = (Bit16u *)buffer;
570 Bit16u useSocket = swapByte(bufword[8]);
571 IPXHeader * tmpHeader;
572 tmpHeader = (IPXHeader *)buffer;
573
574 // Check to see if ping packet
575 if(useSocket == 0x2) {
576 // Is this a broadcast?
577 if((tmpHeader->dest.addr.byIP.host == 0xffffffff) &&
578 (tmpHeader->dest.addr.byIP.port == 0xffff)) {
579 // Yes. We should return the ping back to the sender
580 IPaddress tmpAddr;
581 UnpackIP(tmpHeader->src.addr.byIP, &tmpAddr);
582 pingAck(tmpAddr);
583 return;
584 }
585 }
586
587 useECB = ECBList;
588 while(useECB != NULL)
589 {
590 nextECB = useECB->nextECB;
591 if(useECB->iuflag == USEFLAG_LISTENING && useECB->mysocket == useSocket) {
592 useECB->writeDataBuffer(buffer, bufSize);
593 useECB->NotifyESR();
594 return;
595 }
596 useECB = nextECB;
597 }
598 LOG_IPX("IPX: RX Packet loss!");
599 }
600
IPX_ClientLoop(void)601 static void IPX_ClientLoop(void) {
602 int numrecv;
603 UDPpacket inPacket;
604 inPacket.data = (Uint8 *)recvBuffer;
605 inPacket.maxlen = IPXBUFFERSIZE;
606 inPacket.channel = UDPChannel;
607
608 // Its amazing how much simpler UDP is than TCP
609 numrecv = SDLNet_UDP_Recv(ipxClientSocket, &inPacket);
610 if(numrecv) receivePacket(inPacket.data, inPacket.len);
611 }
612
613
DisconnectFromServer(bool unexpected)614 void DisconnectFromServer(bool unexpected) {
615 if(unexpected) LOG_MSG("IPX: Server disconnected unexpectedly");
616 if(incomingPacket.connected) {
617 incomingPacket.connected = false;
618 TIMER_DelTickHandler(&IPX_ClientLoop);
619 SDLNet_UDP_Close(ipxClientSocket);
620 }
621 }
622
sendPacket(ECBClass * sendecb)623 static void sendPacket(ECBClass* sendecb) {
624 Bit8u outbuffer[IPXBUFFERSIZE];
625 fragmentDescriptor tmpFrag;
626 Bit16u i, fragCount,t;
627 Bit16s packetsize;
628 Bit16u *wordptr;
629 Bits result;
630 UDPpacket outPacket;
631
632 sendecb->setInUseFlag(USEFLAG_AVAILABLE);
633 packetsize = 0;
634 fragCount = sendecb->getFragCount();
635 for(i=0;i<fragCount;i++) {
636 sendecb->getFragDesc(i,&tmpFrag);
637 if(i==0) {
638 // Fragment containing IPX header
639 // Must put source address into header
640 Bit8u * addrptr;
641
642 // source netnum
643 addrptr = (Bit8u *)&localIpxAddr.netnum;
644 for(Bit16u m=0;m<4;m++) {
645 real_writeb(tmpFrag.segment,tmpFrag.offset+m+18,addrptr[m]);
646 }
647 // source node number
648 addrptr = (Bit8u *)&localIpxAddr.netnode;
649 for(Bit16u m=0;m<6;m++) {
650 real_writeb(tmpFrag.segment,tmpFrag.offset+m+22,addrptr[m]);
651 }
652 // Source socket
653 real_writew(tmpFrag.segment,tmpFrag.offset+28, swapByte(sendecb->getSocket()));
654
655 // blank checksum
656 real_writew(tmpFrag.segment,tmpFrag.offset, 0xffff);
657 }
658
659 for(t=0;t<tmpFrag.size;t++) {
660 outbuffer[packetsize] = real_readb(tmpFrag.segment, tmpFrag.offset + t);
661 packetsize++;
662 if(packetsize>=IPXBUFFERSIZE) {
663 LOG_MSG("IPX: Packet size to be sent greater than %d bytes.", IPXBUFFERSIZE);
664 sendecb->setCompletionFlag(COMP_UNDELIVERABLE);
665 sendecb->NotifyESR();
666 return;
667 }
668 }
669 }
670
671 // Add length and source socket to IPX header
672 wordptr = (Bit16u *)&outbuffer[0];
673 // Blank CRC
674 //wordptr[0] = 0xffff;
675 // Length
676 wordptr[1] = swapByte(packetsize);
677 // Source socket
678 //wordptr[14] = swapByte(sendecb->getSocket());
679
680 sendecb->getFragDesc(0,&tmpFrag);
681 real_writew(tmpFrag.segment,tmpFrag.offset+2, swapByte(packetsize));
682
683
684 Bit8u immedAddr[6];
685 sendecb->getImmAddress(immedAddr);
686 // filter out broadcasts and local loopbacks
687 // Real implementation uses the ImmedAddr to check wether this is a broadcast
688
689 bool islocalbroadcast=true;
690 bool isloopback=true;
691
692 Bit8u * addrptr;
693
694 addrptr = (Bit8u *)&localIpxAddr.netnum;
695 for(Bitu m=0;m<4;m++) {
696 if(addrptr[m]!=outbuffer[m+0x6])isloopback=false;
697 }
698 addrptr = (Bit8u *)&localIpxAddr.netnode;
699 for(Bitu m=0;m<6;m++) {
700 if(addrptr[m]!=outbuffer[m+0xa])isloopback=false;
701 if(immedAddr[m]!=0xff) islocalbroadcast=false;
702 }
703 LOG_IPX("SEND crc:%2x",packetCRC(&outbuffer[0], packetsize));
704 if(!isloopback) {
705 outPacket.channel = UDPChannel;
706 outPacket.data = (Uint8 *)&outbuffer[0];
707 outPacket.len = packetsize;
708 outPacket.maxlen = packetsize;
709 // Since we're using a channel, we won't send the IP address again
710 result = SDLNet_UDP_Send(ipxClientSocket, UDPChannel, &outPacket);
711
712 if(result == 0) {
713 LOG_MSG("IPX: Could not send packet: %s", SDLNet_GetError());
714 sendecb->setCompletionFlag(COMP_HARDWAREERROR);
715 sendecb->NotifyESR();
716 DisconnectFromServer(true);
717 return;
718 } else {
719 sendecb->setCompletionFlag(COMP_SUCCESS);
720 LOG_IPX("Packet sent: size: %d",packetsize);
721 }
722 }
723 else sendecb->setCompletionFlag(COMP_SUCCESS);
724
725 if(isloopback||islocalbroadcast) {
726 // Send packet back to ourselves.
727 receivePacket(&outbuffer[0],packetsize);
728 LOG_IPX("Packet back: loopback:%d, broadcast:%d",isloopback,islocalbroadcast);
729 }
730 sendecb->NotifyESR();
731 }
732
pingCheck(IPXHeader * outHeader)733 static bool pingCheck(IPXHeader * outHeader) {
734 char buffer[1024];
735 Bits result;
736 UDPpacket regPacket;
737 IPXHeader *regHeader;
738 regPacket.data = (Uint8 *)buffer;
739 regPacket.maxlen = sizeof(buffer);
740 regPacket.channel = UDPChannel;
741 regHeader = (IPXHeader *)buffer;
742
743 result = SDLNet_UDP_Recv(ipxClientSocket, ®Packet);
744 if (result != 0) {
745 memcpy(outHeader, regHeader, sizeof(IPXHeader));
746 return true;
747 }
748 return false;
749 }
750
ConnectToServer(char const * strAddr)751 bool ConnectToServer(char const *strAddr) {
752 int numsent;
753 UDPpacket regPacket;
754 IPXHeader regHeader;
755 if(!SDLNet_ResolveHost(&ipxServConnIp, strAddr, (Bit16u)udpPort)) {
756
757 // Generate the MAC address. This is made by zeroing out the first two
758 // octets and then using the actual IP address for the last 4 octets.
759 // This idea is from the IPX over IP implementation as specified in RFC 1234:
760 // http://www.faqs.org/rfcs/rfc1234.html
761
762 // Select an anonymous UDP port
763 ipxClientSocket = SDLNet_UDP_Open(0);
764 if(ipxClientSocket) {
765 // Bind UDP port to address to channel
766 UDPChannel = SDLNet_UDP_Bind(ipxClientSocket,-1,&ipxServConnIp);
767 //ipxClientSocket = SDLNet_TCP_Open(&ipxServConnIp);
768 SDLNet_Write16(0xffff, regHeader.checkSum);
769 SDLNet_Write16(sizeof(regHeader), regHeader.length);
770
771 // Echo packet with zeroed dest and src is a server registration packet
772 SDLNet_Write32(0, regHeader.dest.network);
773 regHeader.dest.addr.byIP.host = 0x0;
774 regHeader.dest.addr.byIP.port = 0x0;
775 SDLNet_Write16(0x2, regHeader.dest.socket);
776
777 SDLNet_Write32(0, regHeader.src.network);
778 regHeader.src.addr.byIP.host = 0x0;
779 regHeader.src.addr.byIP.port = 0x0;
780 SDLNet_Write16(0x2, regHeader.src.socket);
781 regHeader.transControl = 0;
782
783 regPacket.data = (Uint8 *)®Header;
784 regPacket.len = sizeof(regHeader);
785 regPacket.maxlen = sizeof(regHeader);
786 regPacket.channel = UDPChannel;
787 // Send registration string to server. If server doesn't get
788 // this, client will not be registered
789 numsent = SDLNet_UDP_Send(ipxClientSocket, regPacket.channel, ®Packet);
790
791 if(!numsent) {
792 LOG_MSG("IPX: Unable to connect to server: %s", SDLNet_GetError());
793 SDLNet_UDP_Close(ipxClientSocket);
794 return false;
795 } else {
796 // Wait for return packet from server.
797 // This will contain our IPX address and port num
798 Bits result;
799 Bit32u ticks, elapsed;
800 ticks = GetTicks();
801
802 while(true) {
803 elapsed = GetTicks() - ticks;
804 if(elapsed > 5000) {
805 LOG_MSG("Timeout connecting to server at %s", strAddr);
806 SDLNet_UDP_Close(ipxClientSocket);
807
808 return false;
809 }
810 CALLBACK_Idle();
811 result = SDLNet_UDP_Recv(ipxClientSocket, ®Packet);
812 if (result != 0) {
813 memcpy(localIpxAddr.netnode, regHeader.dest.addr.byNode.node, sizeof(localIpxAddr.netnode));
814 memcpy(localIpxAddr.netnum, regHeader.dest.network, sizeof(localIpxAddr.netnum));
815 break;
816 }
817
818 }
819
820 LOG_MSG("IPX: Connected to server. IPX address is %d:%d:%d:%d:%d:%d", CONVIPX(localIpxAddr.netnode));
821
822 incomingPacket.connected = true;
823 TIMER_AddTickHandler(&IPX_ClientLoop);
824 return true;
825 }
826 } else {
827 LOG_MSG("IPX: Unable to open socket");
828 }
829 } else {
830 LOG_MSG("IPX: Unable resolve connection to server");
831 }
832 return false;
833 }
834
IPX_NetworkInit()835 void IPX_NetworkInit() {
836
837 localIpxAddr.netnum[0] = 0x0;
838 localIpxAddr.netnum[1] = 0x0;
839 localIpxAddr.netnum[2] = 0x0;
840 localIpxAddr.netnum[3] = 0x1;
841 localIpxAddr.netnode[0] = 0x00;
842 localIpxAddr.netnode[1] = 0x00;
843 localIpxAddr.netnode[2] = 0x00;
844 localIpxAddr.netnode[3] = 0x00;
845 localIpxAddr.netnode[4] = 0x00;
846 localIpxAddr.netnode[5] = 0x00;
847
848 socketCount = 0;
849 return;
850 }
851
852 class IPXNET : public Program {
853 public:
HelpCommand(const char * helpStr)854 void HelpCommand(const char *helpStr) {
855 // Help on connect command
856 if(strcasecmp("connect", helpStr) == 0) {
857 WriteOut("IPXNET CONNECT opens a connection to an IPX tunneling server running on another\n");
858 WriteOut("DosBox session. The \"address\" parameter specifies the IP address or host name\n");
859 WriteOut("of the server computer. One can also specify the UDP port to use. By default\n");
860 WriteOut("IPXNET uses port 213, the assigned IANA port for IPX tunneling, for its\nconnection.\n\n");
861 WriteOut("The syntax for IPXNET CONNECT is:\n\n");
862 WriteOut("IPXNET CONNECT address <port>\n\n");
863 return;
864 }
865 // Help on the disconnect command
866 if(strcasecmp("disconnect", helpStr) == 0) {
867 WriteOut("IPXNET DISCONNECT closes the connection to the IPX tunneling server.\n\n");
868 WriteOut("The syntax for IPXNET DISCONNECT is:\n\n");
869 WriteOut("IPXNET DISCONNECT\n\n");
870 return;
871 }
872 // Help on the startserver command
873 if(strcasecmp("startserver", helpStr) == 0) {
874 WriteOut("IPXNET STARTSERVER starts and IPX tunneling server on this DosBox session. By\n");
875 WriteOut("default, the server will accept connections on UDP port 213, though this can be\n");
876 WriteOut("changed. Once the server is started, DosBox will automatically start a client\n");
877 WriteOut("connection to the IPX tunneling server.\n\n");
878 WriteOut("The syntax for IPXNET STARTSERVER is:\n\n");
879 WriteOut("IPXNET STARTSERVER <port>\n\n");
880 return;
881 }
882 // Help on the stop server command
883 if(strcasecmp("stopserver", helpStr) == 0) {
884 WriteOut("IPXNET STOPSERVER stops the IPX tunneling server running on this DosBox\nsession.");
885 WriteOut(" Care should be taken to ensure that all other connections have\nterminated ");
886 WriteOut("as well sinnce stoping the server may cause lockups on other\nmachines still using ");
887 WriteOut("the IPX tunneling server.\n\n");
888 WriteOut("The syntax for IPXNET STOPSERVER is:\n\n");
889 WriteOut("IPXNET STOPSERVER\n\n");
890 return;
891 }
892 // Help on the ping command
893 if(strcasecmp("ping", helpStr) == 0) {
894 WriteOut("IPXNET PING broadcasts a ping request through the IPX tunneled network. In \n");
895 WriteOut("response, all other connected computers will respond to the ping and report\n");
896 WriteOut("the time it took to receive and send the ping message.\n\n");
897 WriteOut("The syntax for IPXNET PING is:\n\n");
898 WriteOut("IPXNET PING\n\n");
899 return;
900 }
901 // Help on the status command
902 if(strcasecmp("status", helpStr) == 0) {
903 WriteOut("IPXNET STATUS reports the current state of this DosBox's sessions IPX tunneling\n");
904 WriteOut("network. For a list of the computers connected to the network use the IPXNET \n");
905 WriteOut("PING command.\n\n");
906 WriteOut("The syntax for IPXNET STATUS is:\n\n");
907 WriteOut("IPXNET STATUS\n\n");
908 return;
909 }
910 }
911
Run(void)912 void Run(void)
913 {
914 WriteOut("IPX Tunneling utility for DosBox\n\n");
915 if(!cmd->GetCount()) {
916 WriteOut("The syntax of this command is:\n\n");
917 WriteOut("IPXNET [ CONNECT | DISCONNECT | STARTSERVER | STOPSERVER | PING | HELP |\n STATUS ]\n\n");
918 return;
919 }
920
921 if(cmd->FindCommand(1, temp_line)) {
922 if(strcasecmp("help", temp_line.c_str()) == 0) {
923 if(!cmd->FindCommand(2, temp_line)) {
924 WriteOut("The following are valid IPXNET commands:\n\n");
925 WriteOut("IPXNET CONNECT IPXNET DISCONNECT IPXNET STARTSERVER\n");
926 WriteOut("IPXNET STOPSERVER IPXNET PING IPXNET STATUS\n\n");
927 WriteOut("To get help on a specific command, type:\n\n");
928 WriteOut("IPXNET HELP command\n\n");
929
930 } else {
931 HelpCommand(temp_line.c_str());
932 return;
933 }
934 return;
935 }
936 if(strcasecmp("startserver", temp_line.c_str()) == 0) {
937 if(!isIpxServer) {
938 if(incomingPacket.connected) {
939 WriteOut("IPX Tunneling Client alreadu connected to another server. Disconnect first.\n");
940 return;
941 }
942 bool startsuccess;
943 if(!cmd->FindCommand(2, temp_line)) {
944 udpPort = 213;
945 } else {
946 udpPort = strtol(temp_line.c_str(), NULL, 10);
947 }
948 startsuccess = IPX_StartServer((Bit16u)udpPort);
949 if(startsuccess) {
950 WriteOut("IPX Tunneling Server started\n");
951 isIpxServer = true;
952 ConnectToServer("localhost");
953 } else {
954 WriteOut("IPX Tunneling Server failed to start.\n");
955 if(udpPort < 1024) WriteOut("Try a port number above 1024. See IPXNET HELP CONNECT on how to specify a port.\n");
956 }
957 } else {
958 WriteOut("IPX Tunneling Server already started\n");
959 }
960 return;
961 }
962 if(strcasecmp("stopserver", temp_line.c_str()) == 0) {
963 if(!isIpxServer) {
964 WriteOut("IPX Tunneling Server not running in this DosBox session.\n");
965 } else {
966 isIpxServer = false;
967 DisconnectFromServer(false);
968 IPX_StopServer();
969 WriteOut("IPX Tunneling Server stopped.");
970 }
971 return;
972 }
973 if(strcasecmp("connect", temp_line.c_str()) == 0) {
974 char strHost[1024];
975 if(incomingPacket.connected) {
976 WriteOut("IPX Tunneling Client already connected.\n");
977 return;
978 }
979 if(!cmd->FindCommand(2, temp_line)) {
980 WriteOut("IPX Server address not specified.\n");
981 return;
982 }
983 strcpy(strHost, temp_line.c_str());
984
985 if(!cmd->FindCommand(3, temp_line)) {
986 udpPort = 213;
987 } else {
988 udpPort = strtol(temp_line.c_str(), NULL, 10);
989 }
990
991 if(ConnectToServer(strHost)) {
992 WriteOut("IPX Tunneling Client connected to server at %s.\n", strHost);
993 } else {
994 WriteOut("IPX Tunneling Client failed to connect to server at %s.\n", strHost);
995 }
996 return;
997 }
998
999 if(strcasecmp("disconnect", temp_line.c_str()) == 0) {
1000 if(!incomingPacket.connected) {
1001 WriteOut("IPX Tunneling Client not connected.\n");
1002 return;
1003 }
1004 // TODO: Send a packet to the server notifying of disconnect
1005 WriteOut("IPX Tunneling Client disconnected from server.\n");
1006 DisconnectFromServer(false);
1007 return;
1008 }
1009
1010 if(strcasecmp("status", temp_line.c_str()) == 0) {
1011 WriteOut("IPX Tunneling Status:\n\n");
1012 WriteOut("Server status: ");
1013 if(isIpxServer) WriteOut("ACTIVE\n"); else WriteOut("INACTIVE\n");
1014 WriteOut("Client status: ");
1015 if(incomingPacket.connected) {
1016 WriteOut("CONNECTED -- Server at %d.%d.%d.%d port %d\n", CONVIP(ipxServConnIp.host), udpPort);
1017 } else {
1018 WriteOut("DISCONNECTED\n");
1019 }
1020 if(isIpxServer) {
1021 WriteOut("List of active connections:\n\n");
1022 int i;
1023 IPaddress *ptrAddr;
1024 for(i=0;i<SOCKETTABLESIZE;i++) {
1025 if(IPX_isConnectedToServer(i,&ptrAddr)) {
1026 WriteOut(" %d.%d.%d.%d from port %d\n", CONVIP(ptrAddr->host), SDLNet_Read16(&ptrAddr->port));
1027 }
1028 }
1029 WriteOut("\n");
1030 }
1031 return;
1032 }
1033
1034 if(strcasecmp("ping", temp_line.c_str()) == 0) {
1035 Bit32u ticks;
1036 IPXHeader pingHead;
1037
1038 if(!incomingPacket.connected) {
1039 WriteOut("IPX Tunneling Client not connected.\n");
1040 return;
1041 }
1042 TIMER_DelTickHandler(&IPX_ClientLoop);
1043 WriteOut("Sending broadcast ping:\n\n");
1044 pingSend();
1045 ticks = GetTicks();
1046 while((GetTicks() - ticks) < 1500) {
1047 CALLBACK_Idle();
1048 if(pingCheck(&pingHead)) {
1049 WriteOut("Response from %d.%d.%d.%d, port %d time=%dms\n", CONVIP(pingHead.src.addr.byIP.host), SDLNet_Read16(&pingHead.src.addr.byIP.port), GetTicks() - ticks);
1050 }
1051 }
1052 TIMER_AddTickHandler(&IPX_ClientLoop);
1053 return;
1054 }
1055 }
1056 }
1057 };
1058
IPXNET_ProgramStart(Program ** make)1059 static void IPXNET_ProgramStart(Program * * make) {
1060 *make=new IPXNET;
1061 }
1062
IPX_ESRHandler(void)1063 Bitu IPX_ESRHandler(void) {
1064 LOG_IPX("ESR: >>>>>>>>>>>>>>>" );
1065 while(ESRList!=NULL) {
1066 // LOG_IPX("ECB: SN%7d notified.", ESRList->SerialNumber);
1067 if(ESRList->databuffer) ESRList->writeData();
1068 if(ESRList->getESRAddr()) {
1069 // setup registers
1070 SegSet16(es, RealSeg(ESRList->ECBAddr));
1071 reg_si = RealOff(ESRList->ECBAddr);
1072 reg_al = 0xff;
1073 CALLBACK_RunRealFar(RealSeg(ESRList->getESRAddr()),
1074 RealOff(ESRList->getESRAddr()));
1075 }
1076 delete ESRList;
1077 } // while
1078
1079 IO_WriteB(0xa0,0x63); //EOI11
1080 IO_WriteB(0x20,0x62); //EOI2
1081 LOG_IPX("ESR: <<<<<<<<<<<<<<<");
1082 return CBRET_NONE;
1083 }
1084
1085 void VFILE_Remove(const char *name);
1086
1087 class IPX: public Module_base {
1088 private:
1089 CALLBACK_HandlerObject callback_ipx;
1090 CALLBACK_HandlerObject callback_esr;
1091 CALLBACK_HandlerObject callback_ipxint;
1092 RealPt old_73_vector;
1093 static Bit16u dospage;
1094 public:
IPX(Section * configuration)1095 IPX(Section* configuration):Module_base(configuration) {
1096 Section_prop * section = static_cast<Section_prop *>(configuration);
1097 if(!section->Get_bool("ipx")) return;
1098 if(!SDLNetInited) {
1099 if(SDLNet_Init() == -1){
1100 LOG_MSG("SDLNet_Init failed: %s\n", SDLNet_GetError());
1101 return;
1102 }
1103 SDLNetInited = true;
1104 }
1105
1106 ECBList = NULL;
1107 ESRList = NULL;
1108 isIpxServer = false;
1109 isIpxConnected = false;
1110 IPX_NetworkInit();
1111
1112 DOS_AddMultiplexHandler(IPX_Multiplex);
1113
1114 callback_ipx.Install(&IPX_Handler,CB_RETF,"IPX Handler");
1115 ipx_callback = callback_ipx.Get_RealPointer();
1116
1117 callback_ipxint.Install(&IPX_IntHandler,CB_IRET,"IPX (int 7a)");
1118 callback_ipxint.Set_RealVec(0x7a);
1119
1120 callback_esr.Allocate(&IPX_ESRHandler,"IPX_ESR");
1121 Bit16u call_ipxesr1 = callback_esr.Get_callback();
1122
1123 if(!dospage) dospage = DOS_GetMemory(2); // can not be freed yet
1124
1125 PhysPt phyDospage = PhysMake(dospage,0);
1126
1127 LOG_IPX("ESR callback address: %x, HandlerID %d", phyDospage,call_ipxesr1);
1128
1129 //save registers
1130 phys_writeb(phyDospage+0,(Bit8u)0xFA); // CLI
1131 phys_writeb(phyDospage+1,(Bit8u)0x60); // PUSHA
1132 phys_writeb(phyDospage+2,(Bit8u)0x1E); // PUSH DS
1133 phys_writeb(phyDospage+3,(Bit8u)0x06); // PUSH ES
1134 phys_writew(phyDospage+4,(Bit16u)0xA00F); // PUSH FS
1135 phys_writew(phyDospage+6,(Bit16u)0xA80F); // PUSH GS
1136
1137 // callback
1138 phys_writeb(phyDospage+8,(Bit8u)0xFE); // GRP 4
1139 phys_writeb(phyDospage+9,(Bit8u)0x38); // Extra Callback instruction
1140 phys_writew(phyDospage+10,call_ipxesr1); // Callback identifier
1141
1142 // register recreation
1143 phys_writew(phyDospage+12,(Bit16u)0xA90F); // POP GS
1144 phys_writew(phyDospage+14,(Bit16u)0xA10F); // POP FS
1145 phys_writeb(phyDospage+16,(Bit8u)0x07); // POP ES
1146 phys_writeb(phyDospage+17,(Bit8u)0x1F); // POP DS
1147 phys_writeb(phyDospage+18,(Bit8u)0x61); // POPA
1148 phys_writeb(phyDospage+19,(Bit8u)0xCF); // IRET: restores flags, CS, IP
1149
1150 // IPX version 2.12
1151 //phys_writeb(phyDospage+27,(Bit8u)0x2);
1152 //phys_writeb(phyDospage+28,(Bit8u)0x12);
1153 //IPXVERpointer = RealMake(dospage,27);
1154
1155 RealPt ESRRoutineBase = RealMake(dospage, 0);
1156
1157 // Interrupt enabling
1158 RealSetVec(0x73,ESRRoutineBase,old_73_vector); // IRQ11
1159 IO_WriteB(0xa1,IO_ReadB(0xa1)&(~8)); // enable IRQ11
1160
1161 PROGRAMS_MakeFile("IPXNET.COM",IPXNET_ProgramStart);
1162 }
1163
~IPX()1164 ~IPX() {
1165 Section_prop * section = static_cast<Section_prop *>(m_configuration);
1166 PIC_RemoveEvents(IPX_AES_EventHandler);
1167 if(!section->Get_bool("ipx")) return;
1168
1169 if(isIpxServer) {
1170 isIpxServer = false;
1171 IPX_StopServer();
1172 }
1173 DisconnectFromServer(false);
1174
1175 DOS_DelMultiplexHandler(IPX_Multiplex);
1176 RealSetVec(0x73,old_73_vector);
1177 IO_WriteB(0xa1,IO_ReadB(0xa1)|8); // disable IRQ11
1178
1179 PhysPt phyDospage = PhysMake(dospage,0);
1180 for(Bitu i = 0;i < 32;i++)
1181 phys_writeb(phyDospage+i,(Bit8u)0x00);
1182
1183 VFILE_Remove("IPXNET.COM");
1184 }
1185 };
1186
1187 static IPX* test;
1188
IPX_ShutDown(Section * sec)1189 void IPX_ShutDown(Section* sec) {
1190 delete test;
1191 }
1192
IPX_Init(Section * sec)1193 void IPX_Init(Section* sec) {
1194 test = new IPX(sec);
1195 sec->AddDestroyFunction(&IPX_ShutDown,true);
1196 }
1197
1198 //Initialize static members;
1199 Bit16u IPX::dospage = 0;
1200
1201 #endif
1202