My C server so far

[quote=“Lothy, post:20, topic:384832”]Okay, I’ll look into that later. That’s probably something that Blake is going to need to add to Osiris as well, seeing as the code above is completely based on his.

Thanks.[/quote]

lol if only blake had the motivation to look into that sort of stuff.
he completely refuses to open a client’s source code, most of the update blocks i added.

[quote=“Graham, post:8, topic:384832”]In terms of your question about the ticks Lothy, I’m reasonably sure that the official RuneScape server does all game logic processing in multiples of the 600ms tick so there shouldn’t be any need to lower it.

The update server I wrote for 317 is still up on GitHub and hasn’t been removed yet - https://github.com/apollo-rsps/jagcached.[/quote]

I actually read in a recent developer blog on their Engine, that they’ve added the ability for game logic to process in 50ms ticks for some of their newer content. At least I think it was 50ms if my memory is correct. But other then that, up until rev 600+, they’ve confirmed that all their logic is processed in 600ms ticks.

That’s interesting. Do you have a link to it? I did some quick searching but couldn’t find it…

[quote=“Graham, post:22, topic:384832”][quote author=boomer216 link=topic=480598.msg3531361#msg3531361 date=1300205264]
I actually read in a recent developer blog on their Engine, that they’ve added the ability for game logic to process in 50ms ticks for some of their newer content. At least I think it was 50ms if my memory is correct. But other then that, up until rev 600+, they’ve confirmed that all their logic is processed in 600ms ticks.
[/quote]

That’s interesting. Do you have a link to it? I did some quick searching but couldn’t find it…[/quote]

Specifically:

Q. As we know and stated by Andrew the game runs at 600ms intervals (one tick). Is there a reason for this exact number? Could the game benefit from lower interval, for example 300ms?

A. Some upcoming features in the server tick at a 50ms rate. The overall 600ms tick remains, as the content relies on having this time interval available as a base, but we’re not limited by it.
Mod_jackh

Out of curiosity, why are you writing this server in C?

Boils down to a few things:

a) I’ve never written anything non-trivial in C (or C++ - but I don’t like C++ enough to use it)
b) I wanted to change this, because I was sick of being one of these newfag programmers that ONLY uses C#/Java. Both of these languages are quite good (although now that I’ve come into function pointers, the lack of them in java pisses me off a lot), but using them exclusively is pretty bad imo.
c) Because I wanted to see a server that made use of Lua for scripting
d) The RS emulation scene deserves a native solution just like the WoW scene
e) Because I’m a mushroom cloud laying motherfucker, motherfucker!

hey Lothy, nice project, I checked it out

:slight_smile: I will talk to you in IRC about some ideas I have

out of curiosity, why do you think you need function pointers so much?

Because Java’s poor man’s function pointers are, for all intents and purposes, implementing an interface over and over again. That’s as close as it comes.

looks nice lothy keep it up legend

I have no idea why I looked into this as much as I did, but here we go…

src/bit_packer.c
[hr]

[ul][li][font=andale mono]free_bit_buffer[/font] doesn’t free the buffer allocated[/li]
[li][font=andale mono]free_bit_buffer[/font] and [font=andale mono]free_char_buffer[/font] have inconsistent signatures ([font=andale mono]int[/font] vs [font=andale mono]void[/font] return values). Is [font=andale mono]free_char_buffer[/font] needed?[/li]
[li][font=andale mono]write_bits[/font] @64: This code should have nothing to do with players directly. If the function fails, return with an appropriate value and let the calling code take care of it.[/li][/ul]

src/circular_buffer.c
[hr]

[ul][li][font=andale mono]WRAP_WRITE_PTR[/font] should surround its arguments with parentheses: (((x) % (y)).[/li]
[li]Writing to the buffer looks like it could get pretty disgusting (due to long names). It’s not really avoidable, but it might be worth your time to come up with something to make it more readable (macros?).[/li][/ul]

src/config.c
[hr]

[ul][li]Instead of having a fixed list of options and retaining them as individual global variables, store options in a table (even arbitrary tag names).[/li]
[li]The parsing code should be a bit more flexible (whitespace, possibly allow for comments); some sanity checks would be good too (bad syntax).[/li][/ul]

src/item.c
[hr]
[ul][li]A fixed amount of items is understandable, but will you always need the same amount? (different revisions)[/li][/ul]

src/linkedlist.c
[hr]
[ul][li][font=andale mono]linkedlist_create[/font] should zero the structure it allocates.[/li]
[li][font=andale mono]linkedlist_insert_tail[/font] should zero the structure it allocates.[/li][/ul]

src/listener.c
[hr]
[ul][li][font=andale mono]load_event_base[/font] should return a null pointer instead of calling [font=andale mono]exit[/font].[/li]
[li][font=andale mono]sock_write_cb[/font] and [font=andale mono]sock_event_cb[/font] should cleanly disconnect and safely deallocate the client (cleanup function for the client structure).[/li][/ul]

src/module_loader.c
[hr]
[ul][li]You should support different platforms (Windows) appropriately: use POSIX API when available, don’t assume ‘.so’ suffix.[/li]
[li][font=andale mono]load_modules[/font] doesn’t [font=andale mono]closedir[/font] when it returns after error. In addition, exit shouldn’t be used in the event of failure.[/li]
[li][font=andale mono]load_module[/font] has a fixed (and really short!) length char array for the path. [font=andale mono]strcat[/font] should be avoided in favor of more secure alternatives.[/li][/ul]

src/packet_decoder.c
[hr]
[ul][li][font=andale mono]set_login_decoder[/font], [font=andale mono]set_packet_decoder_table[/font], and [font=andale mono]set_packet_sizes_table[/font] shouldn’t [font=andale mono]exit[/font] on failure.[/li][/ul]

src/packet_sender.c
[hr]
[ul][li][font=andale mono]set_send_packet_table[/font] shouldn’t [font=andale mono]exit[/font] on failure.[/li][/ul]

src/region.c
[hr]
[ul][li][font=andale mono]init_region_list[/font] can just zero the whole region list at the same time ([font=andale mono]sizeof(struct region) * REGION_COUNT * REGION_COUNT * REGION_HEIGHT[/font]).[/li][/ul]

src/server.c
[hr]
[ul][li]Tests should be completely separate from the main source tree.[/li]
[li][font=andale mono]set_player_update_function[/font] and [font=andale mono]set_npc_update_function[/font] shouldn’t [font=andale mono]exit[/font] on failure.[/li][/ul]

I understand this is still fairly new, and that a lot of things are just hacked together or prototyped, which is fine. It would be good to check out the way you write code, consistency in naming/signatures and leaning more towards ‘Do one thing and do it well’ will (hopefully) remove some of the pains with writing something like this.

However, it looks OK so far. You may want to look into libev as an alternative to libevent. It’s updated somewhat often and has something else good about it which I forget, ask Mopman.

Hey man, thanks for taking the time to look at it in such depth. I’ll try to address things as best I can, and I’ll undoubtedly have questions too before this reply is finished.

Regarding the bit_packer files, particularly the [font=andale mono]free_bit_buffer[/font] function, those two things are intentional.
[font=andale mono]free_char_buffer[/font] actually has that function signature because it was intended to be a callback that would be used by the libevent API.

My original design involved providing a struct bit_buffer which would then be written to, and then committed to libevent. Once that commit was made, my code could free the bit_buffer – when libevent was finished writing the underlying char *, it would then invoke that callback to free it as well.

I decided that this was rather poor design, so bit_packer.h/c are artifacts that I haven’t gotten around to removing yet. I’ve now actually moved the bit packer functions into circular_buffer.c, and they write to the buffer in there.
Sorry about that, but if nothing else at least it’s blatantly obvious now that I made the right decision in ditching that approach.

As for the bit regarding the semantics of [font=andale mono]write_bits[/font], I agree with you. [font=andale mono]cwrite_bits[/font] in circular_buffer.c follows your suggestion.

[hr]

src/circular_buffer.c
Hopefully this macro is right now: WRAP_WRITE_PTR(x, y) ((x) % (y))

As for the rest, I’m not sure how I could apply macros in place of functions for the writing to the buffer. I’ll keep it in mind though and do some searching, because there is a large amount of code in there and it would be ideal to improve it where possible.

[hr]

src/config.c
I haven’t gotten far with this. Originally I was just playing around seeing how I could parse it. I was actually going to use a lexer, but they all seem to be under GPL (my code, on the other hand, is licensed under the BSD license).
It’s low priority at the moment, mostly because I just want to get it to a working stage.

[hr]

src/item.c
I presume you’re talking about the following:

As far as I can remember, there are less than 14000 unique items in the game at the moment. I guess I can increase it to 15k or more to future proof it for a while, but I don’t really see the value in making it too much larger than the current item count.

For now, I’ve defined a value as 15000:

#ifndef MAX_ITEM_DEFINITIONS
#define MAX_ITEM_DEFINITIONS 15000
#endif

Down the track I’ll either a) document it so that people know that they can define the value at build time or, better yet, b) fix configuration loading up nicely and have it set there.

[hr]

src/linkedlist.c
Done. Thanks for pointing that out.

[hr]

src/listener.c
I’ve changed [font=andale mono]load_event_base[/font]. I’ve also moved the disconnection code to its own function (well, the calls to free the bufferevent and client structs).
Whether this is what you meant by disconnecting cleanly, I’m not entirely sure.
I’ve followed your advice too with regards to the client structure, and it now has its own allocation/deallocation functions (client_create() and client_free()).

[hr]

src/module_loader.c
This is a tricky one for me. :stuck_out_tongue: As you’ve probably noticed, the only windows-related code is found in the [font=andale mono]load_event_base[/font] function. It’s actually only there because I based that code heavily, if not completely, on their example code.

I don’t actually have access to a windows system to build and test the code on, so it’s pretty futile for me to cater to windows at this stage.
I’m really hoping that someone chooses to come on board at a later stage for that once it’s a working product. Worst case though, it is on my TODO list.

Thanks for catching the lack of [font=andale mono]closedir[/font] calls; I’ve fixed that, and I’ve removed the call to exit() as well.

What are the alternatives to strcat? I know that Theo de Raadt wrote a few functions, specifically strlcat and strlcpy, for OpenBSD. These haven’t been adopted in glibc though, and likely never will be.
According to the wikipedia article (http://en.wikipedia.org/wiki/Strlcat), strncat and strncpy are fairly shit.
Are there any others that I’ve missed?
For what it’s worth, I’ve ensured that all of the char arrays that I use are null-terminated.

Anyway, I’ve changed it so that [font=andale mono]full_module_name[/font] is allocated on the heap and then freed after the call to [font=andale mono]dlopen[/font]; this should stop any potential buffer overflows.

I imagine the most ideal way to do module loading would be by defining a few macros, with definitions depending on whether it was being built on windows or a unix-like system.
I’ll get to that down the track.

[hr]

src/packet_decoder.c and src/packet_sender.c
Fixed.

src/region.c
Fixed, thanks for that. I wasn’t exactly sure how the allocation of n-dimensional arrays worked.

src/server.c
Fixed. I’ve removed the test code too.

Again, thank you so much for all of that feedback. Obviously not many people on this forum make use of C, so your input is extremely valuable to me.

This so hard.

On another note, you should’ve used C++ if you wanted this to be open-source for the community to use. It’s just a more common, widely used, and versatile language than C nowadays.

For the fixed size item list, I was referring more to there being less than 14000 items than there being more (older revisions, RT3/RT4). I would probably have it either be a config option or specified by a module (maybe have support for different revisions be loaded as modules, which can take care of setting protocol de/encoders and anything else).

Supporting modules on Windows isn’t too hard.

[code=c]HMODULE mod;
int (*fn)();

mod = LoadLibrary(full_module_name); /* you might need to specify LoadLibraryA explicitly depending on encoding /
if (mod == NULL) {
/
oh no… */
}

fn = (int (()())) GetProcAddress(mod, “module_init”); /* same as above with GetProcAddressA /
/
… */[/code]

For finding modules in a directory

[code=c]WIN32_FIND_DATA ffd;
HANDLE fh;
const char search_path = "modules/.dll";

fh = FindFirstFile(search_path, &ffd);
if (fh == INVALID_HANDLE_VALUE) {
/* oh no… */
}

do {
/* would be good to make this check a bit safer/robust /
/
we filter for *.dll anyway, but for the sake of example… /
if (strstr(ffd.cFileName, “.dll”) != NULL) {
/
load it up */
}
} while (FindNextFile(fh, &ffd) != 0);

FindClose(fh);[/code]

In regards to the static path size, 2000 is much more reasonable, but I was getting at using something like MAX_PATH on Windows. I did just look into it though, and apparently MAX_PATH on Windows is pretty much pointless, and consistency among unix-like systems is pretty bad (some define PATH_MAX and FILENAME_MAX, others may not define them and you need to use pathconf or fpathconf to find it, however it can potentially return a ridiculously huge value or -1, indicating that it isn’t bounded). So at that, I would go for 2048 and call it a day.

strlcat is what I was referring to. I didn’t know it isn’t included in glibc, I’ve been using BSD-like systems for a while now.
You have a few options here.

[ul][li]Do something like this (from a BSD strcat manpage):[/li][/ul]

[code]SECURITY CONSIDERATIONS
The strcat() function is easily misused in a manner which enables malicious users to arbitrarily change a running program’s functionality through a buffer overflow attack. (See the
FSA.)

 Avoid using strcat().  Instead, use strncat() or strlcat() and ensure that no more characters are copied to the destination buffer than it can hold.

 Note that strncat() can also be problematic.  It may be a security concern for a string to be truncated at all.  Since the truncated string will not be as long as the original, it may
 refer to a completely different resource and usage of the truncated resource could result in very incorrect behavior.  Example:

 void
 foo(const char *arbitrary_string)
 {
         char onstack[8] = "";

 #if defined(BAD)
         /*
          * This first strcat is bad behavior.  Do not use strcat!
          */
         (void)strcat(onstack, arbitrary_string);        /* BAD! */
 #elif defined(BETTER)
         /*
          * The following two lines demonstrate better use of
          * strncat().
          */
         (void)strncat(onstack, arbitrary_string,
             sizeof(onstack) - strlen(onstack) - 1);
 #elif defined(BEST)
         /*
          * These lines are even more robust due to testing for
          * truncation.
          */
         if (strlen(arbitrary_string) + 1 >
             sizeof(onstack) - strlen(onstack))
                 err(1, "onstack would be truncated");
         (void)strncat(onstack, arbitrary_string,
             sizeof(onstack) - strlen(onstack) - 1);
 #endif
 }

[/code]
[ul][li]Do some compile time checking or use autoconf (config.h, I wouldn’t really go for that), and do something accordingly (If strlcat is present, go ahead and use it. If not, implement strlcat using the code above or use strlcat.c with some appropriate modifications).[/li]
[li]Don’t bother checking for existing functions and just implement strlcat as mystrlcat or something.[/li][/ul]

I’m not really sure what I would do to make the writing code better either. You could try your best to create a pseudo-DSL with macros, but that could get ugly fast.

[code=c]#define P_C(v) ((char) (v))
#define P_S(v) ((char) ((v) >> 8)), ((char) ((v) & 0xff))
#define P_I(v) ((char) ((v) >> 24)), ((char) ((v) >> 16)),
((char) ((v) >> 8)), ((char) ((v) & 0xff))
#define P_END -1

/*
not sure how i would terminate data. a magic number would be simple, but potentially break with certain packets.
instead of using a char array, maybe use a short array where positive values are simply the unsigned value of the actual byte, and -1 can be a marker for the end of the array.
*/
int c_write(struct circular_buffer buf, char… data) {
/
do stuff */
}

void construct_mypacket(struct circular_buffer buf, int foo, short bar) {
/
let’s just pretend c_write uses a short array with -1 as a marker */
c_write(buf, P_C(123), P_C(0), P_S(bar), P_I(0xdeadbeef), P_I(foo), P_END);
}[/code]

Well, it’s not much of a DSL, is it?
Ultimately it may just be better to suck it up and use your current API, and if it really becomes a nuisance, make nifty Lua bindings for packet construction.

This so hard.

On another note, you should’ve used C++ if you wanted this to be open-source for the community to use. It’s just a more common, widely used, and versatile language than C nowadays.[/quote]
Yes, Java not having function pointers is actually a pain in the ass. Sorry most people don’t like as much boilerplate as you do.

What a crock of shit. C is still more common/used than C++ is, though the adoption rate is probably less. What makes C++ more versatile than C? Some features of C++ would be nice, like exceptions and templates, but much of it isn’t really deal breaking (yes, OOP included, sorry).

that item definition stuff looks rather poorly hacked together, and have you considered writing your own lexer/parser?

[quote=“Lothy, post:30, topic:384832”]I don’t actually have access to a windows system to build and test the code on, so it’s pretty futile for me to cater to windows at this stage.
I’m really hoping that someone chooses to come on board at a later stage for that once it’s a working product. Worst case though, it is on my TODO list.[/quote]

I actually build a decent amount of windows code, and never actually on windows. Basically ever linux distribution has a package for mingw, and you can use it to build windows executables. Then of course there is WINE to test them under. But also, like you said, I don’t know how much I’d worry about it at this stage.

Hi guys, it’s been a while.

Latest commit lets you log in and walk around, but there’s a curious ‘bug’ (of sorts) which I can’t figure out.

Those familiar with the protocol will know that the first 5 bytes of a walk request packet in version 508 make up two shorts (the absolute x and y of the tile that you clicked) and a byte (which indicates whether or not you had the ctrl key pressed when you clicked the tile).

Following that is the rest of the path.

Anyway, when playing with it I realised that, when clicking on certain tiles, the first short is wildly off.
Here’s some sample output:

-127 3430
-128 3430
3197 3430
3197 3430
-127 3430
3196 3430
3196 3444
3196 3419
-123 3430
3191 3430
3195 3428
3198 3430
-128 3430
3198 3430
3199 3429
3199 3430
3197 3430
3197 3430
3198 3430
3199 3430
-128 3430
3199 3430
-127 3430
3199 3430
-128 3430
-128 3430
3199 3430

Does anyone know what’s happening? The functions that read from the underlying evbuffer are fine, and it returns the correct value most of the time. But for some reason those first two bytes (which are read as b[0] - 128 | b[1] << 8) are 0 0.

Notably isn’t an issue when I log in to Blake’s server, Osiris.

should the temp value be an unsigned short?

ps using the obfuscated protocol is still a good idea right… right?

The byte ordering doesn’t matter as long as it is read in the same way that it was written to the stream.
Every single client released in the wild makes use of the obfuscated protocol, which is why my server should be able to operate with them out of the box.
With that said though, anyone at any time can refactor a client so that it only uses big endian and then modify the plugin file accordingly.

Regarding the value’s sign, no. Think about it for a second – if -128 is cast to an unsigned short, what will the value equal? Ballpark answer: somewhere around 65500. That’s certainly not a valid absolute x value for use on runescape’s world map.

[quote=“Lothy, post:25, topic:384832”]Boils down to a few things:

a) I’ve never written anything non-trivial in C (or C++ - but I don’t like C++ enough to use it)
b) I wanted to change this, because I was sick of being one of these newfag programmers that ONLY uses C#/Java. Both of these languages are quite good (although now that I’ve come into function pointers, the lack of them in java pisses me off a lot), but using them exclusively is pretty bad imo.
c) Because I wanted to see a server that made use of Lua for scripting
d) The RS emulation scene deserves a native solution just like the WoW scene
e) Because I’m a mushroom cloud laying motherfucker, motherfucker![/quote]

=D!

Edit:

Just happy to see something like this