Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Join us on Thursday, June 8, for a Q&A with Unity's Content Pipeline group here on the forum, and on the Unity Discord, and discuss topics around Content Build, Import Workflows, Asset Database, and Addressables!
    Dismiss Notice

Poor Man's (Unity) Conditional Compilation to Secure SrvCode

Discussion in 'Multiplayer' started by jashan, May 1, 2008.

  1. jashan

    jashan

    Joined:
    Mar 9, 2007
    Posts:
    3,304
    Srv = Server- (max input-length reached).

    I've been thinking for a while to find a solution to protect my "server code". With a standalone server architecture, the nice thing is that theoretically, you can keep the server code safe on the server to avoid people messing around with it. The nice thing about developing with Unity is that you can keep client and server inside of one project which improves the development workflow quite a bit. The ugly thing about this is that it's not trivial to keep the server code out of the clients. Because in the end, the server is just a client with some flags changed.

    The first solution I had thought of was using the usual precompiler directives to build assemblies specific to the client and specific to the server. If Unity did support precompiler directives (#ifdef SERVER), no assemblies would be needed and that would be the perfect solution (hint hint... wish wish...)

    When you need assemblies, it gets really messy, though - so I was hesitant following that route.

    This morning, I played with JCsLogger (I've added very smooth Debug.Log-support - so now you can configure Debug.Log via a log4net-config file, I'll post about this some other day). One "key concept" I'm using there is simply replacing /**/ with /**/// to comment lines in or out. There, I use this to be able to easily remove the log4net.dll for Web player builds without having to remove all my logging code.

    And suddenly, I had the solution to my paranoia (is it?):

    I call this "Poor Man's Conditional Compilation". It simply works by adding "escape comments" and then doing global search and replace (I'm using Visual Studio, so this is trivial - but there sure are other solutions for that, too).

    What I do is simply something like this:

    Code (csharp):
    1.  
    2. public void MyClientSideMethod() {
    3.     SomeClientCode();
    4.     networkView.RPC("RPCMyServerSideMethod", RPCMode.Server);
    5. }
    6.  
    7. /*SS*/
    8. [RPC()]
    9. public void RPCMyServerSideMethod() {
    10.     SomeVerySecretServerSideCode();
    11. }
    12. /*SE*/
    13.  
    I do this everywhere I have server side code that I don't want to have on the client. I can also comment out code sections inside of methods (I'm having quite a few areas where I execute code only when I'm the server, or only when I'm the client, within methods).

    Once this is done (took me just an hour and a half to apply this to all my game code), I can develop "as usual". However, before doing a client build, I replace /*SS*/ with /*SS*//* (now it suddenly starts a comment section), and /*SE*/ with *//*SE*/ (now it suddenly ends a comment section). Obviously, /*SS*/ means "server start" and /*SE*/ means "server end".

    Very simple. Now this little snippet "suddenly" looks like:

    Code (csharp):
    1.  
    2. public void MyClientSideMethod() {
    3.     SomeClientCode();
    4.     networkView.RPC("RPCMyServerSideMethod", RPCMode.Server);
    5. }
    6.  
    7. /*SS*//*
    8. [RPC()]
    9. public void RPCMyServerSideMethod() {
    10.     SomeVerySecretServerSideCode();
    11. }
    12. *//*SE*/
    13.  
    ... which, when compiled turns to something like

    Code (csharp):
    1.  
    2. public void MyClientSideMethod() {
    3.     SomeClientCode();
    4.     networkView.RPC("RPCMyServerSideMethod", RPCMode.Server);
    5. }
    6.  
    ... RPCMyServerSideMethod() is gone. People trying to create a server from my client will now need to re-programm significant parts of the server. If they can do that... hm... then I'll have to wonder why they don't create their own games (if I find out someone does that and creates something that works, I might consider asking them to join my team ;-) )

    So now, after this global search'n'replace ritual, I compile, and BOOM. I get compiler errors if I used code only supposed to be executed on the server from client-side code (or I made a mistake with my "Poor Man's Precompiler Directives" ;-) ). I appreciate that, because not only does this make it impossible for people cracking my client to build a server from it - it also improves my code quality by giving me compiler errors when I did something I shouldn't be doing ;-)

    So this solves two things that were bugging me at once.

    Now I'm very happy :) ... and I thought I'd share this so you can be happy, too ;-)

    Sunny regards,
    Jashan
     
  2. MikeHergaarden

    MikeHergaarden

    Joined:
    Mar 9, 2008
    Posts:
    1,023
    Thanks a lot, I wondered about this before !

    I should now get a proper IDE, visual studio or similar, to apply this.
     
  3. podperson

    podperson

    Joined:
    Jun 6, 2006
    Posts:
    1,370
  4. AngryAnt

    AngryAnt

    Keyboard Operator Moderator

    Joined:
    Oct 25, 2005
    Posts:
    3,045
    I don't see how using C# .dlls to solve this is such a horrible solution?
     
  5. jashan

    jashan

    Joined:
    Mar 9, 2007
    Posts:
    3,304
    Hi Emil,

    How would you do it?

    What I'm after (part of it "longterm") is

    a) compiling multiple versions of the same game with as little effort as possible, and of course, with as little chance for errors as possible (server, master-client, test-client, normal client; normal client in variations Web player, standalone Win, standalone Mac; each version only containing the relevant code, and for now, optimized for a short iteration cycle) - in the long run, I want to click "build" and end up with the zipped binaries with the correct (versioned) names on the server; as client and server code do "almost the same" in many cases, I currently would not want to separate this into client and server classes

    b) changing the configuration (server / test-client, with logging, without logging, with profiling, without profiling etc.) very easily during development for testing

    c) keep the possibility of fixing a problem that occurs during runtime in the editor quickly with Unitron (this one loses importance when the code stabilizes, which is when I'll consider moving more and more stuff to generic DLLs ;-) ) ... or SlickEdit, once I have that set up ;-)


    To make my approach more "standard", I'll convert the comment-replacement (which is obviously a real hack) to #if / #endif with file-based #defines. I'll still have to do the replacement of the #defines with //#define if I want to deactivate it. But once Unity supports passing those defines to the compiler (which I hope it will someday soon), I can simple remove that and live happily ever after ;-)

    That would be the point where having DLLs that need to change depending on the type of build I'm creating would totally break my neck (unless I'm not seeing an elegant alternative solution with DLLs) ;-)

    Until then, I have C#-code ready from which I can create a little command line tool which does the "switching" by doing global search and replace on the code-files in the Assets-folder. I assume that running this from the command line will be equivalent with changing the files from any other external tools ;-) ... once I've tested this extensively (and it does not create any funny behaviors with Unity ;-) ) I'll release that to the community.

    Sunny regards,
    Jashan
     
  6. AngryAnt

    AngryAnt

    Keyboard Operator Moderator

    Joined:
    Oct 25, 2005
    Posts:
    3,045
    I would implement all the RPC handlers in the needed MonoBehaviour classes, but have them all forward the calls to instances of classes (probably singletons - not sure of your needs) defined in my DLL.

    I would then have the DLL rigged with pre-compiler directives - excluding the VerySecretStuff (tm) when it is built with a "client" symbol defined. This means that the client build of the DLL will only contain a bunch of stubs which will hopefully (but not critically) be optimised away in the unity project build process.

    This makes the build process a two-step one:
    :arrow: Build the DLL for either server or client and copy it into the assets folder of your project (for the Path project I have a nifty little double-click solution set up using a shell script and Platypus which handles both the build process and the copying).
    :arrow: Ask unity to build the project.

    Note: It is possible that you'll have to ask unity to rebuild the assets if it does not detect the new dll file.

    Platypus: http://www.sveinbjorn.org/platypus