/* 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. */ #include #include "mpdosock.h" //#include "types.h" typedef unsigned char BYTE; typedef unsigned short WORD; typedef unsigned long DWORD; //#include "lpc.h" typedef struct { short version; // version of LPC requested short sizeOfArgs; // size of arguments short service; // service # requested char Data[1]; // data } LPCData; typedef struct { short version; // LPC version short sizeOfReturn; // return data size short error; // any error codes short noRet; // number of returns char Data[1]; // data } LPCReturn; //#include "services.h" #define MAXSOCKETS 20 // services #define LPC_SOCKBIND 4 #define LPC_SOCKGETHOSTBYNAME 5 #define LPC_SOCKGETHOSTNAME 6 #define LPC_SOCKGETHOSTBYADDR 7 #define LPC_SOCKCLOSE 8 #define LPC_SOCKSOCKET 9 #define LPC_SOCKRECVFROM 10 #define LPC_SOCKSENDTO 11 #define LPC_SOCKIOCTL 12 #define LPC_SOCKGETSOCKNAME 13 #define LPC_SOCKFLUSH 14 #define LPC_SOCKSETOPT 15 #define LPC_SOCKGETLASTERROR 16 #define LPC_SOCKINETADDR 17 // htons, ntohs, htonl, ntohl implemented locally // errors #define LPC_UNRECOGNIZED_SERVICE -1 #define LPC_NOERROR 0 // structures for support typedef struct { SOCKET s; int namelen; char name[1]; } BindArgs; typedef struct { SOCKET s; long cmd; char data[1]; } IoctlArgs; typedef struct { int retVal; int namelen; char name[1]; } GetSockNameRet; typedef GetSockNameRet GetHostNameRet; typedef struct { int retVal; int h_addr_0; // that's the only important value } GetHostByNameRet; typedef struct { int len; int type; char addr[1]; } GetHostByAddrArgs; typedef struct { int retVal; char h_name[1]; // h_name is the only important value } GetHostByAddrRet; typedef struct { SOCKET s; int flags; } RecvFromArgs; typedef struct { int retVal; int errCode; int len; // message len struct sockaddr sockaddr; int sockaddrlen; char Data[1]; } RecvFromRet; typedef struct { SOCKET s; int flags; int len; struct sockaddr sockaddr; int sockaddrlen; char Data[1]; } SendToArgs; typedef struct { int retVal; int errCode; } SendToRet; typedef struct { int bufflen; SOCKET s; int len; int sockaddrlen; struct sockaddr address; char data[1]; } SocketChannelData; typedef struct { int af; int type; int protocol; } SocketArgs; typedef struct { SOCKET s; int len; int flags; int addrlen; struct sockaddr addr; char data[1]; } WinSockData; typedef struct { SOCKET s; int level; int optname; int optlen; char optval[1]; } SetSockOptArgs; typedef struct { SOCKET sock[MAXSOCKETS]; } SocketMap; //#include "rtq.h" #define RTQ_NODE struct rtq_node RTQ_NODE { RTQ_NODE *self; // Ring zero address of this node RTQ_NODE *left; // Ring zero address of preceding node RTQ_NODE *right; // Ring zero address of succeding node BYTE * rtqDatum; // Ring 3 Datum of Buffer (start of preface) BYTE * rtqInsert; // Ring 3 insertion position WORD rtqLen; // Length of buffer, excluding preface WORD rtqUpCtr; // Up Counter of bytes used so far WORD rtqQCtr; // number of nodes attached WORD padding; // DWORD alignment }; #define RTQ_PARAM_MOVENODE struct rtq_param_movenode RTQ_PARAM_MOVENODE { WORD rtqFromDQ; WORD rtqToDQ; }; RTQ_NODE* rtq_fetch(RTQ_NODE*, RTQ_NODE*); // To, From //#include "mplib.h" // give up time slice void Yield(void); void MGenWakeupDll(void); // post a message to win32 side void PostWindowsMessage(void); // get # of items on qNo int MGenGetQueueCtr(int qNo); // move first node from qFrom to qTo RTQ_NODE *MGenMoveTo(int qFrom, int qTo); // get first node from q RTQ_NODE *MGenGetNode(int q); // get master node, returning size of RTQ_NODE for size verification RTQ_NODE *MGenGetMasterNode(unsigned *size); // move all nodes from qFrom to qTo RTQ_NODE *MGenFlushNodes(int qFrom, int qTo); // count number of nodes in queues designated by bitmask // lowerOrderBits == 0..31, upperOrderBits == 32-63 int MGenMCount(unsigned lowerOrderBits, unsigned upperOrderBits); // perform consistency check on chunnel address space int MGenSanityCheck(void); #include #include extern short flat_selector; #define SOCKET_MAP_QUEUE 41 #define IDLE_QUEUE 44 #define REC_QUEUE 45 #define SEND_QUEUE 46 // queue sizes #define FREEQBASE 58 #define FREEQ64 58 #define FREEQ128 59 #define FREEQ256 60 #define FREEQ512 61 #define FREEQ1024 62 #define FREEQ2048 63 #define NFREEQ 6 #define QLIMIT 10 #define PRIVATEQ 50 #define FARPKL(x) (_farnspeekl((unsigned long) x)) #define FARPKB(x) (_farnspeekb((unsigned long) x)) #define FARPKS(x) (_farnspeekw((unsigned long) x)) #define FARPOKL(x, y) (_farnspokel((unsigned long) x, (unsigned long) y)) #define FARPOKB(x, y) (_farnspokeb((unsigned long) x, (unsigned char) y)) int Qsizes[] = { 64, 128, 256, 512, 1024, 2048 }; int SocketError = 0; SocketMap *SockMap; #define HOSTENT_ALIAS_LIMIT 5 #define HOSTENT_STRLEN_LIMIT 50 #define HOSTENT_ADDR_LIST_LIMIT 5 struct hostent HostEnt; char HostEnt_hname[HOSTENT_STRLEN_LIMIT]; char *HostEnt_h_aliases[HOSTENT_ALIAS_LIMIT]; char HostEnt_names[HOSTENT_ALIAS_LIMIT][HOSTENT_STRLEN_LIMIT]; struct in_addr* HostEnt_addr_list[HOSTENT_ADDR_LIST_LIMIT]; struct in_addr HostEnt_addrs[HOSTENT_ADDR_LIST_LIMIT]; void fmemcpyto(void *to, const void *from, int length) { movedata(_my_ds(), (unsigned)from, flat_selector, (unsigned)to, length); } void fmemcpyfrom(void *to, const void *from, int length) { movedata(flat_selector, (unsigned)from, _my_ds(), (unsigned)to, length); } void fstrcpyto(char *to, const char *from) { while (*from) { FARPOKB(to, *from); to++; from++; } FARPOKB(to, 0); } void fstrncpyto(char *to, const char *from, int len) { while (*from && len) { FARPOKB(to, *from); to++; from++; len--; } FARPOKB(to, 0); } void fstrcpyfrom(char *to, const char *from) { while (FARPKB(from)) { *to = FARPKB(from); from++; to++; } *to = 0; } void fstrncpyfrom(char *to, const char *from, int len) { while (FARPKB(from) && len) { *to = FARPKB(from); from++; to++; len--; } *to = 0; } void GetSocketMap(void) { RTQ_NODE *n = MGenGetNode(SOCKET_MAP_QUEUE); SockMap = (SocketMap *) FARPKL(&n->rtqDatum); } void * GetFreeBufferToQueue(int q, int bufSize) { int i; for (i = 0; i < NFREEQ; i++) { if (Qsizes[i] >= bufSize && MGenGetQueueCtr(i+FREEQBASE)) { RTQ_NODE *n = MGenMoveTo(i+FREEQBASE, q); if (!n) continue; FARPOKL(&n->rtqUpCtr, bufSize); return (void *) FARPKL(&n->rtqDatum); } } return 0; } void FreeBufferFromQueue(int q) { int i; RTQ_NODE *n = MGenGetNode(q); for (i = 0; i < NFREEQ; i++) { if (Qsizes[i] == FARPKS(&n->rtqLen)) { MGenMoveTo(q, i+FREEQBASE); return; } } } void SetLPCData(LPCData *lpc) { FARPOKL(&(lpc->version), 1); FARPOKL(&(lpc->sizeOfArgs), 0); FARPOKL(&(lpc->service), 0); } int bind(SOCKET s, const struct sockaddr *name, int namelen) { RTQ_NODE *n = MGenGetNode(IDLE_QUEUE); LPCData *p; LPCReturn *r; BindArgs *bargs; int retVal; _farsetsel(flat_selector); SocketError = 0; p = (LPCData *) FARPKL(&n->rtqDatum); SetLPCData(p); FARPOKL(&p->service, LPC_SOCKBIND); bargs = (BindArgs *) p->Data; FARPOKL(&bargs->s, s); FARPOKL(&bargs->namelen, namelen); fmemcpyto(bargs->name, name, namelen); MGenMoveTo(IDLE_QUEUE, SEND_QUEUE); PostWindowsMessage(); while ((n = MGenGetNode(REC_QUEUE)) == 0) Yield(); r = (LPCReturn *) FARPKL(&n->rtqDatum); if (FARPKS(&r->error) != LPC_NOERROR) { return -1; } retVal = FARPKL(r->Data); // get ready for next call MGenMoveTo(REC_QUEUE, IDLE_QUEUE); return retVal; } int closesocket(SOCKET s) { RTQ_NODE *n = MGenGetNode(IDLE_QUEUE); LPCData *p; LPCReturn *r; int retVal; _farsetsel(flat_selector); SocketError = 0; p = (LPCData *) FARPKL(&n->rtqDatum); SetLPCData(p); FARPOKL(&p->service, LPC_SOCKCLOSE); FARPOKL(p->Data, s); MGenMoveTo(IDLE_QUEUE, SEND_QUEUE); PostWindowsMessage(); while ((n = MGenGetNode(REC_QUEUE)) == 0) Yield(); r = (LPCReturn *) FARPKL(&n->rtqDatum); if (FARPKS(&r->error) != LPC_NOERROR) { return -1; } retVal = FARPKL(r->Data); // get ready for next call MGenMoveTo(REC_QUEUE, IDLE_QUEUE); return retVal; } void ZapHostEnt() { // do nothing } void ReconstructHostEnt(struct hostent *s, void *flattened) { struct hostent *old = (struct hostent *) flattened; int i; char **ptr; s->h_name = HostEnt_hname; fstrncpyfrom(s->h_name, (char *) FARPKL(&old->h_name), HOSTENT_STRLEN_LIMIT-1); s->h_name[HOSTENT_STRLEN_LIMIT-1] = 0; s->h_aliases = HostEnt_h_aliases; ptr = (char **) FARPKL(&old->h_aliases); for (i = 0; i < (HOSTENT_ALIAS_LIMIT-1) && FARPKL(ptr); i++, ptr++) { s->h_aliases[i] = HostEnt_names[i]; // fstrncpyfrom(s->h_aliases[i], (void *) FARPKL(ptr), HOSTENT_STRLEN_LIMIT-1); s->h_aliases[i][HOSTENT_STRLEN_LIMIT-1] = 0; } s->h_aliases[i] = 0; s->h_addrtype = FARPKS(&old->h_addrtype); s->h_length = FARPKS(&old->h_length); if (FARPKS(&old->h_length) != sizeof(struct in_addr)) { printf("Error!\n"); exit(0); } s->h_addr_list = (char **) HostEnt_addr_list; ptr = (char **) FARPKL(&old->h_addr_list); for (i = 0; i < (HOSTENT_ADDR_LIST_LIMIT - 1) && FARPKL(ptr); i++, ptr++) { s->h_addr_list[i] = (char *) &(HostEnt_addrs[i]); fmemcpyfrom(s->h_addr_list[i], (void *) FARPKL(ptr), s->h_length); } s->h_addr_list[i] = 0; } int getsockname(SOCKET s, struct sockaddr *name, int *namelen) { RTQ_NODE *n = MGenGetNode(IDLE_QUEUE); LPCData *p; LPCReturn *r; GetSockNameRet *ret; int retVal; SocketError = 0; _farsetsel(flat_selector); p = (LPCData *) FARPKL(&n->rtqDatum); SetLPCData(p); FARPOKL(&p->service, LPC_SOCKGETSOCKNAME); FARPOKL(p->Data, s); MGenMoveTo(IDLE_QUEUE, SEND_QUEUE); PostWindowsMessage(); while ((n = MGenGetNode(REC_QUEUE)) == 0) Yield(); r = (LPCReturn *) FARPKL(&n->rtqDatum); if (FARPKS(&r->error) != LPC_NOERROR) { return -1; } ret = (GetSockNameRet *) r->Data; retVal = FARPKL(&ret->retVal); fmemcpyfrom(name, ret->name, FARPKL(&ret->namelen)); *namelen = FARPKL(&ret->namelen); // get ready for next call MGenMoveTo(REC_QUEUE, IDLE_QUEUE); return retVal; } int gethostname(char *name, int namelen) { RTQ_NODE *n; LPCData *p; LPCReturn *r; GetHostNameRet *ret; int retVal; char *s; _farsetsel(flat_selector); SocketError = 0; n = (RTQ_NODE *) MGenGetNode(IDLE_QUEUE); p = (LPCData *) FARPKL(&n->rtqDatum); SetLPCData(p); FARPOKL(&p->service,LPC_SOCKGETHOSTNAME); MGenMoveTo(IDLE_QUEUE, SEND_QUEUE); PostWindowsMessage(); while ((n = (RTQ_NODE *) (MGenGetNode(REC_QUEUE))) == 0) Yield(); r = (LPCReturn *) FARPKL(&n->rtqDatum); if (FARPKS(&r->error) != LPC_NOERROR) { return -1; } ret = (GetHostNameRet *) r->Data; retVal = FARPKL(&ret->retVal); s = ret->name; fstrncpyfrom(name, s, namelen); #if 0 len = strlen(ret->name); if (len > namelen) memcpy(name, ret->name, ret->namelen); else strcpy(name, ret->name); #endif // get ready for next call MGenMoveTo(REC_QUEUE, IDLE_QUEUE); return retVal; } struct hostent * gethostbyname(const char *name) { RTQ_NODE *n = MGenGetNode(IDLE_QUEUE); LPCData *p; LPCReturn *r; struct hostent *retVal; _farsetsel(flat_selector); SocketError = 0; p = (LPCData *) FARPKL(&n->rtqDatum); SetLPCData(p); FARPOKL(&p->service, LPC_SOCKGETHOSTBYNAME); fstrcpyto(p->Data, name); MGenMoveTo(IDLE_QUEUE, SEND_QUEUE); PostWindowsMessage(); while ((n = MGenGetNode(REC_QUEUE)) == 0) Yield(); r = (LPCReturn *) FARPKL(&n->rtqDatum); retVal = (struct hostent *) r->Data; if (FARPKL(&retVal->h_name) == 0) { retVal = 0; } else { ZapHostEnt(); ReconstructHostEnt(&HostEnt, (void *) retVal); retVal = &HostEnt; } // get ready for next call MGenMoveTo(REC_QUEUE, IDLE_QUEUE); return retVal; } struct hostent * gethostbyaddr(const char *addr, int len, int type) { RTQ_NODE *n = MGenGetNode(IDLE_QUEUE); LPCData *p; LPCReturn *r; GetHostByAddrArgs *args; struct hostent *retVal; SocketError = 0; _farsetsel(flat_selector); p = (LPCData *) FARPKL(&n->rtqDatum); SetLPCData(p); FARPOKL(&p->service, LPC_SOCKGETHOSTBYADDR); args = (GetHostByAddrArgs *) p->Data; FARPOKL(&args->len, len); FARPOKL(&args->type, type); fmemcpyto(args->addr, addr, len); MGenMoveTo(IDLE_QUEUE, SEND_QUEUE); PostWindowsMessage(); while ((n = MGenGetNode(REC_QUEUE)) == 0) Yield(); r = (LPCReturn *) FARPKL(&n->rtqDatum); retVal = (struct hostent *) r->Data; if (FARPKL(&retVal->h_name) == 0) { retVal = 0; } else { ZapHostEnt(); ReconstructHostEnt(&HostEnt, (void *) retVal); retVal = &HostEnt; } // get ready for next call MGenMoveTo(REC_QUEUE, IDLE_QUEUE); return retVal; } SOCKET socket(int af, int type, int protocol) { RTQ_NODE *n = MGenGetNode(IDLE_QUEUE); LPCData *p; LPCReturn *r; SocketArgs *args; int retVal; _farsetsel(flat_selector); SocketError = 0; p = (LPCData *) FARPKL(&n->rtqDatum); SetLPCData(p); FARPOKL(&p->service, LPC_SOCKSOCKET); args = (SocketArgs *) p->Data; FARPOKL(&args->af, af); FARPOKL(&args->type, type); FARPOKL(&args->protocol, protocol); MGenMoveTo(IDLE_QUEUE, SEND_QUEUE); PostWindowsMessage(); while ((n = MGenGetNode(REC_QUEUE)) == 0) Yield(); r = (LPCReturn *) FARPKL(&n->rtqDatum); if (FARPKS(&r->error) != LPC_NOERROR) { return -1; } retVal = FARPKL(r->Data); // get ready for next call MGenMoveTo(REC_QUEUE, IDLE_QUEUE); return retVal; } void sockets_flush(void) { RTQ_NODE *n = MGenGetNode(IDLE_QUEUE); LPCData *p; SocketError = 0; p = (LPCData *) FARPKL(&n->rtqDatum); SetLPCData(p); FARPOKL(&p->service, LPC_SOCKFLUSH); MGenMoveTo(IDLE_QUEUE, SEND_QUEUE); PostWindowsMessage(); while ((n = MGenGetNode(REC_QUEUE)) == 0) Yield(); MGenMoveTo(REC_QUEUE, IDLE_QUEUE); } int recvfrom(SOCKET s, char *buf, int len, int flags, struct sockaddr *from, int *fromlen) { int i; RTQ_NODE *n; WinSockData *data; int bytesRead; SocketError = 0; _farsetsel(flat_selector); if (!SockMap) GetSocketMap(); for (i = 0; i < MAXSOCKETS; i++) { if (FARPKL(&(SockMap->sock[i])) == s) break; } if (i == MAXSOCKETS) return SOCKET_ERROR; // pick up node n = MGenGetNode(i); if (n == 0) { SocketError = WSAEWOULDBLOCK; return -1; } data = (WinSockData *) FARPKL(&n->rtqDatum); bytesRead = FARPKL(&data->len); if (from) { fmemcpyfrom(from, &data->addr, sizeof(struct sockaddr)); } if (fromlen) { *fromlen = FARPKL(&data->addrlen); } fmemcpyfrom(buf, data->data, len > bytesRead ? bytesRead : len); if ((flags & MSG_PEEK) == 0) { FreeBufferFromQueue(i); } return bytesRead; } int sendto(SOCKET s, const char *buf, int len, int flags, const struct sockaddr *to, int tolen) { int i; int outQ; WinSockData *data; SocketError = 0; _farsetsel(flat_selector); if (!SockMap) GetSocketMap(); for (i = 0; i < MAXSOCKETS; i++) { if (FARPKL(&SockMap->sock[i]) == s) { break; } } if (i == MAXSOCKETS) { SocketError = WSAENOTSOCK; return SOCKET_ERROR; } outQ = i + MAXSOCKETS; if (MGenGetQueueCtr(outQ) >= QLIMIT) { SocketError = WSAEWOULDBLOCK; return SOCKET_ERROR; } data = GetFreeBufferToQueue(PRIVATEQ, len + sizeof(WinSockData)); if (!data) { SocketError = WSAEWOULDBLOCK; return SOCKET_ERROR; } FARPOKL(&data->s, s); FARPOKL(&data->len, len); if (to) { fmemcpyto(&data->addr, to, tolen); FARPOKL(&data->addrlen, tolen); } else { FARPOKL(&data->addrlen, 0); } FARPOKL(&data->flags, flags); fmemcpyto(data->data, buf, len); MGenMoveTo(PRIVATEQ, outQ); return len; } int ioctlsocket(SOCKET s, long cmd, unsigned long *argp) { RTQ_NODE *n = MGenGetNode(IDLE_QUEUE); LPCData *p; LPCReturn *r; IoctlArgs *args; int retVal; SocketError = 0; _farsetsel(flat_selector); p = (LPCData *) FARPKL(&n->rtqDatum); SetLPCData(p); FARPOKL(&p->service, LPC_SOCKIOCTL); args = (IoctlArgs *) p->Data; FARPOKL(&args->s, s); FARPOKL(&args->cmd, cmd); switch(cmd) { case FIONBIO: FARPOKL(args->data, *argp); break; default: return SOCKET_ERROR; } MGenMoveTo(IDLE_QUEUE, SEND_QUEUE); PostWindowsMessage(); while ((n = MGenGetNode(REC_QUEUE)) == 0) Yield(); r = (LPCReturn *) FARPKL(&n->rtqDatum); retVal = FARPKL(r->Data); // get ready for next call MGenMoveTo(REC_QUEUE, IDLE_QUEUE); return retVal; } int setsockopt(SOCKET s, int level, int optname, const char *optval, int optlen) { RTQ_NODE *n = MGenGetNode(IDLE_QUEUE); LPCData *p; LPCReturn *r; SetSockOptArgs *args; int retVal; SocketError = 0; _farsetsel(flat_selector); p = (LPCData *) FARPKL(&n->rtqDatum); SetLPCData(p); FARPOKL(&p->service, LPC_SOCKSETOPT); args = (SetSockOptArgs *) p->Data; FARPOKL(&args->s, s); FARPOKL(&args->level, level); FARPOKL(&args->optname, optname); FARPOKL(&args->optlen, optlen); fmemcpyto(args->optval, optval, optlen); MGenMoveTo(IDLE_QUEUE, SEND_QUEUE); PostWindowsMessage(); while ((n = MGenGetNode(REC_QUEUE)) == 0) Yield(); r = (LPCReturn *) FARPKL(&n->rtqDatum); retVal = FARPKL(r->Data); // get ready for next call MGenMoveTo(REC_QUEUE, IDLE_QUEUE); return retVal; } int WSAGetLastError(void) { RTQ_NODE *n = MGenGetNode(IDLE_QUEUE); LPCData *p; LPCReturn *r; int retVal; _farsetsel(flat_selector); if (SocketError) { int err = SocketError; SocketError = 0; return err; } p = (LPCData *) FARPKL(&n->rtqDatum); SetLPCData(p); FARPOKL(&p->service, LPC_SOCKGETLASTERROR); MGenMoveTo(IDLE_QUEUE, SEND_QUEUE); PostWindowsMessage(); while ((n = MGenGetNode(REC_QUEUE)) == 0) Yield(); r = (LPCReturn *) FARPKL(&n->rtqDatum); retVal = FARPKL(r->Data); // get ready for next call MGenMoveTo(REC_QUEUE, IDLE_QUEUE); return retVal; } unsigned long inet_addr(const char *cp) { int ret; unsigned int ha1, ha2, ha3, ha4; unsigned long ipaddr; ret = sscanf(cp, "%d.%d.%d.%d", &ha1, &ha2, &ha3, &ha4); if (ret != 4) return -1; ipaddr = (ha1 << 24) | (ha2 << 16) | (ha3 << 8) | ha4; return ipaddr; #if 0 RTQ_NODE *n = MGenGetNode(IDLE_QUEUE); LPCData *p; LPCReturn *r; int retVal; SocketError = 0; _farsetsel(flat_selector); p = (LPCData *) FARPKL(&n->rtqDatum); SetLPCData(p); FARPOKL(&p->service, LPC_SOCKINETADDR); fstrcpyto(p->Data, cp); MGenMoveTo(IDLE_QUEUE, SEND_QUEUE); PostWindowsMessage(); while ((n = MGenGetNode(REC_QUEUE)) == 0) Yield(); r = (LPCReturn *) FARPKL(&n->rtqDatum); if (FARPKS(&r->error) != LPC_NOERROR) { return -1; } retVal = FARPKL(r->Data); // get ready for next call MGenMoveTo(REC_QUEUE, IDLE_QUEUE); return retVal; #endif } char *inet_ntoa (struct in_addr in) { static char buf [32]; sprintf(buf, "%u.%u.%u.%u", in.S_un.S_un_b.s_b1, in.S_un.S_un_b.s_b2, in.S_un.S_un_b.s_b3, in.S_un.S_un_b.s_b4); return buf; }