Welcome!

Join our community of MMO enthusiasts and game developers! By registering, you'll gain access to discussions on the latest developments in MMO server files and collaborate with like-minded individuals. Join us today and unlock the potential of MMO server development!

Join Today!

[Discussion] My two cents about IGCN source code.

Joined
Aug 6, 2005
Messages
550
Reaction score
296
Just a little adendum to the c# vs c++ discussion:

I somehow saw a post I wrote in codereview.stackexchange showing a simple wrapper to marshal raw data into marshalable c# classes. Link:

The question is, why would you want to do that in C#. A few years ago, I tried this approach for reading/writing packets and found marshaling not faster. It adds some unneeded complexity to the code and is sometimes not possible with complex mu packet structures. IMHO, it's the wrong "optimization" at the wrong place - there is simply no need to do such things.

And I don't agree that C# isn't suitable for a server application because of the missing memory management capabilities - I think the opposite is the case:

  • The garbage collector is pretty efficient, the game client doesn't notice when it runs for a millisecond. It's not like it will noticeably lag on client side when it runs on the server.
  • Memory leaks are less likely to occur, therefore the server might run longer without a restart.
  • Caring about memory is error-prone and requires discipline. I've seen all kind of mistakes in my day-to-day job, even from good developers.

Making a game client in C# is a completely different story, of course. Frame-times must be constantly short to get no lags. A GC pause could cause lag there.
 
Junior Spellweaver
Joined
Feb 18, 2011
Messages
108
Reaction score
41
The question is, why would you want to do that in C#. A few years ago, I tried this approach for reading/writing packets and found marshaling not faster. It adds some unneeded complexity to the code and is sometimes not possible with complex mu packet structures. IMHO, it's the wrong "optimization" at the wrong place - there is simply no need to do such things.

And I don't agree that C# isn't suitable for a server application because of the missing memory management capabilities - I think the opposite is the case:

  • The garbage collector is pretty efficient, the game client doesn't notice when it runs for a millisecond. It's not like it will noticeably lag on client side when it runs on the server.
  • Memory leaks are less likely to occur, therefore the server might run longer without a restart.
  • Caring about memory is error-prone and requires discipline. I've seen all kind of mistakes in my day-to-day job, even from good developers.

Making a game client in C# is a completely different story, of course. Frame-times must be constantly short to get no lags. A GC pause could cause lag there.

I think you are missing the points here..

The discussion does not revolves around while c# memory management limitations execute slower than c++. The problem is not how much CPU cycles the C# server will have to execute to accomplish it (or at least it shouldn't be it).

The problem is more about aspects of code quality (like maintainability).

So, to answer your first question: "why would you want to do that in C#". I believe you are talking about why one would go with the Marshal-way to convert raw memory into code objects. The answer is simple: it is the very best alternative to do it in C# so far (when we have ref returns and the possibility to direct cast a pointer into a blittable object it can change). To do this task in C# you have 3 options: A - you can go with the "ReadByte, ReadWord, ReadDWord" method when you will load each offset of a memory chunk separately into the correct variable; B - you can do it with Marshal or C - you can do it with blittable struct cast and pointers.

The first option (A) is very, very, VERY bad for tons of reasons ranging from execution time to many aspects of code quality. Imagine you have to initialize a class of hundreds of variables. Using this method you will write a function to initialize this object from a memory stream once and pray for the gods you don't need ever more to touch this function. If for some reason you need to modify this code you will spend a LOT of time trying to check if the memory offsets are right with your new modifications. This is just NOT the right solution here.

The option C gives you the best performance but it is not recommended because the data objects must be blittable leaving you in bad hands when you have to declare an array for example. For some odd reason C# won't let you declare a blittable array of any element in a struct (even if the elements of the array are blittable).

So finally we have the option B which is using the Marshal class and its attributes. Yes, the code will be bigger with the attribute decorations (MarshalAs) and stuff but it is the better known way to do it in C#. You can create a code that runs 100% in managed mode. The execution is somewhat okay considering the amount of tasks the cpu have to execute to accomplish this but it is not a big problem considering the hardware power we have nowadays.

I don't know which option you are using to do this in your project but I strongly disagree when you say that "marshaling is not faster and adds some unneeded complexity to the code". Well, when you say that there are complex mu packet structures that can not be represented using Marshal I have no clue what you are talking about. The marshal class is made to fit every possible data type in memory. This is the method used in every pinvoke (platform invoke) code. Could you show an example of such a complex structure that can not be represented in a classe using Marshal?
 
Joined
Aug 6, 2005
Messages
550
Reaction score
296
Well, I guess we have different approaches in mind when working with packets. I probably use something similar to your option A, but I don't parse packets into structs or classes. Imho, defining all this message structs just adds a lot of unneeded boilerplate and sometimes complexity. I work directly on a byte array (and soon pooled Memory/Span<byte>) with some convenience extension methods. So basically, I extract the data I need into local variables and pass them to my game action functions. When sending packets I also use extension methods to set the bytes.

Imagine you have to initialize a class of hundreds of variables. Using this method you will write a function to initialize this object from a memory stream once and pray for the gods you don't need ever more to touch this function.

Packets coming from clients are usually very small (under 5 fields), so this is not an issue here. For outgoing packets it's similar - most are relatively small. The big ones are a bit of an issue, but it's a trade-off I made here. The original code doesn't make things much better in this cases :D


If for some reason you need to modify this code you will spend a LOT of time trying to check if the memory offsets are right with your new modifications.

Sure, but the same applies for structs and marshalling. You'll have to correctly define them as well and hope marshalling works as expected. The original game server/client also uses different byte order (little/big-endian) for same types of fields, sometimes structs are packed, sometimes not - big PITA. I wonder how marshalling behaves when it runs under different CPU architectures (possible with .net core at linux), e.g. ARM.


I don't know which option you are using to do this in your project but I strongly disagree when you say that "marshaling is not faster and adds some unneeded complexity to the code".

I wrote a small test which compares my way of reading packets to using struct marshalling. My way is about 100 times faster in this test (if I leave the validation away it's more like 800 times). I know this is more like a theoretical test and that's no bottleneck in the real world, but still. You can find the test code here:

Well, when you say that there are complex mu packet structures that can not be represented using Marshal I have no clue what you are talking about. The marshal class is made to fit every possible data type in memory. This is the method used in every pinvoke (platform invoke) code. Could you show an example of such a complex structure that can not be represented in a classe using Marshal?

I encountered problems when I tried to use arrays of structs. Some packets also have some dynamically sized blocks, e.g. the effects in player meet packets. Simplified example:
[Header]
[Player Count], for each:
[[Fixed block of Player Data] [Effects counter] [Dynamic block of effects, one byte each]]

I don't know if I were too stupid some years ago, but I didn't manage to use Marshal in a simple way. My first problem was, that only the first player got parsed/serialized. Even if I could get that to work, the next problem was, that I couldn't define a variable length of the array of effects. Maybe my approach was flawed and it required more manual work, like multiple marshal calls for one packet.

The problem is more about aspects of code quality (like maintainability).

I hope I got your point now... So, you're telling me that maintaining C++ code is easier than a C# and code quality in this specific aspect will be better in C++? Well, you can write horrible code in any language - you saw this IGCN sources, right?
My experience so far is, that packet handling is one of the easier and smaller aspects during development of a server. The other benefits of C# are way more valuable for me.
And my current approach might not be the final one... I'm thinking about defining packet structures as data which feeds some dynamic code generator - would be just as fast as writing my manual code but highly flexible.
 
Last edited:
Junior Spellweaver
Joined
Feb 18, 2011
Messages
108
Reaction score
41
Well, I guess we have different approaches in mind when working with packets. I probably use something similar to your option A, but I don't parse packets into structs or classes. Imho, defining all this message structs just adds a lot of unneeded boilerplate and sometimes complexity. I work directly on a byte array (and soon pooled Memory/Span<byte>) with some convenience extension methods. So basically, I extract the data I need into local variables and pass them to my game action functions. When sending packets I also use extension methods to set the bytes.

Well, what you call "uneeded boilerplate" I call better readability and maintainability of code. Of course it sums up some extra lines of code, but so what? A well done code documentation sums up an enormous amount of LoC. Will you stop documenting your code because it adds "uneeded boilerplate"? I think you are facing this issue the wrong way...

Packets coming from clients are usually very small (under 5 fields), so this is not an issue here. For outgoing packets it's similar - most are relatively small. The big ones are a bit of an issue, but it's a trade-off I made here. The original code doesn't make things much better in this cases :D
As I'm still learning the game's protocol I can't say much here besides what I already told. You are facing readability and maintainability as if you shouldn't care about it. Today you probably won't face much issues regarding code quality. Tomorrow, however, when you get more people working on your project or when you need to maintain a code wrote 5 years ago you will miss these days when you COULD do it with code quality in mind but you didn't, because it adds "unneeded boilerplate".

Sure, but the same applies for structs and marshalling. You'll have to correctly define them as well and hope marshalling works as expected. The original game server/client also uses different byte order (little/big-endian) for same types of fields, sometimes structs are packed, sometimes not - big PITA. I wonder how marshalling behaves when it runs under different CPU architectures (possible with .net core at linux), e.g. ARM.
No, the same does not applies for marshaling. The amount of complexity added using the "stream reader" option is huge. The added complexity using the "marshal" option is minimal. I have no idea why you think the two scenarious would be the same to maintain. About the endianess you have ways to deal with it. However I have doubts about if this is still the case. As far as I know the client were compiled to IA-32 architecture, which is little endian. You have to deal with it in the case you are compiling for another architecture platform (but it that is the case you will have a LOT more issues to think about than this - this will be a piece of cake near the concerns you will have).

I wrote a small test which compares my way of reading packets to using struct marshalling. My way is about 100 times faster in this test (if I leave the validation away it's more like 800 times). I know this is more like a theoretical test and that's no bottleneck in the real world, but still. You can find the test code here:
Yup, it is a fact that a generic (C#'s generic) function using Marshal will execute slower than a stream reader. However I never say the opposite. In contrary, I said the Marshal option will execute slower but it should not be an issue if we consider the hardware power servers have nowadays. To develop a big project like this you will have to either go for parallelism or a multi threaded application. The amount of cpu cycles needed to execute the Marshal solution should be insignificant in the overall execution of the process. Of course I'm considering you are developing this project to deploy in a server that fit its requirements. If execution time is gold for your project, go for blittable structs, pointers and C++/CLI, not stream reader.

I encountered problems when I tried to use arrays of structs. Some packets also have some dynamically sized blocks, e.g. the effects in player meet packets. Simplified example:
[Header]
[Player Count], for each:
[[Fixed block of Player Data] [Effects counter] [Dynamic block of effects, one byte each]]

I don't know if I were too stupid some years ago, but I didn't manage to use Marshal in a simple way. My first problem was, that only the first player got parsed/serialized. Even if I could get that to work, the next problem was, that I couldn't define a variable length of the array of effects. Maybe my approach was flawed and it required more manual work, like multiple marshal calls for one packet.
You probably did something wront when you tried to do it. To declare an array of structs it's as simple as adding the UnmanagedType.ByValArray in the field declaration.
About the dynamic length array in the packets: yes, you can't declare that using marshal directly. But you can't either in any other possible situation. You can declare an IntPtr and then use Marshal functions to alloc and copy the actual length of elements of the array. This is exactly the same "boilerplate" you have to do if you code this kind of structure in c/c++. You can't let the compiler handle a dynamic length array because dynamic length arrays do not have a fixed length known at compile-time.

I hope I got your point now... So, you're telling me that maintaining C++ code is easier than a C# and code quality in this specific aspect will be better in C++? Well, you can write horrible code in any language - you saw this IGCN sources, right?
My experience so far is, that packet handling is one of the easier and smaller aspects during development of a server. The other benefits of C# are way more valuable for me.
And my current approach might not be the final one... I'm thinking about defining packet structures as data which feeds some dynamic code generator - would be just as fast as writing my manual code but highly flexible.
Yep, I'm saying considering you are developing considering code quality, in this SPECIFIC case C++ will produce a code that is easier to read and maintain than C#'s.
Of course each language have its strengths. I'm not saying C# is useless or impossible or even just bad to develop this application. I'm just saying you have better options, like C++.
Of course² we have to consider C++ is very much more complex and complete language than C#. It takes a LOT more time to master C++ (if that is even possible) than it is needed to master C#. When I say C++ is more suitable to do this than C# I'm considering you have the knowledge to do that in C++ (you will use C++'s garbage collector system, you will use STL and boost, you will use smart pointers, you know code patterns and when to use each in C++, etc).

To conclude I really suggest you take a day or two to read a bit more about code quality and the importance behind it in a long-term software development.

Best regards.
 
IGCN Co-Founder
Joined
Jun 26, 2006
Messages
303
Reaction score
487
Keep in mind this discussion is generally fueled by people with alot of hatred towards IGCN and mostly without any knowledge of how things work / originated in MU Online dev. + the source they review is Season 9. You may Google how many years have passed since it was leaked :)
 
Joined
Aug 6, 2005
Messages
550
Reaction score
296
This thread is just about the source code itself, not about igcn in general. I don’t hate them and the thread starter doesn’t obviously, too - just read the first post ;)
 
Junior Spellweaver
Joined
Feb 18, 2011
Messages
108
Reaction score
41
Keep in mind this discussion is generally fueled by people with alot of hatred towards IGCN and mostly without any knowledge of how things work / originated in MU Online dev. + the source they review is Season 9. You may Google how many years have passed since it was leaked :)

As stated in the beginning of the post, I quote: "I'm mainly talking about the S9 source that seems to start this trend ([Release] IGCN Season 9.5 (src-x9.5 9.5.1.15) SRC (April/2016)) and about the "MuEmu" S12 source (that is basically the same S9 code).".
RickG

As nevS said, this thread is about the quality of the source code (server seasons 9 and 12) they shared in this forum. I can not even infer they use the same code in their servers right now. The conclusion we all came is that those source codes in particular are VERY bad coded. Probably coded by people who was starting to learn programming; the state of those codes are really poor. Despites what drakelv tries to imply, there are programmers in this thread that are clearly far more experienced than the ones who developed the code in discussion.
 
Last edited by a moderator:
Joined
Aug 6, 2005
Messages
550
Reaction score
296
Found a nice old post of GM-Andromeda :)

i agree. 100%, all those private servers out there are using the same base server source, same code, maybe changing items/dmg/exp or some maps details like terrain walking area, but they are all the same.

and of course, i can prove it, since all those bugs i've found in the past, were working in every single version, from season 1 to 6, like dupe methods, crashing GS/CS, kick players (no flood method poop), etc.

they work in every version, no matter ICNG, titashittech, custom private servers, etc. Dupe methods were working even in GMO and u know it :laugh:

so, if u exploit a vulnerability, and u find that the same exploit method is working on every server u test, then the obvious conclusion is that every server is using the same poop base source code, and of course, they say they all have

their own implementation, but of course, they don't know what they are talking about. Like most people here, everyone talks, but everyone copy/paste crap from someone else code. And the reason is very simple, for coding, u need knowledge, and for knowledge u need two things, study + effort. Most people here did not go to collage, and don't want to make an effort. :):
 
Junior Spellweaver
Joined
Feb 18, 2011
Messages
108
Reaction score
41
Found a nice old post of GM-Andromeda :)

Well, analyzing all the different source codes I found in the Mu community I agree that they all seems to be written by people who were just starting to learn software development. There are basically none security checks when using CRT functions. A LOT of potential stack overflows in functions that processes client data packets. I didn't see 1 well written code here in this aspect here. Although this is a problem regarding code quality and software security, there are good stable projects; anyone can start right away to work on that leaked x-team source codes (that one that compiles to s4, s6 and s8) and start fixing the problems in a ad hoc way and have a mu server in minutes.

Despite the problems, it is very nice to have such stable openned sources in the community. It stimulates ppl to start learning development. This is a lot more than we have in the majority of other games communities like this one.

That being said, there are other explanations to different projects being vunerable to the same exploit than the conclusion that GM-Andromeda came. The vunerability can be at some 3rd party lib they all rely on, or framework, or any other possible explanation and yet they all have different "source base codes" (considering he was refering to one project being copy/paste from another).
 
Back
Top