#include #include #include #include // "Standard" data types not defined by default in certain C environments typedef unsigned char byte; typedef unsigned short word; // Custom InSim specific database types // We define these here, rather than fix the InSim header file, // for simplicity in this example typedef struct NodeLap NodeLap; typedef struct CompCar CompCar; typedef struct { int x; int y; int z; } Vec; typedef struct { float x; float y; float z; } Vector; // insim.h is the actual InSim.txt file renamed - it's actually a C/C++ compatible header file #include "insim.h" // The address of the client or server we're trying to connect to #define IS_ADDR "127.0.0.1" // The port InSim is running on #define IS_PORT 29999 // Any admin password #define IS_ADMIN "" // Your application name #define PRODUCT_FULL "InSim Tutorial" // The maximum buffer size // You don't want to play with this really #define PACKET_BUFFER_SIZE 512 // Number of seconds that a select timeout should occur - you don't want to play with this // unless you know what you are doing #define IS_TIMEOUT 5 // Definition for our global buffer datatype struct buffer_t { // Packet buffer - 512 should be more than enough char buffer[PACKET_BUFFER_SIZE]; // Number of bytes currently in buffer unsigned int bytes; }; int main (int argc, char *argv[]) { // Our global buffer struct buffer_t gbuf; // We need to zero it out memset(gbuf.buffer, 0, PACKET_BUFFER_SIZE); gbuf.bytes = 0; // Our socket file descriptor/handle SOCKET s; struct sockaddr_in saddr; // Initialise WinSock // Only required on Windows WSADATA wsadata; if (WSAStartup(0x202, &wsadata) == SOCKET_ERROR) { WSACleanup(); return WSAGetLastError(); } // Create the socket - this defines the type of socket - in this instance a TCP one s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // Could we get the socket handle? If not the OS might be too busy or have run out of available socket descriptors if (s == INVALID_SOCKET) { // Clean up closesocket(s); // Clean up anything to do with WinSock WSACleanup(); return -1; } // Clear the socket address structure for later memset(&saddr, 0, sizeof(saddr)); // Set the type of socket we're using saddr.sin_family = AF_INET; // We find the address to connect to the InSim client struct hostent *hp; // We first try to use DNS (i.e. localhost would resolve to 127.0.0.1) hp = gethostbyname(IS_ADDR); // We managed to resolve it, lets put the binary representation into the socket address structure // If not, we assume that someone put in an IP - lets convert that if (hp != NULL) saddr.sin_addr.s_addr = *((unsigned long*)hp->h_addr); else saddr.sin_addr.s_addr = inet_addr(IS_ADDR); // Set the port number in the socket structure - we convert it from host byte order, to network // We do this for compatibility reasons // Ideally, we should do this for all numbers that get transmitted over the network // However, in the instance of LFS, this isn't really applicable as LFS only runs natively on // little endian processors // Sorry if this is a little technical, but it's worth pointing out saddr.sin_port = htons(IS_PORT); // Now the socket address structure is full, lets try to connect if (connect(s, (struct sockaddr *) &saddr, sizeof(saddr)) < 0) { fprintf(stderr, "Connection to '%s' failed\n", IS_ADDR); // Clean up closesocket(s); // Clean up anything to do with WinSock WSACleanup(); return -1; } // Ok, so we're connected. First we need to let LFS know we're here by sending the // ISI packet struct IS_ISI isi_p; memset(&isi_p, 0, sizeof(struct IS_ISI)); isi_p.Size = sizeof(struct IS_ISI); isi_p.Type = ISP_ISI; isi_p.UDPPort = 0; isi_p.Flags = 0; isi_p.Interval = 0; isi_p.Prefix = '!'; // InSim.txt stipulates that the product name must end with \0 memcpy(isi_p.IName, PRODUCT_FULL, sizeof(isi_p.IName)-1); memcpy(isi_p.Admin, IS_ADMIN, 16); if (send(s, (const char *)&isi_p, sizeof(struct IS_ISI), 0) < 0) { // Clean up closesocket(s); // Clean up anything to do with WinSock WSACleanup(); return -1; } // And now the version packet struct IS_TINY is_v; memset(&is_v, 0, sizeof(struct IS_TINY)); is_v.Size = sizeof(struct IS_TINY); is_v.Type = ISP_TINY; is_v.SubT = TINY_VER; is_v.ReqI = 1; if (send(s, (const char *)&is_v, sizeof(struct IS_TINY), 0) < 0) { // Clean up closesocket(s); // Clean up anything to do with WinSock WSACleanup(); return -1; } // Now this is where it gets complex - We need to do a lot of shifting twiddling that might be // a little complicated to follow int ok = 1; while (ok > 0) { int rc; // Set the timeout period struct timeval select_timeout; select_timeout.tv_sec = IS_TIMEOUT; select_timeout.tv_usec = 0; // Setup the file descriptor watches fd_set readfd, exceptfd; // Clear them FD_ZERO(&readfd); FD_ZERO(&exceptfd); // Set them to watch our socket for data to read and exceptions that maybe thrown FD_SET(s, &readfd); FD_SET(s, &exceptfd); // Select will watch the sockets specificed in each FD_SET, and trigger after a timeout rc = select(s + 1, &readfd, NULL, &exceptfd, &select_timeout); // Fire any prerecv stuff here that you want wish to do // Select output handling if (rc < 0) { // An error occured ok = -1; break; } if (!FD_ISSET(s, &readfd)) { //No data or exception if (FD_ISSET(s, &exceptfd)) { // An exception occured - we want to quit ok = -1; break; } //No data, we want to redo the loop continue; } // We got data! // make local temp buffer char tbuf[PACKET_BUFFER_SIZE]; memset(&tbuf, 0, PACKET_BUFFER_SIZE); // bytes read unsigned int bread = 0; // copy any half packet into the local buffer if (gbuf.bytes > 0) { bread = gbuf.bytes; memcpy(tbuf, gbuf.buffer, gbuf.bytes); // clear left overs memset(gbuf.buffer, 0, PACKET_BUFFER_SIZE); } // Recieve any waiting bytes int retval = recv(s + 1, tbuf + bread, PACKET_BUFFER_SIZE - (unsigned int)bread, 0); // Deal with the results if (retval == 0) { // Connection has been closed at the other end return -1; } if (retval < 0) continue; // This is where it will get very tricky to follow, if you don't understand pointers // The packetisation process is: // read size of packet // check to see if buffer doesnt exceed the limits, and has a packet size. loop until not satisfied // read packet into lua // increase number of bytes read // check remaining sizes and // copy remaining into global buffer and set number of bytes char *p = tbuf; bread = 0; // packet size unsigned char psize; unsigned int tbytes = gbuf.bytes + retval; gbuf.bytes = 0; while ((PACKET_BUFFER_SIZE >= (tbytes - bread - (unsigned char)*p)) && ((tbytes - bread - (unsigned char)*p) >= 0) && ((unsigned char)*p > 0)) { psize = (unsigned char)*p; // Shouldn't be possible // If we get this then there's been a cock up somewhere if (psize == 0) { fprintf(stderr, "Error: Packet came across with a size of 0\n"); ok = -1; break; } unsigned int packet_id = *((char *)p+1); // Deal with your packets here, based on their ID (or type as InSim calls them) switch (packet_id) { case ISP_TINY: { // It's an ISP_TINY! struct IS_TINY *t = (struct IS_TINY *)p; if ((t->ReqI == 0) && (t->SubT == TINY_NONE)) { // It's a keepalive packet! We need to respond printf("Ping? "); struct IS_TINY keepalive_response; memset(&keepalive_response, 0, sizeof(struct IS_TINY)); keepalive_response.Size = sizeof(struct IS_TINY); keepalive_response.Type = ISP_TINY; // Send it back if (send(s, (const char *)&keepalive_response, sizeof(struct IS_TINY), 0) < 0) ok = -1; printf("Pong!\n"); } break; } case ISP_MSO: { // It's a MeSsage Out from LFS! struct IS_MSO *t = (struct IS_MSO *)p; // Strip the user rubbish from the message char *textstart = t->Msg; // Should check that t->TextStart doesn't exceed 127 // to be safe, haven't actually done that here though textstart += ((unsigned char)t->TextStart); // Calculate the actual maximum length of the string now unsigned char textlen = sizeof(t->Msg) - (unsigned char)t->TextStart; // Does it say !quit // If so, we close the connection if (strncmp(textstart, "!quit", textlen) == 0) ok = -2; // Print it to the screen! // Should really do some sanity checking here printf("MeSsageOut: '%s'\n", textstart); break; } } // Increment the pointer and the number of bytes we've read p += psize; bread += psize; } // How many bytes have we left? int tout = (unsigned char)(tbytes - bread); // if it's less than 1 there's been a cock up - // This SHOULD NOT be possible unless the TCP stream got // screwed about with if (tout < 0) { ok = -1; break; } // This should not really be possible either if (tout > PACKET_BUFFER_SIZE) { ok = -1; break; } // Set the number of bytes in the global buffer gbuf.bytes = (unsigned int)(tout); // Copy the bytes from the local buffer into the global one // Note: We do not zero it first for a slight speed increase on very busy // servers and because we know how many true bytes we should have because we set it previously memcpy(gbuf.buffer, tbuf+bread, gbuf.bytes); } // We were asked to quit, so the connection is still present // We need to tell LFS we're leaving if (ok == -2) { struct IS_TINY is_close; memset(&is_close, 0, sizeof(struct IS_TINY)); is_close.Size = sizeof(struct IS_TINY); is_close.Type = ISP_TINY; is_close.ReqI = 0; is_close.SubT = 2; send(s, (const char *)&is_close, sizeof(struct IS_TINY), 0); } // Clean up closesocket(s); // Clean up anything to do with WinSock WSACleanup(); return 0; }