PDA

View Full Version : C++ - CInsim - A basic C++ InSim library


MaKaKaZo
29th August 2008, 11:35
Changelog:

V0.6


Added support for *NIX platforms. You only have to change in the insim.h file the "#define CIS_WINDOWS" preprocessor directive to CIS_LINUX, and you are ready to compile the lib under UNIX/Linux with the exact same functionality. (Portability code provided by MadCatX)



The additional function mstrostr() only uses ANSI-C functions now to ensure platform portability. All the occurrences of itoa() have been replaced by sprintf().


V0.51


Fix for the send_button() method. Now it's thread-safe, there was a small typo in one line.



The additional function mstrostr() has been renamed to ms2str() and now has an optional third parameter to indicate if the resulting string will contain hundredths of second (default) or thousandths of second.


V0.5


New method called send_button(), used to send BTN packets of variable size. Up until now button packets were always 240 chars long in the text field, thus producing a big network overhead when a lot of buttons were sent. Now you have to create a button struct and fill it to your desire. The send_button method calculates the text length and only sends the appropiate amount of data. It also refills the 'Size' field of the struct based on the text length so you don't have to worry about that.



Fix for the thread-safe send_packet() method. Actually, it was supposed to have been updated in the V0.4 but it never was because I didn't add a couple of lines... Now it's suppossed to be working fine.

V0.4


Now uses pthreads-w32 for thread-safe send_packet() method (several threads can use safely the send_packet() without overlapping). Before it had to be controlled by the application programmer.

v0.31


Adds all changes done to insim as of LFS S2 0.5Z28

v0.3


First stable version.

I started using InSim with TheAngryAngel C tutorial and built my own C++ library to use InSim. This library is now functional and stable. The library is called CInsim and it's meant for writing simple C++ InSim applications. It was originally written for Windows but UNIX/Linux portability has been added as of v0.6.

Most of the work must be done "manually". Creating and sending packets, multi-threading when needed and other stuff is responsability of the programmer. This library uses InSim as-is, and reading the insim documentation is a must when you have to manage packets.

What the library offers you is the basics of communicating with InSim:


Establishing the connection to InSim (full TCP, full UDP, TCP+UDP), and using all possible parameters accepted by InSim IS_ISI (initialization) packets.
Keeping the connection always alive.
Keeping the packets coming one at a time.
Peek the type of the next packet, so you know if you have to process it or skip it.
Return the contents of the next packet so you can process it (in case it was one of the types you wanted).
Send packets via the main connection (thread safe since v0.5).
Send size trimmed button packets via the main connection (thread safe since v0.51)
Close connection to InSim.
There's also a function to convert miliseconds to a C string (this is an independent function).

You must cast the contents of the packets to a certain InSim packet struct to access the fileds. This is shown in the working examples linked below.


EXAMPLES:

Note1: Some of these examples don't use the latest version of CInsim, so if you are going to work on your own project, be sure to grab the latest version in this thread.

Note2: There examples might use functions that aren't ANSI-C defined, so maybe you won't be able to compile them right away under UNIX/Linux, sorry.

I have posted three examples using this library, so I'll link to them instead of uploading the same files again:

CInSim V0.6:


Event Control: This application is an event manager (it's server oriented). It was originally designed to enforce HARDCORE qualify sessions, in which the use of Shift+P and Shift+S is forbidden, so people can only do realistic pitstops. The current version allows the use of Race Directors, appointed by adding their lfs usernames in a text file, who can do more stuff to manage an event (restarting, ending, auto-reversing top grid drivers, director chat messages, etc.). http://www.lfsforum.net/showthread.php?p=1403034

CInSim V0.3:


LFS Session Timing: This application shows sector times and time differences to the session best when you cross a checkpoint. It uses buttons, independent threads for button timers and some interesting stuff. http://www.lfsforum.net/showthread.php?t=46643



X-Y-Z Positioning: This application shows the coordinates of your car and the name of the closest player to your position. Of course, more than one car must be online for a name to show up! It uses TCP for main connection and UDP for MCI packets.http://www.lfsforum.net/showthread.php?t=47675


The examples use the CInsim library (of course) and already come zipped with all the required files to compile and even an executable if you can't compile them. Also, code::blocks project files are provided for each example.

shaun463
29th August 2008, 11:41
I was thinking of learning C++, so in future this might be handy for me :).

sun
24th November 2008, 19:40
Thank you!, i've been waiting for one of these to come out for along time :x

I remember someone giving me somthing like this, but it might of been something else.. Anyways. Thanks!

Owen.

Heiko1
24th November 2008, 19:44
Good luck sun with these programming language :)

Hope you get it :thumb:

Dygear
27th January 2010, 03:09
What's required to get this to compile, on both Windows and Linux?

RESTRICTIONS:

The insim.h file has been modified from the original InSim.txt provided in the doc folder. Theoretically, you can have buttons with variable length text up to 240 characters, but I had to fix this to a constant value, which I chose to be 112. I don't know how to make this library work with a variable value in that field. If anyone knows please tell me how to do it!

Anyone know how to fix this limitation?

blackbird04217
27th January 2010, 03:31
I don't know why there is any sort of limitation for the button text, never ran into that. Although I didn't use this library I just made my own and it seems to work find sending that variable lengthed packet. If he had to choose a constant value why not choose the maximum?

About windows vs linux;

closesocket(s); in windows becomes close(s); in linux/unix

windows needs WSAStartup() and WSACleanup() calls at the start and end of the networking project where as linux would not.

Using blocking sockets would require a call to fcntl() instead of ioctlsocket(). There are probable a few other things I am noth thinking of. I have never written networking for linux/unix or other operating systems however I have read and remembered those bits of info - somehow...

DarkTimes
27th January 2010, 10:29
Anyone know how to fix this limitation?
It's the length of the text to the nearest multiple of 4.
TEXT_SIZE = ceil(TEXT_LEN / 4.0) * 4;

Dygear
28th January 2010, 18:31
It's the length of the text to the nearest multiple of 4.
TEXT_SIZE = ceil(TEXT_LEN / 4.0) * 4;

So, they are 'packed strings' then, but still use full int's when they are crossing the Ethernet buffer?

morpha
28th January 2010, 18:40
So, they are 'packed strings' then, but still use full int's when they are crossing the Ethernet buffer?

Network traffic is not typed, if anything it's split into octets. One character corresponds to eight octets, or one byte (eight bits), this is always the case even when multi-byte character encodings are used because LFS first converts them to its own "encoding".
The reason it has to be a multiple of 4 is because LFS uses the packet size for validation purposes, at least that's the explanation I came up with.

Long story short: Fill your string with null bytes until its length is a multiple of 4 :tilt:

Edit: And yes, this is important because LFS interprets an additional 0 followed by text (or random data, which is what you would send if the memory is not Zero'd) as Caption/Text separator!

Degats
28th January 2010, 18:52
TEXT_SIZE = ceil(TEXT_LEN / 4.0) * 4;

The limitation as I understand it (I'm OK with C, but not fluent by any means) is that, in the header, you have to define a constant/fixed size for the text string when defining the struct.

What would be the best (or at least a good) way of implementing a variable length struct/packet/whatever?
Should the text field be defined in insim.h at all or is there a way of trimming the size of the struct/packet later when it's sent or is there a better way than either?

morpha
28th January 2010, 19:05
I believe LFS doesn't use a "variable sized struct", the struct simply has a pointer to the button text and the TextSize is used to determine the necessary amount of memory required to hold the string. However, there is no reason why this has to be a multiple of 4 by what information we have, we simply have to assume that this is required by LFS for whatever it does with buttons.

Simple illustration:
LFS receives button (identified by the Type) compare received bytes to packet's Size, wait for more data if it's fragmented TCP data and the size and received byte count don't match, discard otherwise for being invalid. If valid, create struct for the button, allocate memory for the text (of length TextSize) memcpy the socket buffer contents starting at the offset for text to start+len Set text pointer in struct to newly allocated and filled memory.

DarkTimes
28th January 2010, 19:10
The limitation as I understand it (I'm OK with C, but not fluent by any means) is that, in the header, you have to define a constant/fixed size for the text string when defining the struct.

What would be the best (or at least a good) way of implementing a variable length struct/packet/whatever?
Should the text field be defined in insim.h at all or is there a way of trimming the size of the struct/packet later when it's sent or is there a better way than either?
I dunno, I've never really used InSim with C, but I imagine you could just set the Text size to 240 in the struct and then set the correct size of the packet when you send it.

IS_BTN btn;
strcpy(btn.Text, TEXT);
btn.Size = 12 + (ceil(TEXT_LEN / 4.0) * 4);
send(socket, (char*)&btn, btn.Size, 0);
That codes untested, just to demonstrate what I mean.

Stuff
28th January 2010, 19:17
Yeah, pretty sure structs have to be a static size. Which means in InSim for the buttons you'd have to define TEXT_SIZE yourself between 0-240 depending on what your program does. Smaller the better of course.. Once defined, the struct works as usual. No problems I can see.

But for a variable length struct? Well, that's an array or in C++ a std::vector or a special class (something that returns an array or vector that's the packet) would be better. Just get your text length, fill in the data and ship it :) Seems like it would work..

Degats
28th January 2010, 19:24
The multiple of 4 is stated in insim.h and is presumably either because of some networking limitation or just that 4 bytes conveniently fits in 32bits of memory.

I had forgotten that in the original insim.h, the text portion of the button packets is commented out. Presumably then, one way to create the packet would be to extend the struct or append the text somehow before sending? (calloc()/malloc()/realloc() ? )

ed: DarkTimes and Stuff posted while I was writing the above - I'll have a play in a day or two when I get the time :)

Dygear
28th January 2010, 21:40
(calloc()/malloc()/realloc() ? )

This is why I find C, and C++ much to difficult for new programmers and why I'm trying to make a simple interface for writing powerful InSim applications. I can't really think of anything you won't be able to do with PAWN that you can't do with C, and C++ once people start using it.

I'm trying to make C application based off this code that will allow me to write PAWN plugins for the InSim system. It C application will be the 'glue' between the plugins and the LFS TCP/UDP/IP interface. I think this would be fantastically powerful once implemented, and easy for new people to get into programming. PAWN has a proven track record with the likes of AMX Mod X (http://www.amxmodx.org/) as being easy to program for, and fast enough to execute within each frame of a FPS.

blackbird04217
28th January 2010, 21:51
You don't need to allocate any memory to pull this off. Simply have it defined as text[240] inside the Insim packet; however when sending it get the size of your string and pad it until it is 4 bytes. The struct IS FIXED size, but what you SEND to LFS is variable sized; the reason for this is likely to save on bandwidth because a lot of InSim applications have lots of buttons going back and forth.

MaKaKaZo
1st February 2010, 13:18
First of all, sorry for leaving this thread totally unassisted. I haven't been programming anything at all in the last year. Totally by chance I ended here and I find a discussion going on in the last days.

The thing about defining the text size in buttons struct is what has already been said. That line is commented in the original insim.txt file and there's an explanation that it can have a variable size up to 240 bytes, thus I didn't understand how a struct could have a member with a variable size. So, I chose 112 in my lib, but it can be changed of course to 240. I always send packets of the same size, or at least that's what I think :D

I'm not a good programmer so I didn't understand very well how to overcome this so I could send packets with variable text lengths, saving bandwith when the text is short.

What blackbird says makes sense. I don't know right now what changes should be done to the code to allow this. I don't think I'll have time nor feel like having a look at it anytime soon. Also I think that there have been newer updates to insim which I'm not sure if they affect this library.

Dygear
2nd February 2010, 18:54
Regardless thanks for the code, MaKaKaZo, it has been a great help.

MaKaKaZo
3rd April 2010, 19:08
I have added a new application called "Qual Control" in the first post, a simple qualifying session manager whose only purpose is to enforce hardcore sessions. I have made a thread for it: http://www.lfsforum.net/showthread.php?p=1403034

MaKaKaZo
21st April 2010, 18:33
Qual Control has now been updated and changed to Event Control.

Also, there's a new version 0.31 of CInsim, which includes all changes made to insim as of LFS S2 0.5Z28. The previous version v0.3 was done in 2008, so it had to be updated :)

MaKaKaZo
24th April 2010, 21:30
I have updated the CInSim library to a new version v0.4. This new version now uses pthreads-w32 to achieve thread-safe sending. Previously the programmer neede to use some kind of mutual exclusion checking so several threads didn't use the send_packet( method at the same time. Now the library does this checking.


Also, Event Control has been updated again with new features and it's the first CInSim application to use the version 0.4 of the library.

PoVo
18th May 2010, 17:40
Hey! I was wondering, if maybe anyone could make an example of how you could use this, to make a button in a server displaying the time and date? Thanks.

MaKaKaZo
19th May 2010, 18:52
In case you just want to show the date and time for a brief moment then you just create a button and use the appropiate function to retrieve date and time.

If you want it to be updated so it's continuously showing the time, then you have to create a separate thread to act as a timer. That thread, in a loop, updates the text in the button and then sleeps for 30 or 60 seconds (or less, in case you want to update by minutes, depending on the acuracy you want), or 1 second (if you want to update each second).

The thread repeats that in a loop until it receives some kind of signal to stop and finish its execution (to avoid an endless loop). I guess using a global variable would be the way I would do it, but probably the pthreads library adds some kind of mechanism to communicate threads. You can use the pthreads library that I have been using in my examples. You can grab the "Event Control" example application (first post) and have a look at the code. I use threads there for timers, to erase buttons after a certain amount of time.

Anyway, I'm not a good programmer and I'm not thinking this very thoroughly...

PoVo
21st May 2010, 16:11
Yes, i know the timer bit, i just don't know where to start, e.g How to make the button itself? In C# I get predictive functions when i type something, but in C++ i don't :(

blackbird04217
21st May 2010, 16:42
Look in the header files, do some searching. I mean, sure there are tools out there for the auto typing "lazy" behavior. This is my opinion, though I don't look down on people using this stuff, I do disguise it because it makes too many people rely on the feature. Point proven here. Just start with the main .h files, and see what gets included.

Good luck, and I can't really help with the project specific stuff because I don't use it. So sorry I can't give a direct header file to look at, but you must be including it somewhere, so start there.

EDIT: Forgot to mention, the first place to look (which I hope you have), IF AVAILABLE, is some form of ReadMe, or other documentation that may come with the library, or code. Again I don't know the specifics of this library.

MaKaKaZo
21st May 2010, 18:35
PoVo, to use this library I think you need to have a more proactive attitude towards programming.

First of all read the insim.h file, which contains the structs I use. It's the original insim.txt file in the "...\LFS\docs" folder converted to a header file. There you will find explanations on all packets and their fields, including buttons of course.

Then go to any of my examples and look in the main.cpp for code where button packets are created. That is, look for "ISP_BTN". If you don't understand what is going on there then please don't ask again about it, because all I'm using are functions included in standard C/C++ libraries and basic C/C++ features, and you should be able to understand all of that by yourself (or be able to look it up in any C++ reference manual yourself).

MaKaKaZo
5th August 2010, 10:36
CInSim V0.5:

I updated the library with a bugfix and an important addition.

First of all, I thought I had made it so the send_packet() method was thread safe, but I forgot to add the most important lines. Basically I didn't lock/unlock the mutex var. I', sure I had done that, so I guess it was just a matter of distraction when it came to build the zip file of a work in progress...

Second, and most important, I finally added a function that sends variable sized buttons. Up until now you had to create a button with a 240 chars 'Text' field and it was sent completely using the send_packet() method, even if you didn't even fill the Text field with any text at all.

I recenlty came up with problems derived of network overhead so I decided to give it a go again. Turned out to be much simpler than I had anticipated. I guess I was ofuscated the other times I tried to sort this out...

The new function is called send_button() and must be used only to send buttons. The process is the same as before: you create an IS_BTN struct, and fill it with all the necessary data, and optionally you fill the Text field. Then you call the send_button() method just like you used send_packet() before. The new method calculates the text length, refills the Size field with the appropiate value -so you don't have to worry about knowing how long is your text beforehand- and sends only the necessary amount of data.

The IS_BTN that you create is always 240 chars long in the text field, but only the right amount of data is sent. This may mean an overhead for the insim regarding the amount of allocated memory used, but now there's no network overhead, which is more important. Anyway, the mentioned allocated memory overhead isn't very important because when you send many buttons yo usually reutilize the same IS_BTN struct time and time again, you don't create a new struct for each button :)

Soon I will be working on anti-flooding techniques of some sort to control that massive sending of buttons doesn't cause losing the TCP connection.

Dygear
7th August 2010, 03:01
Soon I will be working on anti-flooding techniques of some sort to control that massive sending of buttons doesn't cause losing the TCP connection.

When you do the test, it might be a good idea to have WireShark (http://www.wireshark.org/) running so you can read raw TCP & UDP Packets, and get a better idea on what's sent and received by LFS and your clients.

PoVo
1st September 2010, 16:03
So I finally got everything working, and so far I'm enjoying this lib.

Just ran into a problem. I'm trying to make a players variable (int Distance) which holds the total driven distance in Metres.

So I took a bit of code from the C# LFS_External InSim app, to help me and this is what I got.

ginfo->players[i].Distance += (int)(((pack_mci->Info[j].Speed / 32768) * 100) / 2);

The problem is, this code is always 0. I don't exactly know if "+=" is the correct way of adding in C++, but the maths formulae should work.

Any Ideas?

Stuff
1st September 2010, 17:59
Yeah, += is good in C++. No problems there.. But I do see a problem with the method of distance calculation. In the MCI packet, speed is meters per second.. Well, in order to get the distance you have to know how much time has passed. Which is what that 2 is I guess, 1 packet every 500ms? On top of that, the speed is variable between the 2 packets. Right? (I didn't work anything out, just theory :))

For the 0, you have make sure its not integer division, which I think that is as speed is a word and the rest of it is whole numbers. I would copy speed into a float then do the math.. Also, check out C++ casts instead of plain C ones (http://stackoverflow.com/questions/103512/in-c-why-use-static-castintx-instead-of-intx)..

Instead of speed, another distance solution might be to use the X,Y,Z point and calculate the distance between the current and the last packet using something like:

xd = x2-x1
yd = y2-y1
zd = z2-z1
Distance = SquareRoot(xd*xd + yd*yd + zd*zd)
However this will also be an approximation as it will be a straight line between the points and not a curve like the driver may have taken. On a hairpin for example. In either case, the more points the better.

One final thing that might work is messing with the NLP packets. I'm not sure how the nodes work but if you know where they are and which node the driver is currently at, then you could get the distance between the nodes.

Hope this helps! :)

PoVo
1st September 2010, 18:08
Well the MCI packet is called every 1000 ms, so I actually don't exactly understand the formulae :(

MaKaKaZo
1st September 2010, 18:41
So I finally got everything working, and so far I'm enjoying this lib.

Just ran into a problem. I'm trying to make a players variable (int Distance) which holds the total driven distance in Metres.

So I took a bit of code from the C# LFS_External InSim app, to help me and this is what I got.

ginfo->players[i].Distance += (int)(((pack_mci->Info[j].Speed / 32768) * 100) / 2);The problem is, this code is always 0. I don't exactly know if "+=" is the correct way of adding in C++, but the maths formulae should work.

Any Ideas?

Everything looks fine to me.

You look to be requesting MCI packets at a rate of 0.5 seconds (given the formula you are using). The Speed field in the CompCar struct is a 2-byte integer, and you are casting it to an (int), which should be 4-byte, bigger than the source data.

The operator += works in C++, so that is no problem either. The only way I see that the math would be 0 is in case that Speed is also 0. So I'd check first of all the value of the Speed you are grabbing. Maybe you are adding the speed from a wrong player?

I don't see any problem why this expression would evaluate as 0 any other than "pack_mci->Info[j].Speed" being zero.


EDIT: I hadn't read any of the previous replies... editing!

First of all, from "insim.txt":
word Speed; // speed (32768 = 100 m/s)

If you are requesting MCI packets at 1 second intervals, your math should be:
((pack_mci->Info[j].Speed / 32768) * 100)

But that would throw metres in x100, so unless you are going faster than 100 m/s it would evaluate as 0.

As Stuff said this is integer division you are doing, so to not lose precission you must change order:
((pack_mci->Info[j].Speed * 100) / 32768)

So the final code would be:
ginfo->players[i].Distance += (int)((pack_mci->Info[j].Speed * 100) / 32768) ;

By doing the multiplication first you might run out of range, but you are casting a (2-byte * 100) to a 4-byte, and that should be no problem I think.


Anyway, as it has been pointed out, this is no good way to calculate the amount of metres the car has moved, and the longer the period between MCI packets the lower the accuracy of the calculation, but if you don't need a lot of accuracy it is an approximation very fast to calculate.

PoVo
2nd September 2010, 19:03
So the final code would be:
ginfo->players[i].Distance += (int)((pack_mci->Info[j].Speed * 100) / 32768) ;

Thanks, that worked straight away! :thumb:

So far I actually prefer this lib over LFS_External (C#) for language possibility reasons.

I was able to pick C++ up pretty quickly when I moved from C#, so I'm quite happy about it.

Currently I'm using your Event Control as a base, with CInsim 0.5 :thumb:

MaKaKaZo
2nd September 2010, 21:00
Actually the reason I chose C++ for this is because it's basically the only language I know, but the library is very basic and doesn't use C++ advanced features. In fact in my insim applications I use mostly C constructions like structs instead of classes, etc.


Good things:

- Whenever there is an insim update by Scawen the only thing you have to do to make CInsim work with the new changes is copy the "../LFS/docs/insim.txt" into your project as insim.h and edit the line for the ISP_BTN text field length (remove the comment).

- You can handle everything that insim sends to you and you can send anything that you want to insim, with no limitations.

- Being a "low level" C++ library, it is usually faster than other libs that provide high level functions and methods which in the end result in sentences being unnecessarily repeated.

- Using my basic tutorial applications as a base you can understand very quickly how this lib and insim work together and how to build your own application using a similar approach.


Bad things:

- Programming with this library is quite... "manual" not like other libs that provide high level functions. You have to build the whole packets with all the fields, but this is also maybe the fastest way when executing when it comes to reusing packets.

- If you need things like timers (for example to clear a button after X seconds) you have to get your hands on third party libraries to create and handle child threads. I use pthreads-w32 and it works pretty fine. It's used in "Event Control", which code is available so you can also have a look on how that works.

- My tutorial applications are not a good example of clean and neat code style. I pretty much do my whole applications all in only one .cpp file for the main and all the functions which handle all packets used in the application. Anyway, I'm not trying to teach people how to write programs with good style, but rather how to write insim apps using this lib!

MaKaKaZo
2nd September 2010, 21:21
Small update!

Changelog:

V0.51


Fix for the send_button() method. Now it's thread-safe, there was a small typo in one line.



The additional function mstrostr() has been renamed to ms2str() and now has an optional third parameter to indicate if the resulting string will contain hundredths of second (default) or thousandths of second.

PoVo
3rd September 2010, 19:58
Hey, just another problem, what's the best way of displaying a Double variable on a button?

I need something like itoa();

Didn't really want to make a new thread, so I posted here :)

MaKaKaZo
3rd September 2010, 20:17
Hey, just another problem, what's the best way of displaying a Double variable on a button?

I need something like itoa();

Didn't really want to make a new thread, so I posted here :)

Use the powerful "sprintf()" with the %f specifier:
http://www.cplusplus.com/reference/clibrary/cstdio/sprintf/

something like:

sprintf(pack_btn.Text, "Fuel burned: %f litres", ginfo->players[i].Fuel);
Would turn into: "Fuel burned: 123.23 litres"

See the example in the reference link above. You can add more text to the formatted.

sprintf is very useful to build preformatted C strings containing any kind of data: integers, C strings, floats, etc.

PoVo
3rd September 2010, 21:16
Great! I tried it before, but never knew that I had to use the %.02f things.

I finally set up my KM display using %.03f, and it's very nice. At the moment I'm trying to set up a MySQL database, which is going well :thumb:

MaKaKaZo
4th September 2010, 17:45
New update!

Changelog:

V0.6



Added support for *NIX platforms. You only have to change in the insim.h file the "#define CIS_WINDOWS" preprocessor directive to CIS_LINUX, and you are ready to compile the lib under Linux/UNIX with the exact same functionality. (Portability code provided by MadCatX)



The additional function mstrostr() only uses ANSI-C functions now to ensure platform portability. All the occurrences of itoa() have been replaced by sprintf().

broken
7th September 2010, 20:00
Ok, I have to be a complete noob and ask: How to get this to compile? It seems like pthread is causing me problems, cause it says "sched.h" not found or sth like that.

I visited their site(if that is it: http://sourceware.org/pthreads-win32/ ), but the download directories are htaccess protected with passes.. which I, obviously don't know. I saw that there are executables but, yeah, they are in those same download directories. So I'm kind of stuck.

Here's what I did to try compiling it:
Made an empty project in vc2010;
Added all the files to it, that I thought are neccessary;
That didn't work, so I removed all the files;
I then added all the files from the archive;
Got the same error;
Started browsing the net and chatting;
A day later, here I am, posting in this thread, after another few failed attempts.

I made so many empty projects to try different stuff, that I started giving them some stupid names like "damnit", "whattheduck", uncensored version of whattheduck too, "deletemeeeee" and the likes.

And please, don't judge my terminology, I know that it completely sucks. :D

MaKaKaZo
7th September 2010, 20:34
Ok, I have to be a complete noob and ask: How to get this to compile? It seems like pthread is causing me problems, cause it says "sched.h" not found or sth like that.

I visited their site(if that is it: http://sourceware.org/pthreads-win32/ ), but the download directories are htaccess protected with passes.. which I, obviously don't know. I saw that there are executables but, yeah, they are in those same download directories. So I'm kind of stuck.

Here's what I did to try compiling it:
Made an empty project in vc2010;
Added all the files to it, that I thought are neccessary;
That didn't work, so I removed all the files;
I then added all the files from the archive;
Got the same error;
Started browsing the net and chatting;
A day later, here I am, posting in this thread, after another few failed attempts.

I made so many empty projects to try different stuff, that I started giving them some stupid names like "damnit", "whattheduck", uncensored version of whattheduck too, "deletemeeeee" and the likes.

And please, don't judge my terminology, I know that it completely sucks. :D
Yes, it looks like I haven't been providing all the necessary files from pthreads-w32 to compile because I have them in my global includes path.

Anyway, you can download all the pre-built include and lib files from their sourceware ftp:
ftp://sourceware.org/pub/pthreads-win32/dll-latest/

You can also grab one of the compressed files which have everything, but the directory structure in those is kinda messed up.

I think you need basically the header files from the include directory (sched.h and maybe semaphore.h). The library files needed are both provided with the library (the ".a" and the ".dll").

If you have any further problem I'll try to help you, but I don't know about compiling with visual studio, I have never used it.

You have to add the CInSim files to the project, also link to the pthreads lib and, if required, to the winsocks2 lib. Then you will also have your own project files and headers, and you must have the pthreads DLL available in either a system DLL path or directly the working directory of your application executable.

You can download and try "Event Control" to check all the files needed (though the header files from pthread mentioned before are missing).

PoVo
23rd December 2010, 14:17
Hey, I have gotten this to _kinda_ compile on Visual Studio. Only problem is I'm getting these errors:

1>------ Build started: Project: main, Configuration: Debug Win32 ------
1>CInsim.obj : error LNK2019: unresolved external symbol __imp__bind@12 referenced in function "public: int __thiscall CInsim::init(char *,unsigned short,char *,char *,struct IS_VER *,unsigned char,unsigned short,unsigned short,unsigned short)" (?init@CInsim@@QAEHPADG00PAUIS_VER@@EGGG@Z)
1>CInsim.obj : error LNK2019: unresolved external symbol __imp__connect@12 referenced in function "public: int __thiscall CInsim::init(char *,unsigned short,char *,char *,struct IS_VER *,unsigned char,unsigned short,unsigned short,unsigned short)" (?init@CInsim@@QAEHPADG00PAUIS_VER@@EGGG@Z)
1>CInsim.obj : error LNK2019: unresolved external symbol __imp__htons@4 referenced in function "public: int __thiscall CInsim::init(char *,unsigned short,char *,char *,struct IS_VER *,unsigned char,unsigned short,unsigned short,unsigned short)" (?init@CInsim@@QAEHPADG00PAUIS_VER@@EGGG@Z)
1>CInsim.obj : error LNK2019: unresolved external symbol __imp__inet_addr@4 referenced in function "public: int __thiscall CInsim::init(char *,unsigned short,char *,char *,struct IS_VER *,unsigned char,unsigned short,unsigned short,unsigned short)" (?init@CInsim@@QAEHPADG00PAUIS_VER@@EGGG@Z)
1>CInsim.obj : error LNK2019: unresolved external symbol __imp__gethostbyname@4 referenced in function "public: int __thiscall CInsim::init(char *,unsigned short,char *,char *,struct IS_VER *,unsigned char,unsigned short,unsigned short,unsigned short)" (?init@CInsim@@QAEHPADG00PAUIS_VER@@EGGG@Z)
1>CInsim.obj : error LNK2019: unresolved external symbol __imp__closesocket@4 referenced in function "public: int __thiscall CInsim::init(char *,unsigned short,char *,char *,struct IS_VER *,unsigned char,unsigned short,unsigned short,unsigned short)" (?init@CInsim@@QAEHPADG00PAUIS_VER@@EGGG@Z)
1>CInsim.obj : error LNK2019: unresolved external symbol __imp__socket@12 referenced in function "public: int __thiscall CInsim::init(char *,unsigned short,char *,char *,struct IS_VER *,unsigned char,unsigned short,unsigned short,unsigned short)" (?init@CInsim@@QAEHPADG00PAUIS_VER@@EGGG@Z)
1>CInsim.obj : error LNK2019: unresolved external symbol __imp__WSACleanup@0 referenced in function "public: int __thiscall CInsim::init(char *,unsigned short,char *,char *,struct IS_VER *,unsigned char,unsigned short,unsigned short,unsigned short)" (?init@CInsim@@QAEHPADG00PAUIS_VER@@EGGG@Z)
1>CInsim.obj : error LNK2019: unresolved external symbol __imp__WSAStartup@8 referenced in function "public: int __thiscall CInsim::init(char *,unsigned short,char *,char *,struct IS_VER *,unsigned char,unsigned short,unsigned short,unsigned short)" (?init@CInsim@@QAEHPADG00PAUIS_VER@@EGGG@Z)
1>CInsim.obj : error LNK2019: unresolved external symbol __imp__recv@16 referenced in function "public: int __thiscall CInsim::next_packet(void)" (?next_packet@CInsim@@QAEHXZ)
1>CInsim.obj : error LNK2019: unresolved external symbol ___WSAFDIsSet@8 referenced in function "public: int __thiscall CInsim::next_packet(void)" (?next_packet@CInsim@@QAEHXZ)
1>CInsim.obj : error LNK2019: unresolved external symbol __imp__select@20 referenced in function "public: int __thiscall CInsim::next_packet(void)" (?next_packet@CInsim@@QAEHXZ)
1>CInsim.obj : error LNK2019: unresolved external symbol __imp__send@16 referenced in function "public: int __thiscall CInsim::send_packet(void *)" (?send_packet@CInsim@@QAEHPAX@Z)
1>G:\Users\Povilas\Documents\Visual Studio 2010\Projects\main\Debug\main.exe : fatal error LNK1120: 13 unresolved externals
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========


These errors require you to supply additional LIBS to the project in Visual Studio. I was getting more errors than this, but I solved it by adding the "libpthreadGC2.a" to the additional libs.

Seems like there is more missing. Any ideas? Is there any lib file or such for CInSim?

Thanks, Povo.

morpha
23rd December 2010, 14:32
ws2_32.lib (WinSock2)

PoVo
23rd December 2010, 15:20
ws2_32.lib (WinSock2)

Okay, thanks, I'll try it when I get back to my PC.

If it works I will post the source, for anyone else that needs it for VS.

PoVo
28th March 2011, 18:28
Hi, a few months back, I tried to get this library running with Visual Studio.

I got very far, to a point where the program builds, but crashes on start up.

I basically got the Event InSim app created my the OP, and copied it to the Project in VS.

I was unable to find the problem, so I decided to post it here, so that if anyone could have a look, to check what the problem is.

I would really appreciate someone experienced in C++ to look at my project (which can be downloaded from my site HERE (http://lifestyle-cruise.de/MyFailAttempt.zip) (couldn't upload here as it's too big))

Thanks :)

MadCatX
28th March 2011, 19:13
I haven't had a chance to take a better look at the code yet, but I have the feeling the crash might have something to do with the pthread lib. You seem to be using pthreadGC2 library, but (AFAIK) the recommended version for use with MSVS is pthreadVC2.

EDIT: The code itself seems to be OK, I had to make few minor changes to get it compiled with GCC on my Linux box, but nothing that would cause a crash. I could connect to LFS and receive some packets, I didn't test your app any further though.
I did however replace the CInSim I found within your app with mine version, why did you replace all those strcpy with strcpy_s in ms2str function?

PoVo
28th March 2011, 21:40
I haven't had a chance to take a better look at the code yet, but I have the feeling the crash might have something to do with the pthread lib. You seem to be using pthreadGC2 library, but (AFAIK) the recommended version for use with MSVS is pthreadVC2.

EDIT: The code itself seems to be OK, I had to make few minor changes to get it compiled with GCC on my Linux box, but nothing that would cause a crash. I could connect to LFS and receive some packets, I didn't test your app any further though.
I did however replace the CInSim I found within your app with mine version, why did you replace all those strcpy with strcpy_s in ms2str function?

Because VS was giving me errors about "outdated" functions, and it wouldn't let me compile unless I changed it.

It said something like "Outdated function. Consider using 'strcpy_s'."

Would you be able to reupload the ZIP with the new stuff, so I could try it? Thanks

MadCatX
28th March 2011, 22:35
I'm quite sure VS can be overridden to stop bitching about "deprecated" strcpy so you don't have to rewrite the whole ms2str.

Check out this one if it works correctly...

MaKaKaZo
29th March 2011, 10:05
I'm happy to see there's people at least doing something with my code :D

Sorry for not providing too much support on the lib. After the latest update some months ago I practically stopped visiting the forum as I'm on a LFS indefinite hiatus :shrug:

PoVo
29th March 2011, 12:56
I'm quite sure VS can be overridden to stop bitching about "deprecated" strcpy so you don't have to rewrite the whole ms2str.

Check out this one if it works correctly...

Still no progress :(

1>------ Build started: Project: main, Configuration: Debug Win32 ------
1> main.cpp
1>g:\users\povilas\desktop\myfailreloaded\main\main. cpp(25): warning C4627: '#include "pthread.h"': skipped when looking for precompiled header use
1> Add directive to 'StdAfx.h' or rebuild precompiled header
1>g:\users\povilas\desktop\myfailreloaded\main\main. cpp(26): warning C4627: '#include "CInsim.h"': skipped when looking for precompiled header use
1> Add directive to 'StdAfx.h' or rebuild precompiled header
1>g:\users\povilas\desktop\myfailreloaded\main\main. cpp(757): fatal error C1010: unexpected end of file while looking for precompiled header. Did you forget to add '#include "StdAfx.h"' to your source?
1> CInsim.cpp
1>g:\users\povilas\desktop\myfailreloaded\main\cinsi m.cpp(37): warning C4627: '#include "CInsim.h"': skipped when looking for precompiled header use
1> Add directive to 'StdAfx.h' or rebuild precompiled header
1>g:\users\povilas\desktop\myfailreloaded\main\cinsi m.cpp(791): fatal error C1010: unexpected end of file while looking for precompiled header. Did you forget to add '#include "StdAfx.h"' to your source?
1> Generating Code...
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========

MadCatX
29th March 2011, 15:06
Did you try to do just what the compiler suggested, that is including "stdafx.h" to cinsim.h? I did the testing on my Linux box where there is no "stdafx.h"...

@MaKaKaZo: You kidding? CInSim is the best InSim lib out there, simple enough to not do anything weird behind my back, yet fully functional and easy to use.

PoVo
29th March 2011, 16:50
Did you try to do just what the compiler suggested, that is including "stdafx.h" to cinsim.h? I did the testing on my Linux box where there is no "stdafx.h"...

@MaKaKaZo: You kidding? CInSim is the best InSim lib out there, simple enough to not do anything weird behind my back, yet fully functional and easy to use.

It is already added :(

MadCatX
29th March 2011, 16:52
You might want do experiment a bit with it or you might just disable precompiled headers in the project settings, that should get you sorted...

PoVo
29th March 2011, 18:47
Meh, now I can't even get it to build in Code::Blocks...

If I build it, I get a file of 38kb, which crashes.

If I debug it, it runs... :really:

MadCatX
29th March 2011, 19:24
That's kind of weird, do you have any idea where exactly it crashes? You can put few std::cerr's to the code to track it down. BTW, do you use pthreadGC2.lib and not pthreadVC2.lib with Code::Blocks?

PoVo
29th March 2011, 19:25
BTW, do you use pthreadGC2.lib and not pthreadVC2.lib with Code::Blocks?

Yes :razz:

MadCatX
29th March 2011, 19:32
Have you made any substantial changes to the code, or can I use the version you've already uploaded? I'll see if I encounter the same problem as you with Code::Blocks...

PoVo
29th March 2011, 20:08
Have you made any substantial changes to the code, or can I use the version you've already uploaded? I'll see if I encounter the same problem as you with Code::Blocks...

No, I downloaded Event Control from the start of this thread, which includes the Project file :)

MaKaKaZo
30th March 2011, 17:50
Meh, now I can't even get it to build in Code::Blocks...

If I build it, I get a file of 38kb, which crashes.

If I debug it, it runs... :really:

I can help with this! This is a problem that comes from mingw compiler code optimization options. I think there were three levels of code optimization in the compiler options. If you check some of them *sometimes* it will generate an executable that crashes. This has been reported in mingw support several times, it has nothing to do with code::blocks, CInSim or my application. It works when debugging because in debug mode no optimization options are checked. You can just uncheck some of the optimization levels until you get a working executable.

This actually got me crazy some time ago when I did some minor changes to a project and suddenly it started crashing for no reason.

With you VS problem I can't help much. It's pretty clear that it's something to do with the includes, libraries and such. I'm no expert at all at that matter neither I know about VS as I have never used it.

PoVo
30th March 2011, 19:32
I can help with this! This is a problem that comes from mingw compiler code optimization options. I think there were three levels of code optimization in the compiler options. If you check some of them *sometimes* it will generate an executable that crashes. This has been reported in mingw support several times, it has nothing to do with code::blocks, CInSim or my application. It works when debugging because in debug mode no optimization options are checked. You can just uncheck some of the optimization levels until you get a working executable.

This actually got me crazy some time ago when I did some minor changes to a project and suddenly it started crashing for no reason.

With you VS problem I can't help much. It's pretty clear that it's something to do with the includes, libraries and such. I'm no expert at all at that matter neither I know about VS as I have never used it.

I disabled all the optimisations, and still the same problem :schwitz:

MadCatX
30th March 2011, 21:35
I couldn't get the Code::Blocks project to build (MinGW linker refuses to find the pthreads lib and I don't have the nerve to solve it right now), but I got the MSVS project working. There was just one #include "stdafx.h" missing in the main.cpp:)

PoVo
30th March 2011, 21:43
WOW! Nice one! Thanks very much for the great work, you made my day :nod:

To Makakazo: What exactly is the pthread_mutex_lock (&ismutex); and pthread_mutex_unlock (&ismutex); used for? I noticed it between sending buttons, does that mean, I have to use it everytime I send buttons?

MaKaKaZo
31st March 2011, 09:11
WOW! Nice one! Thanks very much for the great work, you made my day :nod:

To Makakazo: What exactly is the pthread_mutex_lock (&ismutex); and pthread_mutex_unlock (&ismutex); used for? I noticed it between sending buttons, does that mean, I have to use it everytime I send buttons?

That is used for mutual exclusion:
http://en.wikipedia.org/wiki/Mutual_exclusion

This is *only* important if you have a multi-threaded application.

As I recall, I used those functions inside the main application code in earlier versions of CInSim, but soon I realized that I could just put that code inside the library code itself, so the programmer doesn't need to control mutual exclusion.

The thing is, if you have an application where multiple threads are created, and those threads use common resources (ie. global variables), then it could happen that those resources might be used by both threads at the same time, resulting in undesirable consequences.

Please tell me which file you are viewing that code in (and CInSim version used) and I'll go into deeper detail of why I used it, but I'm pretty confident that it has to be something related with the main application algorithm, not with the library. I mean, it isn't supposed to be something that has to be used for all CInSim applications!

PoVo
31st March 2011, 17:57
It's from the X-Y-Z Positioning application :)

MadCatX
31st March 2011, 19:14
Mutex in question is handled internally in CInSim 0.6, so you don't need to explicitly lock it in your application. It's needed because there are two threads (one for receiving TCP and another for UDP packets) which both call the send_packet function. Packet sending has to be an atomic operation and the mutex is there to make sure that one thread doesn't get interrupted by the other one when sending a packet.

PoVo
31st March 2011, 20:21
So I don't have to use it while sending buttons? :)

MadCatX
31st March 2011, 20:28
You shouldn't have to, CInSim v0.6 is thread-safe in this respect. If you take a look at "send_packet" or "send_button" in CInsim.cpp, you'll see it's locking a mutex before calling the "send" method.

MaKaKaZo
1st April 2011, 09:07
That's right. X-Y-Z positioning was my first attempt on an insim app using buttons and MCI packets. It's very old and uses an earlier version of CInSim. As Madcatx said, CInSim 0.6 is thread safe already so you don't need to use them.

Actually, aside from what has been already mentioned about UDP and TCP using the same method for sending packets, my main concern was using the same method frmo different threads.

For example, I usually create new threads that act as timers. They are created, wait for several seconds, and then they do something like clear a set of buttons that were created previously (this is the way I create a message that automatically disappears after X seconds). This timer thread has to use the send() method. If you have several threads like this doing different stuff, there's quite a high possibility that at some time two of them will try to use the method at the same time, and the result might lead to a unstable status or even a crash. It actually happened to me before I fixed it.

CInSim 0.6 blocks the critical methods before using them, so that only one thread can use them and calls from other threads are queued until the current one finishes. You don't need to handle that manually anymore.

If you are interested in CInSim maybe you should download the latest demo application which uses the latest CInSim version (I think... :D).

denis-takumi
25th April 2011, 07:45
in insim 5 IS_MTC was modifed and i add this code in my CInsim class


struct IS_MTC // Msg To Connection - hosts only - send to a connection / a player / all
{
byte Size; // 8 + TEXT_SIZE (TEXT_SIZE = 4, 8, 12... 128)
byte Type; // ISP_MTC
byte ReqI; // 0
byte Sound; // sound effect (see Message Sounds below)

byte UCID; // connection's unique id (0 = host / 255 = all)
byte PLID; // player's unique id (if zero, use UCID)
byte Sp2;
byte Sp3;

// char Text[TEXT_SIZE]; // up to 128 characters of text - last byte must be zero
};


function with error protection :tilt:
but a have problems if strlen > 123


/**
* Send a variable sized message to connect
*/
bool CInsim::send_mtc(void* s_mst, char *errmsg)
{
struct IS_MTC *pack_mtc = (struct IS_MTC*)s_mst;

int text_len = strlen(pack_mtc->Msg);
int text2send;

if (text_len == 0)
{
strcpy(errmsg,"strlen == 0");
return false;
}
else if (text_len > 127)
{
strcpy(errmsg,"strlen > 127");
return false;
}

text2send = text_len + 4 - text_len%4; // 4, 8 ,12, ..., 128

pack_mtc->Size = 8 + text2send;

pthread_mutex_lock (&ismutex);
if (send(sock, (const char *)pack_mtc, pack_mtc->Size, 0) < 0)
{
pthread_mutex_unlock (&ismutex);
strcpy(errmsg,"can't send mtc");
return false;
}
pthread_mutex_unlock (&ismutex);
return true;
}

MadCatX
25th April 2011, 12:11
Shouldn't

text2send = text_len + 4 - text_len%4; // 4, 8 ,12, ..., 128


actually be?

byte remd = text_len % 4
if(remd > 0)
text2send = text_len + (4 - remd);
else
text2send = text_len;


Haven't tried it myself, but your code adds 4 to the text2send if it already is multiple of 4.

denis-takumi
25th April 2011, 13:16
Shouldn't

text2send = text_len + 4 - text_len%4; // 4, 8 ,12, ..., 128
actually be?

byte remd = text_len % 4
if(remd > 0)
text2send = text_len + (4 - remd);
else
text2send = text_len;
Haven't tried it myself, but your code adds 4 to the text2send if it already is multiple of 4.

i tried it but lfs said what last byte must be zero, and i used this code
and i wrote what have problems if len > 123 chars (program is down)

MadCatX
25th April 2011, 14:11
Your "send_mtc" function seems to be OK, I tried it now it worked for me just fine. I hardcode the size of the Text array to 128 in insim.h and send the IS_MTC like this (msg is 127 chars long). It appears in LFS all right and my sample program doesn't crash...:shrug:


char err[128];
struct IS_MTC mtc_pack;

memset(&mtc_pack, 0, sizeof(struct IS_MTC));
char* msg = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWX YZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL MNOPQRSTUVWXYZ1234567890123";

strcpy(mtc_pack.Text, msg);

mtc_pack.Size = sizeof(mtc_pack);
mtc_pack.Type = ISP_MTC;
mtc_pack.ReqI = 0;
mtc_pack.Sound = SND_MESSAGE;
mtc_pack.UCID = 255;
mtc_pack.PLID = 0;

insim.send_mtc(&mtc_pack, err);

MaKaKaZo
25th April 2011, 19:33
Why don't you just copy and edit the send_button() function? It's exactly the same case and it works fine.

BTW, there's a mistake in your code because you use the field "Msg" in the IS_MTC, but now it's called "Text".

I'll change the library source code with the new insim changes when I have some time. It's a quite simple task. The good thing is that it will be backwards compatible. We will have a new send_mtc() function to send variable sized mtc packets, but the generic senc_packet() will work as well, causing a little network overhead because in that case packets sent will be the biggest possible (with 128 bytes of text).

MadCatX
25th April 2011, 19:59
text2send = (1+((text_len-1)/4))*4;

has to be replaced with something like

text2send = (1+((text_len-1)/4))*4 + 4;

or with Denis' code to handle a situation when you have say 12 chars long string ending with zero terminator. In this case "strlen" returns 12 but you actually need to send 13 bytes where the 13th byte is '\0'. If you don't do that, LFS gives you "Last byte must be zero" error. (I found that the hard way today:) )

I was also thinking, wouldn't it be better to have just "send_packet" function and overload in accordingly to handle different packet types? If InSim ever gets more packets with variable size, having a different function to send each of them would get rather inconvenient...

denis-takumi
25th April 2011, 20:09
MadCatX
(1+((126-1)/4))*4 + 4 = 132

makakazo was right

MaKaKaZo sorry my fail :tilt:
btw i was copy and edit send_button function
but error protection is good idea

MaKaKaZo
25th April 2011, 22:03
I was also thinking, wouldn't it be better to have just "send_packet" function and overload in accordingly to handle different packet types? If InSim ever gets more packets with variable size, having a different function to send each of them would get rather inconvenient...
I'll think about this, it sounds like a good idea indeed.

denis-takumi
26th April 2011, 06:37
i was:
0. include insim v5
1. change send_mtc
size =len + (4 - len%4) Reason : see below

2. add "swith case" in send_packet to detect ISP_BTN and ISP_MTC and do functions.

3. change return type to bool

4. you can call send_packet(&pack, errmsg) or send_packet(&pack)

PS. i do for me, if MaKaKaZo likes it, he can correct the mistakes and put this code :tilt:

denis-takumi
26th April 2011, 08:17
MaKaKaZo, i was test you method

(((124-1)/4)+1)*4 = 124 but need 128 because lfs said "last byte must be zero"

but 124 + (4 - 124%4) = 128

Dygear
26th April 2011, 08:20
Really the longest string can only be 127 character long, the 128th character must be NULL (0). If you have a string that's 124 characters long, then you have to use the string length of 128. If you have a string that's 123 characters long, then you can use a string length of 124, so long as 124 is equal to NULL (0). Really it's just a nice way to clip the size of a string and keep the packet size down.

MaKaKaZo
26th April 2011, 17:40
I used
(1+((text_len-1)/4))*4
because in the insim documentation it doesn't say anywhere that the "Text" field for the IS_BTN packet must end with a zero:
char Text[240]; // 0 to 240 characters of text
That's why I used that. I understand that in IS_BTN packets you don't need to add a terminating zero byte. In IS_MTC the situation is different as it states that the string must be zero terminated.

denis-takumi
26th April 2011, 18:49
MaKaKaZo, just see my CInsim, may be you like it

DarkTimes
27th April 2011, 06:44
I used
(1+((text_len-1)/4))*4
because in the insim documentation it doesn't say anywhere that the "Text" field for the IS_BTN packet must end with a zero:
char Text[240]; // 0 to 240 characters of text
That's why I used that. I understand that in IS_BTN packets you don't need to add a terminating zero byte. In IS_MTC the situation is different as it states that the string must be zero terminated.
The BTN text sometimes does need a null terminator. If you send a BTN with text exactly 240 characters long (no trailing '\0') then LFS just discards the button with no error message. This was a bug I had to track down recently in InSim.NET.

Edit: It occurs to me now that this may actually be a bug in LFS.

MaKaKaZo
27th April 2011, 09:16
The BTN text does need a null terminator. If you send a BTN with text exactly 240 characters long (no trailing '\0') then LFS just discards the button with no error message. This was a bug I had to track down recently in InSim.NET.

Edit: It occurs to me now that this may actually be a bug in LFS.

I think I never did a hard test on this matter, I just went on with what the doc said. But now I think that I have been sending buttons without a null terminator all the time since I included the send_button() function (aproximately one of every four buttons that contain text). The text on this buttons shows up just fine, so maybe it's a bug that happens with texts 240 chars long.

I can't do a test right now, but i'm pretty sure that a button containing "1234" will send only those four bytes as the Text field (in the latest CInSim version), and they will show right without any need of a null terminator. I don't know about 240 chars...

DarkTimes
27th April 2011, 09:43
Yes, I've tested it.

If you send a button that's a multiple of four then it gets displayed, despite lacking a trailing zero. However if the text is exactly 240 characters then it does not get displayed, unless you replace the last char with a '\0'.

Here is a Python script I used to test the issue. I send three buttons, first eight chars long (no trailing zero), second 240 chars long (no trailing zero), third 239 + trailing zero. Only the first and third buttons get displayed. There is no error message in LFS about the button, it seems it's just discarded.

import socket
import struct

sd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sd.connect(('127.0.0.1', 29999))

# Init InSim.
isi = struct.pack('4B2HBcH15sx15sx',
44, # Size
1, # Type
0, # ReqI
0, # Zero
0, # UDPPort
0, # Flags
0, # Sp0
'\x00', # Prefix
0, # Interval
'', # Admin
'Test',) # IName
sd.send(isi)

# Send button with eight chars of text (displayed)
btn = struct.pack('12B8s',
20, # Size
45, # Type
1, # ReqI
255, # UCID
1, # ClickID
0, # Inst
0, # BStyle
0, # TypeIn
20, # L
20, # T
40, # W
10, # H
'12345678',) # Eight chars long
sd.send(btn)

# Create a str 240 chars long.
text = ''.join(['a' for i in xrange(0, 240)]) # Create str.

# Send button with exactly 240 chars (not displayed)
btn = struct.pack('12B240s',
252, # Size
45, # Type
1, # ReqI
255, # UCID
2, # ClickID
0, # Inst
0, # BStyle
0, # TypeIn
20, # L
40, # T
40, # W
10, # H
text,) # 240 chars long
sd.send(btn)

# Create str 240 chars with trailing zero
text = ''.join(['a' for i in xrange(0, 239)]) + '\x00'

# Send button 240 chars with trailing zero (displayed)
btn = struct.pack('12B240s',
252, # Size
45, # Type
1, # ReqI
255, # UCID
3, # ClickID
0, # Inst
0, # BStyle
0, # TypeIn
20, # L
60, # T
40, # W
10, # H
text,) # 240 chars with null terminator
sd.send(btn)

while True:
pass

MaKaKaZo
27th April 2011, 09:52
Yes, I've tested it.

If you send a button that's a multiple of four then it gets displayed, despite lacking a trailing zero. However if the text is exactly 240 characters then it does not get displayed, unless you replace the last char with a '\0'.

Here is a Python script I used to test the bug. I send three buttons, first eight chars long, second 240 chars long, third 239 + NULL. Only the first and third buttons get displayed. There is no error message in LFS about the button, it seems it's just discarded.


I think you should open a new thread to report this and maybe Scawen will have a look at it. The behaviour should be persistent regardless of the text size.

MadCatX
27th April 2011, 10:27
Perhaps there's something wrong with the way I tested it, but this bug seems to occur when a size of IS_BTN is 252 bytes and the text is NOT 239 chars long with zero terminator.
Sending text up to 236 chars (248 bytes long IS_BTN) works regardless if I zero terminate it or not. If the packet size is 252, then the only packet that doesn't get dropped is one that contains zero terminated 239 chars long text like DarkTimes posted.

I used this to check

struct IS_BTN btn_pack;
memset(&btn_pack, 1, sizeof(IS_BTN)); //Memset to 1 to not have a zero terminator at the end "automatically"

btn_pack.Type = ISP_BTN;
btn_pack.ReqI = 1;
btn_pack.UCID = 255;
btn_pack.ClickID = 1;
btn_pack.L = 1;
btn_pack.T = 1;
btn_pack.W = 240;
btn_pack.H = 10;

char* text = /****/

memcpy(btn_pack.Text, text, strlen(text)); //Copy just the text, NOT the terminator
btn_pack.Text[strlen(text)] = '\0';

insim.send_button(&btn_pack, strlen(text) + 1); //Modified func form CInsim, second parameter is the length of the text to send


EDIT: Just found out that if I memset the memory to 0 instead of 1, InSim behaves just like DarkTimes posted.

Scawen
27th April 2011, 14:13
I confirm the IS_BTN had a bug which I have now fixed. It is not supposed to require a trailing zero - that is because bandwidth is very important when sending a lot of buttons, so this helps you to keep button size to a minimum.

The IS_BTN should allow text up to 240 characters (with no zero) and I've fixed that in my version. I'm aiming to get to a compatible update on Friday.

The bandwidth saving issue doesn't really apply with messages, and LFS stores screen messages internally in a 128 byte buffer with terminating zeros, so I'll leave the message packets requiring a terminating zero, although I understand that seems inconsistent with the buttons which do not require a terminating zero.

DarkTimes
27th April 2011, 16:59
Thanks, Scawen! :)

MaKaKaZo
27th April 2011, 18:28
Nice, that one is sorted then, and I won't have to change my code for sending buttons :D

I think I'll redo the send_packet() function so that depending on the packet type -which is always the second byte of the struct/data to be sent- it will then call to separate functions in case it's a button, a mtc or otherwise (generic packet). I'm not feeling like doing it today anyway. What do you people think?

MadCatX
27th April 2011, 20:08
You had something like this in mind?


int CInsim::send_packet(void* s_packet)
{
pthread_mutex_lock (&ismutex);

//Detect packet type
switch(*((unsigned char*)s_packet+1))
{
case ISP_BTN:
{
struct IS_BTN* pack = (struct IS_BTN*)s_packet;
unsigned char text_len = strlen(pack->Text);

/*TODO: Should we truncate the string if it's too long
* or should we just discard the packet?
* If we discard the packet, perhaps another
* return code should be used. */
if(text_len > IS_BTN_MAXTLEN)
return -1;

unsigned char text2send;

unsigned char remdr = text_len % 4;
if(remdr == 0)
text2send = text_len;
else
text2send = text_len + 4 - remdr;

pack->Size = IS_BTN_HDRSIZE + text2send;
}
break;

case ISP_MTC:
{
struct IS_MTC* pack = (struct IS_MTC*)s_packet;
unsigned char text_len = strlen(pack->Text);

//Same as above
if(text_len > IS_MTC_MAXTLEN)
return -1;

unsigned char text2send = text_len + 4 - text_len % 4;

pack->Size = IS_MTC_HDRSIZE + text2send;
}
break;

default:
break;
}

if (send(sock, (const char *)s_packet, *((unsigned char*)s_packet), 0) < 0)
{
pthread_mutex_unlock (&ismutex);
return -1;
}
pthread_mutex_unlock (&ismutex);
return 0;
}

MaKaKaZo
27th April 2011, 21:13
You had something like this in mind?


...

Yes, that's the main idea. I'd keep the send_button() probably for compatibility reasons.

About checking text size, I don't want to do it. When you declare an IS_BTN struct the Text field is statically set to 240 chars long. In theory you shouldn't be able to send a button packet with a text longer than that, unless you modify the definition of the struct in insim.h or you do something nasty in your application. So I consider there's no need to worry about that :)

Dygear
27th April 2011, 23:02
Can you overflow the struct within C, considering your not using a NULL terminator?

MadCatX
27th April 2011, 23:24
You can overflow pretty much anything in C, something like char c[3]; memcpy(c, "XXXXXXXX", 8); is perfectly legal as far as compiler is concerned. Executing it would probably lead to segfault, so MaKaKaZo is right about not needing any sort of check for overflows. Even if such malformed packet gets through, LFS discards it anyway.

Dygear
27th April 2011, 23:45
char c[3];
memcpy(c, "XXXXXXXX", 8);

That's insane! Surly we can do better then this! Don't get me wrong I understand that C++ (and C in general) is very fast, but something like this should really be checked at compile time. No good can from from that code being allowed to execute.

Dygear
28th April 2011, 00:26
When you try to copy a source that is larger then a destination.

[EDIT] Damn it DarkTimes, stop deleting your posts. It's making me look like I'm talking to myself!

So, type softly and use a big stack?

morpha
28th April 2011, 00:27
memcpy takes a pointer dst and will fill the memory from that location onwards with size bytes of src. The compiler doesn't know (well, it does, but by passing it to memcpy you're casting it to a void*) that the pointer points to an array (strictly speaking, to the beginning of it) of 3 chars, it's simply an address.

That's the C way though, C++ offers a much safer way already in the form of the STL containers. They take care of everything memory-related (allocation, reallocation on resize, freeing on destruction).

std::string c;
c = "XXXXXXXX";doesn't overflow, is properly initialised (empty string) and doesn't leak.

€: Obviously there are less error-prone ways in C as well, didn't mean to make C look completely hopeless :P

Dygear
28th April 2011, 00:31
std::string c;
c = "XXXXXXXX";doesn't overflow, is properly initialised (empty string) and doesn't leak.

Remind me to learn C++ then, because C is a big scary monster (http://blog.candysporks.org/2010/08/06/top-10-lolcats-big-scary-monster/)!

MadCatX
28th April 2011, 00:35
When you try to copy a source that is larger then a destination.
So, type softly and use a big stack?

The stupid example above could be easily caught by the compiler, but sizes of data a program works with are usually dynamic, so there'd have to be some runtime checking to prevent invalid memory access. Consider a typical example of lame coding:

char c[10];

std::cin >> c;
std::cout << c << std::endl;


As long as user doesn't input anything longer than 9 chars, everything is OK and the compiler cannot possibly compensate for a programmer not taking care of this.


C++ has it's own big-scary-monstrous things too BTW:)

DarkTimes
28th April 2011, 00:35
[EDIT] Damn it DarkTimes, stop deleting your posts. It's making me look like I'm talking to myself!

I only delete my posts because they suck and I have no idea what I'm talking about.

Dygear
28th April 2011, 00:39
I only delete my posts because they suck and I have no idea what I'm talking about.

Well, that makes two of us, but I leave my suckieness out there (and my foot in my mouth for the whole world to see).

morpha
28th April 2011, 00:52
C++ has it's own big-scary-monstrous things too BTW:)If anything I'd say it's more monstrous. Simply put, C hands you a shotgun to shoot yourself in the foot with, C++ offers a wide array of weaponry. Some very elegant, some very bloody, but the end-result is still a missing foot.

Well, that makes two of us, but I leave my suckieness out there (and my foot in my mouth for the whole world to see).To keep the foot-analogy going, a foot in your mouth is a foot safe from being shot :D

Programmers need to shoot themselves in their feet though, best way to learn. Also, often times it's actually good fun.

MaKaKaZo
28th April 2011, 08:19
After this discussion, I think that you get my point now. This is very common in C, the programmer is responsible for filling in the data structures adecuately before passing them to functions. I don't say that it's the best way. Anyway, in this case, if a programmer overflows the Text field of the button it's his responsibility and CInSim's send_button() function won't fail because of this, probably the whole application will fail in an unexpected way.

And people don't get too scared of the overflowing example above. To prevent this you can use standard functions like strncpy() which will copy n characters and truncate the rest, although no null character is appended at the end if it has to truncate, so you actually have to copy text_field_size-1 characters if you want to ensure the C string is null terminated (considering you have previously filled the whole string with all zeroes, of course). Sounds easy, uh? :D

MadCatX
29th October 2011, 23:56
Hey guys,

since MaKaKaZo is busy having a life and all, I took the liberty of getting CInSim up to date to make it work with 0.6B. The list of changes include


Updated send_packet(). It now properly handles both IS_BTN and IS_MTC packets and sends only the amount of data that is needed.
Removed send_button() as it is no longer needed. (This breaks compatibility with old apps)
Simplified next_packet() and udp_next_packet()
As I understand it there's no need to check for FD_ISSET(&readfd). If select() returns a positive number, FD_ISSET should be also true. (It should, right?)
Got rid of 100% CPU load caused by next_packet()o n Linux systems
select() on Linux (and possibly other *NIX systems) reduces the passed timeval so that it contains the time that was left until a timeout would occur. I assume that's not the case on Windows. This eventually leads to zero timeval and select() returning immediately, which in turn causes very high CPU load.
I replaced select() with pselect() which doesn't modify the timeval.
(Other *NIX systems that use their own standard C library might exhibit a different behavior)


If MaKaKaZo ever decides to update CInSim himself, this little fork of mine will be removed.

Dygear
30th October 2011, 16:04
I really want to do some C++ programming. :)

Nice job guys!

MaKaKaZo
1st November 2011, 17:47
Hi. Thanks MadCatX for your work again. The thing is that right now I have too much of a life. So much that I don't have any time for gaming or leisure projects. I hope to have the time and be in the mood to go back to LFS at some point, but this year doesn't look too promising in that front, so every work you or anyone does on CInSim is very welcome ;)

MadCatX
2nd November 2011, 11:20
I expect my free time to become a rather rare commodity too later this year, but I think I can keep CInSim in a working state in case another InSim update is released. Should you ever decide to update the mainline CInSim, just let me know and feel free to use anything I posted or wrote so far, GL with your excessiveness of a life BTW:)