├── .travis.yml
├── LibSOE
├── Database
│ └── IDatabaseBackend.cs
├── packages.config
├── App.config
├── Globals
│ ├── SOEDisconnectReasons.cs
│ └── SOEOpCodes.cs
├── Interfaces
│ ├── SOEPacket.cs
│ ├── SOEMessage.cs
│ ├── SOEReader.cs
│ └── SOEWriter.cs
├── Properties
│ └── AssemblyInfo.cs
├── LibSOE.csproj
├── Handlers
│ ├── MessageHandlers.cs
│ └── SOEHandlers.cs
└── Core
│ ├── SOEClient.cs
│ ├── SOEConnectionManager.cs
│ ├── SOEServer.cs
│ ├── SOEDataChannel.cs
│ └── SOEProtocol.cs
├── SOEDaemon
├── Program.cs
├── App.config
├── Properties
│ └── AssemblyInfo.cs
└── SOEDaemon.csproj
├── README.md
├── LibSOE.sln
└── .gitignore
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: csharp
2 | solution: LibSOE.sln
--------------------------------------------------------------------------------
/LibSOE/Database/IDatabaseBackend.cs:
--------------------------------------------------------------------------------
1 | namespace SOE.Database
2 | {
3 | public interface IDatabaseBackend
4 | {
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/LibSOE/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/SOEDaemon/Program.cs:
--------------------------------------------------------------------------------
1 | namespace SOEDaemon
2 | {
3 | class Program
4 | {
5 | static void Main(string[] args)
6 | {
7 | // TODO
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/LibSOE/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/SOEDaemon/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # LibSOE
2 | A reverse engineering of Sony Online Entertainment's (or as it's now known as, Daybreak Games) ForgeLight networking library written in C#. (Specifically for traffic from Free Realms)
3 |
4 | [](https://travis-ci.org/Joshsora/LibSOE)
5 |
--------------------------------------------------------------------------------
/LibSOE/Globals/SOEDisconnectReasons.cs:
--------------------------------------------------------------------------------
1 | namespace SOE
2 | {
3 | public enum SOEDisconnectReasons : ushort
4 | {
5 | None,
6 | ICMPError,
7 | Timeout,
8 | Terminated,
9 | MangagerDeleted,
10 | ConnectFail,
11 | Application,
12 | UnreachableConnection,
13 | UnackTimeout,
14 | NewConnection,
15 | ConnectionRefused,
16 | MutualConnectError,
17 | ConnectingToSelf,
18 | ReliableOverflow
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/LibSOE/Globals/SOEOpCodes.cs:
--------------------------------------------------------------------------------
1 | namespace SOE
2 | {
3 | public enum SOEOPCodes : ushort
4 | {
5 | SESSION_REQUEST = 0x01,
6 | SESSION_RESPONSE = 0x02,
7 | MULTI = 0x03,
8 | DISCONNECT = 0x05,
9 | PING = 0x06,
10 |
11 | NET_STATUS_REQUEST = 0x07,
12 | NET_STATUS_RESPONSE = 0x08,
13 |
14 | RELIABLE_DATA = 0x09,
15 | FRAGMENTED_RELIABLE_DATA = 0x0D,
16 | OUT_OF_ORDER_RELIABLE_DATA = 0x11,
17 | ACK_RELIABLE_DATA = 0x15,
18 | MULTI_MESSAGE = 0x19,
19 |
20 | FATAL_ERROR = 0x1D,
21 | FATAL_ERROR_RESPONSE = 0x1E
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/LibSOE/Interfaces/SOEPacket.cs:
--------------------------------------------------------------------------------
1 | namespace SOE.Interfaces
2 | {
3 | public class SOEPacket
4 | {
5 | private ushort OpCode;
6 | private byte[] Raw;
7 |
8 | public SOEPacket(ushort opCode, byte[] rawMessage)
9 | {
10 | OpCode = opCode;
11 | Raw = rawMessage;
12 | }
13 |
14 | public ushort GetOpCode()
15 | {
16 | return OpCode;
17 | }
18 |
19 | public byte[] GetRaw()
20 | {
21 | return Raw;
22 | }
23 |
24 | public int GetLength()
25 | {
26 | return Raw.Length;
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/LibSOE/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("ClientAgent")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("ClientAgent")]
13 | [assembly: AssemblyCopyright("Copyright © 2015")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("4da1f780-d4fd-47b1-b1ff-ec1ea17de602")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/SOEDaemon/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("SOEDaemon")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("SOEDaemon")]
13 | [assembly: AssemblyCopyright("Copyright © 2015")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("52fa7f42-94fa-4fe9-ab3d-4beed1d6752e")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/LibSOE/Interfaces/SOEMessage.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace SOE.Interfaces
4 | {
5 | public class SOEMessage
6 | {
7 | private ushort OpCode;
8 | private byte[] Raw;
9 |
10 | private readonly List Fragments;
11 | public bool IsFragmented;
12 |
13 | public SOEMessage(ushort opCode, byte[] rawMessage)
14 | {
15 | OpCode = opCode;
16 | Raw = rawMessage;
17 |
18 | Fragments = new List();
19 | IsFragmented = false;
20 | }
21 |
22 | public ushort GetOpCode()
23 | {
24 | return OpCode;
25 | }
26 |
27 | public byte[] GetRaw()
28 | {
29 | return Raw;
30 | }
31 |
32 | public int GetLength()
33 | {
34 | return Raw.Length;
35 | }
36 |
37 | public int GetFragmentCount()
38 | {
39 | return Fragments.Count;
40 | }
41 |
42 | public byte[] GetFragment(int i)
43 | {
44 | if (IsFragmented)
45 | {
46 | if (i < Fragments.Count)
47 | {
48 | return Fragments[i];
49 | }
50 | }
51 |
52 | return new byte[0];
53 | }
54 |
55 | public void AddFragment(byte[] fragment)
56 | {
57 | if (!IsFragmented)
58 | {
59 | IsFragmented = true;
60 | }
61 |
62 | Fragments.Add(fragment);
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/LibSOE.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 2013
4 | VisualStudioVersion = 12.0.31101.0
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibSOE", "LibSOE\LibSOE.csproj", "{28A6251E-60CF-4164-8F86-B5216811EEC2}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SOEDaemon", "SOEDaemon\SOEDaemon.csproj", "{D720EE93-03E3-4129-B068-D4692A0F8EAA}"
9 | ProjectSection(ProjectDependencies) = postProject
10 | {28A6251E-60CF-4164-8F86-B5216811EEC2} = {28A6251E-60CF-4164-8F86-B5216811EEC2}
11 | EndProjectSection
12 | EndProject
13 | Global
14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
15 | Debug|Any CPU = Debug|Any CPU
16 | Release|Any CPU = Release|Any CPU
17 | EndGlobalSection
18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
19 | {28A6251E-60CF-4164-8F86-B5216811EEC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
20 | {28A6251E-60CF-4164-8F86-B5216811EEC2}.Debug|Any CPU.Build.0 = Debug|Any CPU
21 | {28A6251E-60CF-4164-8F86-B5216811EEC2}.Release|Any CPU.ActiveCfg = Release|Any CPU
22 | {28A6251E-60CF-4164-8F86-B5216811EEC2}.Release|Any CPU.Build.0 = Release|Any CPU
23 | {D720EE93-03E3-4129-B068-D4692A0F8EAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
24 | {D720EE93-03E3-4129-B068-D4692A0F8EAA}.Debug|Any CPU.Build.0 = Debug|Any CPU
25 | {D720EE93-03E3-4129-B068-D4692A0F8EAA}.Release|Any CPU.ActiveCfg = Release|Any CPU
26 | {D720EE93-03E3-4129-B068-D4692A0F8EAA}.Release|Any CPU.Build.0 = Release|Any CPU
27 | EndGlobalSection
28 | GlobalSection(SolutionProperties) = preSolution
29 | HideSolutionNode = FALSE
30 | EndGlobalSection
31 | EndGlobal
32 |
--------------------------------------------------------------------------------
/SOEDaemon/SOEDaemon.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {D720EE93-03E3-4129-B068-D4692A0F8EAA}
8 | Exe
9 | Properties
10 | SOEDaemon
11 | SOEDaemon
12 | v4.5
13 | 512
14 |
15 |
16 | AnyCPU
17 | true
18 | full
19 | false
20 | bin\Debug\
21 | DEBUG;TRACE
22 | prompt
23 | 4
24 |
25 |
26 | AnyCPU
27 | pdbonly
28 | true
29 | bin\Release\
30 | TRACE
31 | prompt
32 | 4
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
58 |
--------------------------------------------------------------------------------
/LibSOE/LibSOE.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {28A6251E-60CF-4164-8F86-B5216811EEC2}
8 | Library
9 | Properties
10 | SOE
11 | LibSOE
12 | v4.5
13 | 512
14 |
15 |
16 |
17 | AnyCPU
18 | true
19 | full
20 | false
21 | bin\Debug\
22 | DEBUG;TRACE
23 | prompt
24 | 4
25 |
26 |
27 | AnyCPU
28 | pdbonly
29 | true
30 | bin\Release\
31 | TRACE
32 | prompt
33 | 4
34 |
35 |
36 |
37 |
38 |
39 |
40 | ..\packages\DotNetZip.1.9.6\lib\net20\Ionic.Zip.dll
41 |
42 |
43 | ..\Ionic.Zlib.dll
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
86 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | build/
21 | bld/
22 | [Bb]in/
23 | [Oo]bj/
24 |
25 | # Visual Studo 2015 cache/options directory
26 | .vs/
27 |
28 | # MSTest test Results
29 | [Tt]est[Rr]esult*/
30 | [Bb]uild[Ll]og.*
31 |
32 | # NUNIT
33 | *.VisualState.xml
34 | TestResult.xml
35 |
36 | # Build Results of an ATL Project
37 | [Dd]ebugPS/
38 | [Rr]eleasePS/
39 | dlldata.c
40 |
41 | *_i.c
42 | *_p.c
43 | *_i.h
44 | *.ilk
45 | *.meta
46 | *.obj
47 | *.pch
48 | *.pdb
49 | *.pgc
50 | *.pgd
51 | *.rsp
52 | *.sbr
53 | *.tlb
54 | *.tli
55 | *.tlh
56 | *.tmp
57 | *.tmp_proj
58 | *.log
59 | *.vspscc
60 | *.vssscc
61 | .builds
62 | *.pidb
63 | *.svclog
64 | *.scc
65 |
66 | # Chutzpah Test files
67 | _Chutzpah*
68 |
69 | # Visual C++ cache files
70 | ipch/
71 | *.aps
72 | *.ncb
73 | *.opensdf
74 | *.sdf
75 | *.cachefile
76 |
77 | # Visual Studio profiler
78 | *.psess
79 | *.vsp
80 | *.vspx
81 |
82 | # TFS 2012 Local Workspace
83 | $tf/
84 |
85 | # Guidance Automation Toolkit
86 | *.gpState
87 |
88 | # ReSharper is a .NET coding add-in
89 | _ReSharper*/
90 | *.[Rr]e[Ss]harper
91 | *.DotSettings.user
92 |
93 | # JustCode is a .NET coding addin-in
94 | .JustCode
95 |
96 | # TeamCity is a build add-in
97 | _TeamCity*
98 |
99 | # DotCover is a Code Coverage Tool
100 | *.dotCover
101 |
102 | # NCrunch
103 | _NCrunch_*
104 | .*crunch*.local.xml
105 |
106 | # MightyMoose
107 | *.mm.*
108 | AutoTest.Net/
109 |
110 | # Web workbench (sass)
111 | .sass-cache/
112 |
113 | # Installshield output folder
114 | [Ee]xpress/
115 |
116 | # DocProject is a documentation generator add-in
117 | DocProject/buildhelp/
118 | DocProject/Help/*.HxT
119 | DocProject/Help/*.HxC
120 | DocProject/Help/*.hhc
121 | DocProject/Help/*.hhk
122 | DocProject/Help/*.hhp
123 | DocProject/Help/Html2
124 | DocProject/Help/html
125 |
126 | # Click-Once directory
127 | publish/
128 |
129 | # Publish Web Output
130 | *.[Pp]ublish.xml
131 | *.azurePubxml
132 | # TODO: Comment the next line if you want to checkin your web deploy settings
133 | # but database connection strings (with potential passwords) will be unencrypted
134 | *.pubxml
135 | *.publishproj
136 |
137 | # NuGet Packages
138 | *.nupkg
139 | # The packages folder can be ignored because of Package Restore
140 | **/packages/*
141 | # except build/, which is used as an MSBuild target.
142 | !**/packages/build/
143 | # Uncomment if necessary however generally it will be regenerated when needed
144 | #!**/packages/repositories.config
145 |
146 | # Windows Azure Build Output
147 | csx/
148 | *.build.csdef
149 |
150 | # Windows Store app package directory
151 | AppPackages/
152 |
153 | # Others
154 | *.[Cc]ache
155 | ClientBin/
156 | [Ss]tyle[Cc]op.*
157 | ~$*
158 | *~
159 | *.dbmdl
160 | *.dbproj.schemaview
161 | *.pfx
162 | *.publishsettings
163 | node_modules/
164 | bower_components/
165 |
166 | # RIA/Silverlight projects
167 | Generated_Code/
168 |
169 | # Backup & report files from converting an old project file
170 | # to a newer Visual Studio version. Backup files are not needed,
171 | # because we have git ;-)
172 | _UpgradeReport_Files/
173 | Backup*/
174 | UpgradeLog*.XML
175 | UpgradeLog*.htm
176 |
177 | # SQL Server files
178 | *.mdf
179 | *.ldf
180 |
181 | # Business Intelligence projects
182 | *.rdl.data
183 | *.bim.layout
184 | *.bim_*.settings
185 |
186 | # Microsoft Fakes
187 | FakesAssemblies/
188 |
189 | # Node.js Tools for Visual Studio
190 | .ntvs_analysis.dat
191 |
192 | # Visual Studio 6 build log
193 | *.plg
194 |
195 | # Visual Studio 6 workspace options file
196 | *.opt
197 |
--------------------------------------------------------------------------------
/LibSOE/Handlers/MessageHandlers.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Reflection;
5 |
6 | using SOE.Interfaces;
7 |
8 | namespace SOE.Core
9 | {
10 | public delegate void DelSOEMessageHandler(SOEClient sender, SOEMessage message);
11 |
12 | [AttributeUsage(AttributeTargets.Method)]
13 | public class SOEMessageHandler : Attribute
14 | {
15 | public string MessageName;
16 | public string Protocol;
17 | public ushort OpCode;
18 |
19 | public SOEMessageHandler(string messageName, ushort opCode, string protocol="SOE")
20 | {
21 | MessageName = messageName;
22 | Protocol = protocol;
23 | OpCode = opCode;
24 | }
25 | }
26 |
27 | public static class MessageHandlers
28 | {
29 | public static Dictionary> Protocol2MessageName =
30 | new Dictionary>();
31 |
32 | public static Dictionary> Protocol2Handlers =
33 | new Dictionary>();
34 |
35 | private static bool Initalized = false;
36 |
37 | public static void Initialize()
38 | {
39 | if (Initalized)
40 | return;
41 |
42 | LoadMessageHandlers();
43 | Initalized = true;
44 | }
45 |
46 | private static void LoadMessageHandlers()
47 | {
48 | Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
49 | foreach (var assembly in assemblies)
50 | {
51 | var types = assembly.GetTypes();
52 | foreach (var type in types)
53 | {
54 | foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Static))
55 | {
56 | SOEMessageHandler handlerAttribute = (SOEMessageHandler)method
57 | .GetCustomAttributes(typeof(SOEMessageHandler), false).SingleOrDefault();
58 |
59 | if (handlerAttribute != null)
60 | {
61 | Delegate del = Delegate.CreateDelegate(typeof(DelSOEMessageHandler), method);
62 | DelSOEMessageHandler handler = (DelSOEMessageHandler) del;
63 |
64 | if (handlerAttribute.Protocol == "SOE")
65 | {
66 | for (int i = 0; i < Protocol2Handlers.Keys.Count; i++)
67 | {
68 | string protocol = Protocol2Handlers.Keys.ElementAt(i);
69 | Protocol2Handlers[protocol].Add(handlerAttribute.OpCode, handler);
70 | Protocol2MessageName[protocol].Add(handlerAttribute.OpCode, handlerAttribute.MessageName);
71 | }
72 | }
73 | else
74 | {
75 | if (!Protocol2Handlers.ContainsKey(handlerAttribute.Protocol))
76 | {
77 | Protocol2Handlers.Add(handlerAttribute.Protocol, new Dictionary());
78 | }
79 |
80 | Protocol2Handlers[handlerAttribute.Protocol].Add(handlerAttribute.OpCode, handler);
81 | Protocol2MessageName[handlerAttribute.Protocol].Add(handlerAttribute.OpCode, handlerAttribute.MessageName);
82 | }
83 | }
84 | }
85 | }
86 | }
87 | }
88 |
89 | public static bool HasHandler(string protocol, ushort OpCode)
90 | {
91 | if (Protocol2Handlers.ContainsKey(protocol))
92 | {
93 | if (Protocol2Handlers[protocol].ContainsKey(OpCode))
94 | {
95 | return true;
96 | }
97 | }
98 |
99 | return false;
100 | }
101 |
102 | public static DelSOEMessageHandler GetHandler(string protocol, ushort OpCode)
103 | {
104 | return Protocol2Handlers[protocol][OpCode];
105 | }
106 |
107 | public static void MakeProtocol(string protocol)
108 | {
109 | Protocol2Handlers.Add(protocol, new Dictionary());
110 | Protocol2MessageName.Add(protocol, new Dictionary());
111 | }
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/LibSOE/Interfaces/SOEReader.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Text;
3 | using System.IO;
4 |
5 | namespace SOE.Interfaces
6 | {
7 | public class SOEReader
8 | {
9 | private readonly Stream Stream;
10 |
11 | public SOEReader(SOEPacket packet)
12 | {
13 | Stream = new MemoryStream(packet.GetRaw());
14 |
15 | // Skip the SOE OpCode
16 | ReadUInt16();
17 | }
18 |
19 | public SOEReader(SOEMessage message)
20 | {
21 | Stream = new MemoryStream(message.GetRaw());
22 |
23 | // Skip the message OpCode
24 | ReadHostInt16();
25 | }
26 |
27 | public SOEReader(byte[] rawPacket)
28 | {
29 | Stream = new MemoryStream(rawPacket);
30 | }
31 |
32 | public byte ReadByte()
33 | {
34 | byte[] buffer = ReadBytes(1);
35 | return buffer[0];
36 | }
37 |
38 | public bool ReadBoolean()
39 | {
40 | return ReadByte() == 0x01;
41 | }
42 |
43 | public byte[] ReadBytes(int length)
44 | {
45 | byte[] buffer = new byte[length];
46 | Stream.Read(buffer, 0, length);
47 |
48 | return buffer;
49 | }
50 |
51 | public ushort ReadUInt16()
52 | {
53 | byte[] buffer = ReadBytes(2);
54 | return (ushort)(buffer[0] << 8 | buffer[1]);
55 | }
56 |
57 | public uint ReadUInt32()
58 | {
59 | byte[] buffer = ReadBytes(4);
60 | return (uint)(buffer[0] << 24 | buffer[1] << 16 | buffer[2] << 8 | buffer[3]);
61 | }
62 |
63 | public uint ReadUInt64()
64 | {
65 | byte[] buffer = ReadBytes(8);
66 | return (uint)(buffer[0] << 56 | buffer[1] << 48 | buffer[2] << 40 | buffer[3] << 32 | buffer[4] << 24 | buffer[5] << 16 | buffer[6] << 8 | buffer[7]);
67 | }
68 |
69 | public short ReadInt16()
70 | {
71 | byte[] buffer = ReadBytes(2);
72 | return (short)(buffer[0] << 8 | buffer[1]);
73 | }
74 |
75 | public int ReadInt32()
76 | {
77 | byte[] buffer = ReadBytes(4);
78 | return buffer[0] << 24 | buffer[1] << 16 | buffer[2] << 8 | buffer[3];
79 | }
80 |
81 | public ushort ReadHostUInt16()
82 | {
83 | byte[] buffer = ReadBytes(2);
84 | return (ushort)(buffer[1] << 8 | buffer[0]);
85 | }
86 |
87 | public uint ReadHostUInt32()
88 | {
89 | byte[] buffer = ReadBytes(4);
90 | return (uint)(buffer[3] << 24 | buffer[2] << 16 | buffer[1] << 8 | buffer[0]);
91 | }
92 |
93 | public uint ReadHostUInt64()
94 | {
95 | byte[] buffer = ReadBytes(8);
96 | return (uint)(buffer[7] << 56 | buffer[6] << 48 | buffer[5] << 40 | buffer[4] << 32 | buffer[3] << 24 | buffer[2] << 16 | buffer[1] << 8 | buffer[0]);
97 | }
98 |
99 | public short ReadHostInt16()
100 | {
101 | byte[] buffer = ReadBytes(2);
102 | return (short)(buffer[1] << 8 | buffer[0]);
103 | }
104 |
105 | public int ReadHostInt32()
106 | {
107 | byte[] buffer = ReadBytes(4);
108 | return buffer[3] << 24 | buffer[2] << 16 | buffer[1] << 8 | buffer[0];
109 | }
110 |
111 | public string ReadNullTerminatedString()
112 | {
113 | List buffer = new List ();
114 | while (true)
115 | {
116 | byte b = ReadByte();
117 | if (b != (char)0x0)
118 | {
119 | buffer.Add(b);
120 | continue;
121 | }
122 |
123 | break;
124 | }
125 |
126 | return Encoding.ASCII.GetString(buffer.ToArray());
127 | }
128 |
129 | public string ReadASCIIString()
130 | {
131 | uint length = ReadHostUInt32();
132 | byte[] buffer = ReadBytes((int)length);
133 |
134 | return Encoding.ASCII.GetString(buffer);
135 | }
136 |
137 | public string ReadUnicodeString()
138 | {
139 | uint length = ReadHostUInt32();
140 | byte[] buffer = ReadBytes((int)length * 2);
141 |
142 | return Encoding.Unicode.GetString(buffer);
143 | }
144 |
145 | public byte[] ReadBlob()
146 | {
147 | uint length = ReadHostUInt32();
148 | return ReadBytes((int) length);
149 | }
150 |
151 | public byte[] ReadToEnd(uint exclude=0)
152 | {
153 | long length = (Stream.Length - exclude) - Stream.Position;
154 | return ReadBytes((int)length);
155 | }
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/LibSOE/Handlers/SOEHandlers.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using SOE.Interfaces;
3 |
4 | namespace SOE.Core
5 | {
6 | public partial class SOEProtocol
7 | {
8 | public void HandleSessionRequest(SOEClient sender, SOEPacket packet)
9 | {
10 | // Setup a reader
11 | SOEReader reader = new SOEReader(packet);
12 |
13 | // Get the data from the packet
14 | uint crcLength = reader.ReadUInt32();
15 | uint sessionID = reader.ReadUInt32();
16 | uint udpBufferSize = reader.ReadUInt32();
17 | string protocol = reader.ReadNullTerminatedString();
18 |
19 | // Is the client using the correct protocol?
20 | if (ProtocolString == protocol)
21 | {
22 | // Can we encrypt/compress?
23 | bool encryptable = false;
24 | bool compressable = true;
25 |
26 | // Start the session and manage the client
27 | sender.StartSession(crcLength, sessionID, udpBufferSize);
28 | sender.SetCompressable(compressable);
29 | sender.SetEncryptable(encryptable);
30 |
31 | Server.ConnectionManager.AddNewClient(sender);
32 |
33 | // Setup a writer
34 | SOEWriter writer = new SOEWriter((ushort)SOEOPCodes.SESSION_RESPONSE);
35 |
36 | // Write a response
37 | writer.AddUInt32(sessionID);
38 | writer.AddUInt32(sender.GetCRCSeed());
39 | writer.AddByte((byte)crcLength);
40 | writer.AddBoolean(compressable);
41 | writer.AddBoolean(encryptable);
42 | writer.AddUInt32(udpBufferSize);
43 | writer.AddUInt32(3);
44 |
45 | // Get the response
46 | SOEPacket response = writer.GetFinalSOEPacket(sender, false, false);
47 |
48 | // Send the response!
49 | sender.SendPacket(response);
50 | }
51 | else
52 | {
53 | // They aren't using the right protocol...
54 | Log("Got connection request from client with incorrect protocol. Client: {0}, Server: {1}", protocol, ProtocolString);
55 | }
56 | }
57 |
58 | public void HandleDisconnect(SOEClient sender, SOEPacket packet)
59 | {
60 | // Setup a reader
61 | SOEReader reader = new SOEReader(packet);
62 |
63 | // Get the data from the packet
64 | uint sessionID = reader.ReadUInt32();
65 | ushort reason = reader.ReadUInt16();
66 |
67 | // Handle
68 | if (sessionID == sender.GetSessionID())
69 | {
70 | Console.WriteLine("Disconnecting");
71 | Server.ConnectionManager.DisconnectClient(sender, reason, true);
72 | }
73 | }
74 |
75 | public void HandlePing(SOEClient sender)
76 | {
77 | // Setup a writer
78 | SOEWriter writer = new SOEWriter((ushort)SOEOPCodes.PING);
79 | SOEPacket pong = writer.GetFinalSOEPacket(sender, false, false);
80 |
81 | // Send a pong!
82 | sender.SendPacket(pong);
83 | }
84 |
85 | [SOEMessageHandler("MULTI_MESSAGE", (ushort)SOEOPCodes.MULTI_MESSAGE)]
86 | public void HandleMultiData(SOEClient sender, SOEMessage message)
87 | {
88 | // Setup a reader and skip the OpCode
89 | SOEReader reader = new SOEReader(message);
90 | int offset = 2;
91 |
92 | // Get the data length
93 | int dataLength = message.GetLength();
94 |
95 | // Get the packets
96 | while (offset < dataLength)
97 | {
98 | // Message size
99 | int MessageSize = reader.ReadByte();
100 | byte extendedMessageAmount = 0;
101 |
102 | // If the first byte is 0xFF then:
103 | // Read how many bytes to add, and then add that many
104 | if (MessageSize == 0xFF)
105 | {
106 | // How many bytes are there?
107 | extendedMessageAmount = reader.ReadByte();
108 |
109 | // Add that many bytes
110 | for (int i = 0; i < extendedMessageAmount; i++)
111 | {
112 | MessageSize += reader.ReadByte();
113 | }
114 |
115 | extendedMessageAmount++;
116 | }
117 |
118 | // Read the Message data from the size
119 | byte[] data = reader.ReadBytes(MessageSize);
120 |
121 | // Handle the Message
122 | sender.ReceiveMessage(data);
123 |
124 | // Move along
125 | offset += MessageSize + extendedMessageAmount + 1;
126 | }
127 | }
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/LibSOE/Core/SOEClient.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Net;
4 |
5 | using SOE.Interfaces;
6 |
7 | namespace SOE.Core
8 | {
9 | public class SOEClient
10 | {
11 | // Components
12 | public SOEServer Server;
13 | public SOEConnectionManager Manager;
14 | public SOEDataChannel DataChannel;
15 | public IPEndPoint Client;
16 |
17 | // Session properties
18 | private bool SessionStarted;
19 | private uint SessionID;
20 | private uint CRCLength;
21 | private uint BufferSize;
22 | private uint CRCSeed;
23 | private bool Encryptable;
24 | private bool Compressable;
25 |
26 | // Server properties
27 | private int ClientID = -1;
28 | private int LastInteraction;
29 | private bool Encrypted;
30 |
31 | public SOEClient(SOEConnectionManager manager, IPEndPoint client)
32 | {
33 | // Manager and server
34 | Server = manager.Server;
35 | Manager = manager;
36 | Client = client;
37 | DataChannel = new SOEDataChannel(this);
38 |
39 | // Session
40 | SessionStarted = false;
41 | Encryptable = false;
42 | Compressable = true;
43 |
44 | Encrypted = false;
45 |
46 | // This client is new
47 | Interact();
48 | }
49 |
50 | public int GetClientID()
51 | {
52 | return ClientID;
53 | }
54 |
55 | public void SetClientID(int clientId)
56 | {
57 | // New client ID
58 | ClientID = clientId;
59 |
60 | // This client is still alive
61 | Interact();
62 | }
63 |
64 | public IPAddress GetClientAddress()
65 | {
66 | return Client.Address;
67 | }
68 |
69 | public void StartSession(uint crcLength, uint sessionId, uint udpBufferSize)
70 | {
71 | // Generate a CRC Seed for this session
72 | // CRCSeed = (uint)(new Random().Next(int.MaxValue));
73 | CRCSeed = 1884358976;
74 |
75 | // Session variables
76 | CRCLength = crcLength;
77 | SessionID = sessionId;
78 | BufferSize = udpBufferSize;
79 | SessionStarted = true;
80 |
81 | // By default, all sessions start compressable..
82 | Compressable = true;
83 |
84 | // By default, all sessions start un-encryptable..
85 | Encryptable = false;
86 | Encrypted = false;
87 | }
88 |
89 | public bool HasSession()
90 | {
91 | return SessionStarted;
92 | }
93 |
94 | public uint GetCRCLength()
95 | {
96 | return CRCLength;
97 | }
98 |
99 | public uint GetSessionID()
100 | {
101 | return SessionID;
102 | }
103 |
104 | public uint GetBufferSize()
105 | {
106 | return BufferSize;
107 | }
108 |
109 | public uint GetCRCSeed()
110 | {
111 | return CRCSeed;
112 | }
113 |
114 | public bool IsEncrypted()
115 | {
116 | return Encrypted;
117 | }
118 |
119 | public void SetEncryptable(bool encryptable)
120 | {
121 | Encryptable = encryptable;
122 | }
123 |
124 | public void ToggleEncryption()
125 | {
126 | if (Encryptable)
127 | {
128 | Encrypted = !Encrypted;
129 | }
130 | }
131 |
132 | public bool IsCompressable()
133 | {
134 | return Compressable;
135 | }
136 |
137 | public void SetCompressable(bool compressable)
138 | {
139 | Compressable = compressable;
140 | }
141 |
142 | public byte[] Encrypt(byte[] data)
143 | {
144 | return Server.Protocol.Encrypt(this, data);
145 | }
146 |
147 | public byte[] Compress(byte[] data)
148 | {
149 | return Server.Protocol.Compress(data);
150 | }
151 |
152 | public byte[] GetAppendedCRC32(byte[] packet)
153 | {
154 | // Used to store the bytes we get
155 | byte[] finalCRCBytes = new byte[CRCLength];
156 |
157 | // Get the CRC
158 | uint crc = GetCRC32Checksum(packet);
159 |
160 | // Get the bytes for the CRC, and append the neccasary ones.
161 | int place = 0;
162 | byte[] crcBytes = BitConverter.GetBytes(crc).Reverse().ToArray();
163 | for (int i = 4 - (int)CRCLength; i < crcBytes.Length; i++)
164 | {
165 | finalCRCBytes[place] = crcBytes[i];
166 | place++;
167 | }
168 |
169 | // Return the bytes we need to
170 | return finalCRCBytes;
171 | }
172 |
173 | public uint GetCRC32Checksum(byte[] packet)
174 | {
175 | // Use the Protocol object to calculate the CRC
176 | return Server.Protocol.GetCRC32Checksum(CRCSeed, packet);
177 | }
178 |
179 | public void Disconnect(ushort reason, bool client = false)
180 | {
181 | // Dead client or intentional disconnect...
182 | Manager.DisconnectClient(this, reason, client);
183 | }
184 |
185 | public void SendPacket(SOEPacket packet)
186 | {
187 | // Send the packet
188 | Server.SendPacket(this, packet);
189 |
190 | // This client is still alive
191 | Interact();
192 | }
193 |
194 | public void SendMessage(SOEMessage message)
195 | {
196 | // Send the message through the data channel
197 | DataChannel.Send(message);
198 |
199 | // This client is still alive
200 | Interact();
201 | }
202 |
203 | public void ReceiveMessage(byte[] rawMessage)
204 | {
205 | // We've received a message! Tell the server!
206 | Server.ReceiveMessage(this, rawMessage);
207 |
208 | // This client is still alive
209 | Interact();
210 | }
211 |
212 | public int GetLastInteraction()
213 | {
214 | // Return the last time we were interacted with..
215 | return LastInteraction;
216 | }
217 |
218 | public ushort GetNextSequenceNumber()
219 | {
220 | // This is kinda dangerous as it assumes that the message is going to be sent.
221 | return DataChannel.GetNextSequenceNumber();
222 | }
223 |
224 | private void Interact()
225 | {
226 | // Set our last interaction so we don't get destroyed
227 | LastInteraction = (int)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds;
228 | }
229 | }
230 | }
231 |
--------------------------------------------------------------------------------
/LibSOE/Core/SOEConnectionManager.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading;
4 | using System.Net;
5 |
6 | using SOE.Interfaces;
7 |
8 | namespace SOE.Core
9 | {
10 | public class SOEConnectionManager
11 | {
12 | // Server component
13 | public SOEServer Server;
14 |
15 | // Connections
16 | private readonly List Clients;
17 | private readonly Dictionary Host2ClientID;
18 | private readonly Dictionary SessionID2ClientID;
19 |
20 | public SOEConnectionManager(SOEServer server)
21 | {
22 | // Server
23 | Server = server;
24 |
25 | // Our client lists
26 | Clients = new List();
27 | Host2ClientID = new Dictionary();
28 | SessionID2ClientID = new Dictionary();
29 |
30 | // Log
31 | Log("Service constructed");
32 | }
33 |
34 | public void AddNewClient(SOEClient newClient)
35 | {
36 | // Do they exist already?
37 | if (SessionID2ClientID.ContainsKey(newClient.GetSessionID()))
38 | {
39 | // Disconnect the new client
40 | Log("[WARNING] Someone tried connecting with the same Session ID!");
41 | newClient.Disconnect((ushort)SOEDisconnectReasons.ConnectFail);
42 |
43 | // Don't continue adding this connection
44 | return;
45 | }
46 |
47 | // Is there already a connection from this endpoint?
48 | if (Host2ClientID.ContainsKey(newClient.Client))
49 | {
50 | // Disconnect the new client
51 | Log("[WARNING] Someone tried connecting from the same endpoint!");
52 | newClient.Disconnect((ushort)SOEDisconnectReasons.ConnectFail);
53 |
54 | // Don't continue adding this connection
55 | return;
56 | }
57 |
58 | // Loop through the Clients list, looking for an open space
59 | int newClientId;
60 | for (newClientId = 0; newClientId < Clients.Count; newClientId++)
61 | {
62 | // Is this client nulled?
63 | if (Clients[newClientId] == null)
64 | {
65 | // We've found an empty space!
66 | break;
67 | }
68 | }
69 |
70 | // Set their Client ID
71 | newClient.SetClientID(newClientId);
72 |
73 | // Add them to the Clients map
74 | if (newClientId >= Clients.Count)
75 | {
76 | Clients.Add(newClient);
77 | }
78 | else
79 | {
80 | Clients[newClientId] = newClient;
81 | }
82 |
83 | // Add them to our maps
84 | Host2ClientID.Add(newClient.Client, newClientId);
85 | SessionID2ClientID.Add(newClient.GetSessionID(), newClientId);
86 |
87 | // Log
88 | Log("New client connection from {0}, (ID: {1})", newClient.GetClientAddress(), newClient.GetClientID());
89 | }
90 |
91 | public SOEClient GetClient(int clientId)
92 | {
93 | // Is the requested index within our List?
94 | if (clientId < Clients.Count)
95 | {
96 | // Return the associated client
97 | return Clients[clientId];
98 | }
99 |
100 | // Return a null client
101 | return null;
102 | }
103 |
104 | public SOEClient GetClientFromSessionID(uint sessionId)
105 | {
106 | // Does this SessionID exist?
107 | if (SessionID2ClientID.ContainsKey(sessionId))
108 | {
109 | // Return the associated client
110 | return Clients[SessionID2ClientID[sessionId]];
111 | }
112 |
113 | // Return a null client
114 | return null;
115 | }
116 |
117 | public SOEClient GetClientFromHost(IPEndPoint client)
118 | {
119 | // Do we have a connection from this endpoint?
120 | if (Host2ClientID.ContainsKey(client))
121 | {
122 | // Return the associated client
123 | return Clients[Host2ClientID[client]];
124 | }
125 |
126 | // Return a null client
127 | return null;
128 | }
129 |
130 | public void DisconnectClient(SOEClient client, ushort reason, bool clientBased = false)
131 | {
132 | // Disconnect
133 | Log("Disconnecting client on {0} (ID: {1}) for reason: {2}", client.GetClientAddress(), client.GetClientID(), (SOEDisconnectReasons)reason);
134 |
135 | // Are they a connected client?
136 | if (Clients.Contains(client))
137 | {
138 | // We don't care about them anymore
139 | // Open their ID as a space
140 | Host2ClientID.Remove(client.Client);
141 | SessionID2ClientID.Remove(client.GetSessionID());
142 | Clients[client.GetClientID()] = null;
143 | }
144 |
145 | // Was this a disconnect request from the client itself?
146 | if (!clientBased)
147 | {
148 | // Tell them we're disconnecting them
149 | SOEWriter packetWriter = new SOEWriter((ushort)SOEOPCodes.DISCONNECT);
150 |
151 | // Arguments
152 | packetWriter.AddUInt32(client.GetSessionID());
153 | packetWriter.AddUInt16(reason);
154 |
155 | // Send!
156 | SOEPacket packet = packetWriter.GetFinalSOEPacket(client, true, false);
157 | client.SendPacket(packet);
158 | }
159 | }
160 |
161 | public void StartKeepAliveThread()
162 | {
163 | Thread keepAliveThread = new Thread((threadStart3) =>
164 | {
165 | while (Server.Running)
166 | {
167 | // Get a Now time for this cycle
168 | int now = (int)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds;
169 |
170 | // Loop through the clients
171 | for (int i = 0; i < Clients.Count; i++)
172 | {
173 | // Client
174 | SOEClient client = GetClient(i);
175 |
176 | // Empty space?
177 | if (client == null)
178 | {
179 | continue;
180 | }
181 |
182 | // Idle?
183 | if (now > (client.GetLastInteraction() + Server.CLIENT_TIMEOUT))
184 | {
185 | Log("Disconnecting Idle client.");
186 | client.Disconnect((ushort)SOEDisconnectReasons.Timeout);
187 | }
188 | }
189 |
190 | Thread.Sleep(Server.SERVER_THREAD_SLEEP);
191 | }
192 | });
193 | keepAliveThread.Name = "SOEServer::KeepAliveThread";
194 | keepAliveThread.Start();
195 | }
196 |
197 | public void Log(string message, params object[] args)
198 | {
199 | Console.WriteLine(":SOEConnectionManager: " + message, args);
200 | }
201 | }
202 | }
203 |
--------------------------------------------------------------------------------
/LibSOE/Core/SOEServer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net;
3 | using System.Net.Sockets;
4 | using System.Collections.Concurrent;
5 | using System.Threading;
6 |
7 | using SOE.Interfaces;
8 |
9 | namespace SOE.Core
10 | {
11 | public struct SOEPendingPacket
12 | {
13 | public SOEClient Client;
14 | public byte[] Packet;
15 |
16 | public SOEPendingPacket(SOEClient sender, byte[] packet)
17 | {
18 | Client = sender;
19 | Packet = packet;
20 | }
21 | }
22 |
23 | public struct SOEPendingMessage
24 | {
25 | public SOEClient Client;
26 | public byte[] Message;
27 |
28 | public SOEPendingMessage(SOEClient sender, byte[] packet)
29 | {
30 | Client = sender;
31 | Message = packet;
32 | }
33 | }
34 |
35 | public class SOEServer
36 | {
37 | // Server components
38 | private readonly UdpClient UdpClient;
39 | public readonly SOEConnectionManager ConnectionManager;
40 | public readonly SOEProtocol Protocol;
41 |
42 | private readonly ConcurrentQueue IncomingPackets;
43 | private readonly ConcurrentQueue IncomingMessages;
44 |
45 | // Server variables
46 | public readonly bool Running = true;
47 | private readonly int Port = 0;
48 |
49 | // Settings
50 | public string GAME_NAME = "SOE";
51 |
52 | public int CLIENT_TIMEOUT = 15;
53 | public int SERVER_THREAD_SLEEP = 13;
54 |
55 | public int THREAD_POOL_SIZE = 8;
56 | public bool WANT_PACKET_THREADING = true;
57 | public bool WANT_MESSAGE_THREADING = true;
58 |
59 | public SOEServer(int port, string protocol="SOE")
60 | {
61 | // Log
62 | Log("Initiating server on port: {0}", port);
63 |
64 | // UDP Listener
65 | UdpClient = new UdpClient(port);
66 | Port = port;
67 |
68 | // Server components
69 | ConnectionManager = new SOEConnectionManager(this);
70 | Protocol = new SOEProtocol(this, protocol);
71 |
72 | IncomingPackets = new ConcurrentQueue();
73 | IncomingMessages = new ConcurrentQueue();
74 |
75 | // Initialize our message handlers
76 | Log("Initializing message handlers");
77 | MessageHandlers.Initialize();
78 | Log("Initiated server");
79 | }
80 |
81 | private void DoNetCycle()
82 | {
83 | // Receive a packet
84 | IPEndPoint sender = new IPEndPoint(IPAddress.Any, Port);
85 | byte[] rawPacket;
86 |
87 | try
88 | {
89 | rawPacket = UdpClient.Receive(ref sender);
90 | }
91 | catch (SocketException)
92 | {
93 | // Maybe we just killed the client?
94 | return;
95 | }
96 |
97 | // Get the associated client (or create a new fake one)
98 | SOEClient client = ConnectionManager.GetClientFromHost(sender);
99 | if (client == null)
100 | {
101 | // Make a fake client for new connections
102 | client = new SOEClient(ConnectionManager, sender);
103 | }
104 |
105 | // Do we wanna handle this, or give it to our workers?
106 | if (WANT_PACKET_THREADING)
107 | {
108 | // Put it in the queue for our workers..
109 | IncomingPackets.Enqueue(new SOEPendingPacket(client, rawPacket));
110 | }
111 | else
112 | {
113 | // Handle the packet
114 | Protocol.HandlePacket(client, rawPacket);
115 | }
116 | }
117 |
118 | public void SendPacket(SOEClient client, SOEPacket packet)
119 | {
120 | // Send the message
121 | UdpClient.Send(packet.GetRaw(), packet.GetLength(), client.Client);
122 | }
123 |
124 | public void ReceiveMessage(SOEClient sender, byte[] rawMessage)
125 | {
126 | if (WANT_MESSAGE_THREADING)
127 | {
128 | IncomingMessages.Enqueue(new SOEPendingMessage(sender, rawMessage));
129 | }
130 | else
131 | {
132 | Protocol.HandleMessage(sender, rawMessage);
133 | }
134 | }
135 |
136 | public void Run()
137 | {
138 | // Server threads
139 | Log("Starting server threads");
140 | Thread netThread = new Thread((threadStart) =>
141 | {
142 | while (Running)
143 | {
144 | // Do a cycle
145 | DoNetCycle();
146 |
147 | // Sleep
148 | Thread.Sleep(SERVER_THREAD_SLEEP);
149 | }
150 | });
151 | netThread.Name = "SOEServer::NetThread";
152 | netThread.Start();
153 |
154 | // Create the packet worker threads
155 | if (WANT_PACKET_THREADING)
156 | {
157 | for (int i = 0; i < THREAD_POOL_SIZE; i++)
158 | {
159 | Thread workerThread = new Thread((workerThreadStart) =>
160 | {
161 | while (Running)
162 | {
163 | // Get a packet and handle it.
164 | SOEPendingPacket packet;
165 |
166 | if (IncomingPackets.TryDequeue(out packet))
167 | {
168 | Protocol.HandlePacket(packet.Client, packet.Packet);
169 | }
170 |
171 | // Sleep
172 | Thread.Sleep(SERVER_THREAD_SLEEP);
173 | }
174 | });
175 |
176 | workerThread.Name = string.Format("SOEServer::PacketWorkerThread{0}", i + 1);
177 | workerThread.Start();
178 | }
179 | }
180 |
181 | // Create the message worker threads
182 | if (WANT_PACKET_THREADING)
183 | {
184 | for (int i = 0; i < THREAD_POOL_SIZE; i++)
185 | {
186 | Thread workerThread = new Thread((workerThreadStart) =>
187 | {
188 | while (Running)
189 | {
190 | // Get a packet and handle it.
191 | SOEPendingMessage message;
192 |
193 | if (IncomingMessages.TryDequeue(out message))
194 | {
195 | Protocol.HandleMessage(message.Client, message.Message);
196 | }
197 |
198 | // Sleep
199 | Thread.Sleep(SERVER_THREAD_SLEEP);
200 | }
201 | });
202 |
203 | workerThread.Name = string.Format("SOEServer::MessageWorkerThread{0}", i + 1);
204 | workerThread.Start();
205 | }
206 | }
207 |
208 | // Create the idle connection thread
209 | ConnectionManager.StartKeepAliveThread();
210 |
211 | // Done
212 | Log("Started listening");
213 | }
214 |
215 | public void Log(string message, params object[] args)
216 | {
217 | Console.WriteLine(":SOEServer: " + message, args);
218 | }
219 | }
220 | }
221 |
--------------------------------------------------------------------------------
/LibSOE/Core/SOEDataChannel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | using SOE.Interfaces;
5 |
6 | namespace SOE.Core
7 | {
8 | public class SOEDataChannel
9 | {
10 | // Components
11 | public SOEClient Client;
12 |
13 | // Last client-sent reliable data
14 | private ushort LastReceivedSequenceNumber;
15 |
16 | // Server-sent reliable data
17 | private int LastDataSendTime;
18 | private ushort NextSequenceNumber;
19 |
20 | // Fragmented
21 | private bool StartedFragmentedPacket;
22 | private ushort FragmentSequenceNumber;
23 | private uint ReceivedFragmentsSize;
24 | private byte[] FragmentedData;
25 | private byte FragmentsTillAck;
26 |
27 | private bool BusySendingFragmentedPacket;
28 | private Queue FragmentedQueue;
29 |
30 | public SOEDataChannel(SOEClient client)
31 | {
32 | // Associated client
33 | Client = client;
34 |
35 | // Defaults
36 | LastReceivedSequenceNumber = 0;
37 | LastDataSendTime = 0;
38 | NextSequenceNumber = 0;
39 | StartedFragmentedPacket = false;
40 | FragmentSequenceNumber = 0;
41 | ReceivedFragmentsSize = 0;
42 | FragmentsTillAck = 6;
43 | BusySendingFragmentedPacket = false;
44 | FragmentedQueue = new Queue();
45 | }
46 |
47 | private void Acknowledge(ushort sequenceNumber)
48 | {
49 | // Setup a writer
50 | SOEWriter writer = new SOEWriter((ushort)SOEOPCodes.ACK_RELIABLE_DATA);
51 |
52 | // Compressed? (Always false)
53 | writer.AddBoolean(false);
54 |
55 | // Add the sequence number
56 | writer.AddUInt16(sequenceNumber);
57 |
58 | // Send the packet
59 | SOEPacket packet = writer.GetFinalSOEPacket(Client, true, true);
60 | Client.SendPacket(packet);
61 | }
62 |
63 | private void ReceivedSequenceOutOfOrder(ushort sequenceNumber)
64 | {
65 | // Setup a writer
66 | SOEWriter writer = new SOEWriter((ushort)SOEOPCodes.OUT_OF_ORDER_RELIABLE_DATA);
67 |
68 | // Where abouts did the sending mess up?
69 | writer.AddUInt16(sequenceNumber);
70 |
71 | // Send the packet
72 | SOEPacket packet = writer.GetFinalSOEPacket(Client, true, true);
73 | Client.SendPacket(packet);
74 | }
75 |
76 | private void ReceiveFragment(SOEPacket packet)
77 | {
78 | // Setup a reader
79 | SOEReader reader = new SOEReader(packet);
80 | reader.ReadUInt16();
81 |
82 | // Have we already started a fragmented packet?
83 | if (StartedFragmentedPacket)
84 | {
85 | // One less fragment till we need to acknowledge!
86 | FragmentsTillAck--;
87 |
88 | // Get our sequence number
89 | uint previousFragmentSequenceNumber = FragmentSequenceNumber;
90 | FragmentSequenceNumber = reader.ReadUInt16();
91 |
92 | // Did we get a correct sequence number?
93 | if (FragmentSequenceNumber != previousFragmentSequenceNumber + 1)
94 | {
95 | // Out of order!
96 | ReceivedSequenceOutOfOrder(FragmentSequenceNumber);
97 | return;
98 | }
99 |
100 | // Append the rest of the packet to the fragmented data
101 | for (int i = 4; i < FragmentedData.Length; i++)
102 | {
103 | FragmentedData[ReceivedFragmentsSize] = reader.ReadByte();
104 | ReceivedFragmentsSize++;
105 | }
106 | }
107 | else
108 | {
109 | // We're expecting the starting packet
110 | FragmentSequenceNumber = reader.ReadUInt16();
111 | uint totalSize = reader.ReadUInt32();
112 |
113 | // Is this a valid sequence number?
114 | if ((FragmentSequenceNumber != LastReceivedSequenceNumber + 1) && (FragmentSequenceNumber != 0))
115 | {
116 | // Out of order!
117 | ReceivedSequenceOutOfOrder(FragmentSequenceNumber);
118 | return;
119 | }
120 |
121 | // Get the total size
122 | FragmentedData = new byte[totalSize];
123 |
124 | // How many fragments till we need to acknowledge
125 | FragmentsTillAck = 4;
126 |
127 | // Append the rest of the packet to the fragmented data
128 | for (int i = 8; i < FragmentedData.Length; i++)
129 | {
130 | FragmentedData[ReceivedFragmentsSize] = reader.ReadByte();
131 | ReceivedFragmentsSize++;
132 | }
133 |
134 | // Started a fragmented packet
135 | StartedFragmentedPacket = true;
136 | }
137 |
138 | // Are we finished with the fragmented data?
139 | if (ReceivedFragmentsSize >= FragmentedData.Length)
140 | {
141 | // Finish fragmented packet
142 | StartedFragmentedPacket = false;
143 | FragmentsTillAck = 0;
144 |
145 | // Handle the fragmented packet as a RELIABLE_DATA packet
146 | SOEWriter writer = new SOEWriter((ushort)SOEOPCodes.RELIABLE_DATA);
147 | writer.AddBytes(FragmentedData);
148 |
149 | SOEPacket wholePacket = writer.GetFinalSOEPacket(Client, false, false);
150 |
151 | // Receive this packet!
152 | Receive(wholePacket);
153 | return;
154 | }
155 |
156 | // Do we need to acknowledge?
157 | if (FragmentsTillAck == 0)
158 | {
159 | Acknowledge(FragmentSequenceNumber);
160 | FragmentsTillAck = 5;
161 | }
162 | }
163 |
164 | private void ReceiveMessage(SOEPacket packet)
165 | {
166 | SOEReader reader = new SOEReader(packet);
167 |
168 | // Have we received in order?
169 | ushort sequenceNumber = reader.ReadUInt16();
170 | if ((sequenceNumber != LastReceivedSequenceNumber + 1) && (sequenceNumber != 0))
171 | {
172 | ReceivedSequenceOutOfOrder(sequenceNumber);
173 | return;
174 | }
175 |
176 | // Acknowledge
177 | Acknowledge(sequenceNumber);
178 | LastReceivedSequenceNumber = sequenceNumber;
179 |
180 | // Get the SOEMessage
181 | byte[] data = reader.ReadToEnd();
182 |
183 | // Handle!
184 | Client.ReceiveMessage(data);
185 | }
186 |
187 | public void Receive(SOEPacket packet)
188 | {
189 | ushort opCode = packet.GetOpCode();
190 | if (opCode == (ushort)SOEOPCodes.FRAGMENTED_RELIABLE_DATA)
191 | {
192 | ReceiveFragment(packet);
193 | }
194 | else if (opCode == (ushort)SOEOPCodes.RELIABLE_DATA)
195 | {
196 | ReceiveMessage(packet);
197 | }
198 | else if (opCode == (ushort) SOEOPCodes.ACK_RELIABLE_DATA)
199 | {
200 | // TODO: Handle repeat-until-acknowledged and all that comes with it.
201 | Log("Data Ack");
202 | }
203 | else
204 | {
205 | // Shrug ¯\_(ツ)_/¯
206 | Log("Received a packet that was not data or acknowledge. Discarding..");
207 | }
208 | }
209 |
210 | private void SendFragmentedMessage(SOEMessage message)
211 | {
212 | // Are we already busy?
213 | if (BusySendingFragmentedPacket)
214 | {
215 | // The already-busy thread will pick up our message..
216 | FragmentedQueue.Enqueue(message);
217 | return;
218 | }
219 |
220 | // Set that we're busy IMMEDIATELY! (thread-safe)
221 | BusySendingFragmentedPacket = true;
222 |
223 | // Setup the for loop
224 | SOEWriter writer;
225 | SOEPacket packet;
226 | ushort sequenceNumber;
227 | bool sentInitial = false;
228 |
229 | // The rest aren't any different
230 | for (int i = 0; i < message.GetFragmentCount(); i++)
231 | {
232 | // Setup a new writer
233 | writer = new SOEWriter((ushort)SOEOPCodes.FRAGMENTED_RELIABLE_DATA);
234 |
235 | // Are we the first packet?
236 | if (!sentInitial)
237 | {
238 | // Add the total message length
239 | writer.AddUInt32((uint)message.GetLength());
240 | sentInitial = true;
241 | }
242 |
243 | // Sequence number
244 | sequenceNumber = GetNextSequenceNumber();
245 | writer.AddUInt16(sequenceNumber);
246 |
247 | // Add the message fragment
248 | writer.AddBytes(message.GetFragment(i));
249 |
250 | // Get the final packet and send it!
251 | packet = writer.GetFinalSOEPacket(Client, true, true);
252 | Client.SendPacket(packet);
253 | }
254 |
255 | // Did any other thread add a fragmented packet?
256 | if (FragmentedQueue.Count > 0)
257 | {
258 | BusySendingFragmentedPacket = false;
259 | SendFragmentedMessage(FragmentedQueue.Dequeue());
260 | }
261 | }
262 |
263 | private void SendMessage(SOEMessage message)
264 | {
265 | // Setup a writer
266 | SOEWriter writer = new SOEWriter((ushort)SOEOPCodes.RELIABLE_DATA);
267 |
268 | // Sequence number
269 | ushort sequenceNumber = GetNextSequenceNumber();
270 | writer.AddUInt16(sequenceNumber);
271 |
272 | // Add the message
273 | writer.AddMessage(message);
274 |
275 | // Get the final packet and send it!
276 | SOEPacket packet = writer.GetFinalSOEPacket(Client, true, true);
277 | Client.SendPacket(packet);
278 |
279 | // TODO repeat-till-acknowledged
280 | }
281 |
282 | public void Send(SOEMessage message)
283 | {
284 | if (message.IsFragmented)
285 | {
286 | SendFragmentedMessage(message);
287 | }
288 | else
289 | {
290 | SendMessage(message);
291 | }
292 | }
293 |
294 | public ushort GetNextSequenceNumber()
295 | {
296 | ushort sequenceNumber = NextSequenceNumber;
297 | if (NextSequenceNumber == 0xFFFF)
298 | {
299 | NextSequenceNumber = 0;
300 | }
301 | else
302 | {
303 | NextSequenceNumber++;
304 | }
305 |
306 | return sequenceNumber;;
307 | }
308 |
309 | public void Log(string message, params object[] args)
310 | {
311 | string msg = string.Format(":SOEDataChannel(Client: {0}): ", Client.GetClientID()) + message;
312 | Console.WriteLine(msg, args);
313 | }
314 | }
315 | }
316 |
--------------------------------------------------------------------------------
/LibSOE/Interfaces/SOEWriter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | using SOE.Core;
7 |
8 | namespace SOE.Interfaces
9 | {
10 | public class SOEWriter
11 | {
12 | // Message details
13 | private ushort OpCode;
14 | private bool IsMessage;
15 |
16 | // Message
17 | private List Data;
18 |
19 | public SOEWriter()
20 | {
21 | // Message information
22 | Data = new List();
23 | OpCode = 0;
24 |
25 | // We're some kinda data, not a message or packet..
26 | IsMessage = false;
27 | }
28 |
29 | public SOEWriter(ushort opCode, bool isMessage=false)
30 | {
31 | // Message information
32 | Data = new List();
33 | IsMessage = isMessage;
34 | OpCode = opCode;
35 |
36 | // Add our OpCode
37 | if (IsMessage)
38 | {
39 | AddHostUInt16(opCode);
40 | }
41 | else
42 | {
43 | AddUInt16(opCode);
44 | }
45 | }
46 |
47 | public SOEWriter(SOEPacket packet)
48 | {
49 | // Message information
50 | Data = new List(packet.GetRaw());
51 | OpCode = packet.GetOpCode();
52 |
53 | // We're a packet, not a message
54 | IsMessage = false;
55 | }
56 |
57 | public SOEWriter(SOEMessage message)
58 | {
59 | // Message information
60 | Data = new List(message.GetRaw());
61 | OpCode = message.GetOpCode();
62 |
63 | // We're a message!
64 | IsMessage = true;
65 | }
66 |
67 | public void AddByte(byte value)
68 | {
69 | Data.Add(value);
70 | }
71 |
72 | public void AddBytes(byte[] value)
73 | {
74 | foreach (byte b in value)
75 | {
76 | Data.Add(b);
77 | }
78 | }
79 |
80 | public void AddUInt16(ushort value)
81 | {
82 | byte[] Message = BitConverter.GetBytes(value).Reverse().ToArray();
83 | AddBytes(Message);
84 | }
85 |
86 | public void AddUInt32(uint value)
87 | {
88 | byte[] Message = BitConverter.GetBytes(value).Reverse().ToArray();
89 | AddBytes(Message);
90 | }
91 |
92 | public void AddInt16(short value)
93 | {
94 | byte[] Message = BitConverter.GetBytes(value).Reverse().ToArray();
95 | AddBytes(Message);
96 | }
97 |
98 | public void AddInt32(int value)
99 | {
100 | byte[] Message = BitConverter.GetBytes(value).Reverse().ToArray();
101 | AddBytes(Message);
102 | }
103 |
104 | public void AddHostUInt16(ushort value)
105 | {
106 | byte[] Message = BitConverter.GetBytes(value).ToArray();
107 | AddBytes(Message);
108 | }
109 |
110 | public void AddHostUInt32(uint value)
111 | {
112 | byte[] Message = BitConverter.GetBytes(value).ToArray();
113 | AddBytes(Message);
114 | }
115 |
116 | public void AddNullTerminatedString(string value)
117 | {
118 | value += (char)0x0;
119 | byte[] Message = Encoding.ASCII.GetBytes(value);
120 | AddBytes(Message);
121 | }
122 |
123 | public void AddBoolean(bool value)
124 | {
125 | byte v = (byte)(value ? 0x1 : 0x0);
126 | AddByte(v);
127 | }
128 |
129 | public void AddMessage(SOEMessage message)
130 | {
131 | if (IsMessage)
132 | {
133 | // Handle multi messages
134 | if (OpCode == (ushort)SOEOPCodes.MULTI_MESSAGE)
135 | {
136 | if (message.GetOpCode() == (ushort)SOEOPCodes.MULTI_MESSAGE)
137 | {
138 | // Setup a reader
139 | SOEReader reader = new SOEReader(message);
140 |
141 | // Get the messages and add them
142 | byte[] messages = reader.ReadToEnd();
143 | AddBytes(messages);
144 | }
145 | else
146 | {
147 | // Get the size of the message
148 | int size = message.GetLength();
149 |
150 | // Is the size bigger than 255?
151 | if (size > 0xFF)
152 | {
153 | // Do the stupid >255 thing
154 | AddByte(0xFF);
155 | size -= 0xFF;
156 |
157 | // Get how many bytes to add
158 | byte toAdd = (byte)((size / 0xFF) + (size % 0xFF) & 0xFF);
159 | AddByte(toAdd);
160 |
161 | // Add sizes until we're at a value of 0
162 | while (size > 0)
163 | {
164 | // Do we not want to add 0xFF?
165 | if (size < 0xFF)
166 | {
167 | // Add the rest of the size
168 | AddByte((byte)size);
169 | size = 0;
170 | }
171 | else
172 | {
173 | // Add 0xFF
174 | AddByte(0xFF);
175 | size -= 0xFF;
176 | }
177 | }
178 | }
179 | else
180 | {
181 | // Just do the regular size adding
182 | AddByte((byte)(size & 0xFF));
183 | }
184 |
185 | // Add the actual message
186 | AddBytes(message.GetRaw());
187 | }
188 | }
189 | }
190 | else
191 | {
192 | // Just add the message
193 | AddBytes(message.GetRaw());
194 | }
195 | }
196 |
197 | public SOEPacket GetFinalSOEPacket(SOEClient client, bool compressed, bool appendCRC)
198 | {
199 | // Data
200 | byte[] originalPacket = GetRaw();
201 | byte[] rawData = new byte[Data.Count - 2];
202 | byte[] newPacket;
203 |
204 | // Fail-safe
205 | ushort originalOpCode = 0;
206 |
207 | // Are we a message?
208 | if (IsMessage)
209 | {
210 | // Yes, so we'll try make a data packet.
211 | // Can we fit into one packet?
212 | SOEMessage message = GetFinalSOEMessage(client);
213 | if (message.IsFragmented)
214 | {
215 | // We're gonna have to fragment, so we can't handle this gracefully...
216 | client.Server.Log("[ERROR] Tried to handle 'GetFinalSOEPacket' call on written SOEMessage gracefully but failed due to fragmentation. Returning null.");
217 | client.Server.Log("[INFO] Call 'GetFinalSOEMessage' as it deals with fragmentation!");
218 |
219 | // Welp, goodbye world! :'(
220 | return null;
221 | }
222 |
223 | // Make the new packet
224 | Data = new List();
225 | AddUInt16(client.GetNextSequenceNumber());
226 | AddBytes(originalPacket);
227 |
228 | // Set our raw data
229 | rawData = GetRaw();
230 |
231 | // Change our OpCode so that we're a reliable data packet
232 | originalOpCode = OpCode;
233 | OpCode = (ushort)SOEOPCodes.RELIABLE_DATA;
234 |
235 | // Because we're reliable data, take compression into consideration and append a CRC
236 | compressed = true;
237 | appendCRC = true;
238 |
239 | // We handled it gracefully! :)
240 | client.Server.Log("[INFO] Handled 'GetFinalSOEPacket' call on written SOEMessage gracefully.");
241 | }
242 | else
243 | {
244 | // Get just the data for this packet. (Remove the OP Code)
245 | byte[] completeRawData = GetRaw();
246 | for (int i = 2; i < completeRawData.Length; i++)
247 | {
248 | rawData[i - 2] = completeRawData[i];
249 | }
250 | }
251 |
252 | // Start a new packet
253 | Data = new List();
254 | AddUInt16(OpCode);
255 |
256 | // Are we compressable?
257 | if (client.IsCompressable())
258 | {
259 | if (compressed)
260 | {
261 | AddBoolean(rawData.Length > 100);
262 | if (rawData.Length > 100)
263 | {
264 | rawData = client.Compress(rawData);
265 | }
266 | }
267 | }
268 |
269 | // Are we encrypted?
270 | if (client.IsEncrypted())
271 | {
272 | // Encrypt the SOE Packet
273 | rawData = client.Encrypt(rawData);
274 | }
275 |
276 | // Add the raw data
277 | AddBytes(rawData);
278 |
279 | // Appended CRC32?
280 | if (appendCRC)
281 | {
282 | AddBytes(client.GetAppendedCRC32(GetRaw()));
283 | }
284 |
285 | // Set our new packet
286 | newPacket = GetRaw();
287 |
288 | // Get our old message before compression/encryption
289 | Data = new List(originalPacket);
290 |
291 | // If we are a message, set our OpCode back
292 | if (IsMessage)
293 | {
294 | // Set our OpCode back too..
295 | OpCode = originalOpCode;
296 | }
297 |
298 | // Return the compressed/encrypted packet
299 | return new SOEPacket(OpCode, newPacket);
300 | }
301 |
302 | public SOEMessage GetFinalSOEMessage(SOEClient client)
303 | {
304 | // Are we a packet?
305 | if (!IsMessage)
306 | {
307 | // Yes, and there really isn't a nice way to deal with this..
308 | client.Server.Log("[ERROR] Tried Calling 'GetFinalSOEMessage' on written SOEPacket. Returning null.");
309 |
310 | // Welp, goodbye world! :'(
311 | return null;
312 | }
313 |
314 | // Make our message
315 | SOEMessage message = new SOEMessage(OpCode, GetRaw());
316 |
317 | // Does this message have to be fragmented?
318 | if (Data.Count > client.GetBufferSize())
319 | {
320 | // Setup a reader and keep track of our size
321 | SOEReader reader = new SOEReader(GetRaw());
322 | int size = message.GetLength();
323 |
324 | // While there are fragments to be added..
325 | while (size > 0)
326 | {
327 | // Store the next fragment
328 | byte[] raw;
329 |
330 | // Is this fragment going to be smaller than the buffer size?
331 | if (size < client.GetBufferSize())
332 | {
333 | raw = reader.ReadBytes(size);
334 | size = 0;
335 | }
336 | else
337 | {
338 | raw = reader.ReadBytes((int)client.GetBufferSize());
339 | size -= (int)client.GetBufferSize();
340 | }
341 |
342 | // Add the finalized fragment
343 | message.AddFragment(raw);
344 | }
345 | }
346 |
347 | // Return the message we made
348 | return message;
349 | }
350 |
351 | public byte[] GetRaw()
352 | {
353 | return Data.ToArray();
354 | }
355 | }
356 | }
357 |
--------------------------------------------------------------------------------
/LibSOE/Core/SOEProtocol.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.IO;
4 | using Ionic.Zlib;
5 |
6 | using SOE.Interfaces;
7 |
8 | namespace SOE.Core
9 | {
10 | public partial class SOEProtocol
11 | {
12 | public string ProtocolString;
13 | private readonly SOEServer Server;
14 |
15 | static readonly uint[] CRCTable =
16 | {
17 | 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
18 | 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
19 | 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
20 | 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
21 | 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
22 | 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
23 | 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
24 | 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
25 | 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
26 | 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
27 | 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
28 | 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
29 | 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
30 | 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
31 | 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
32 | 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
33 | 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
34 | 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
35 | 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
36 | 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
37 | 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
38 | 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
39 | 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
40 | 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
41 | 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
42 | 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
43 | 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
44 | 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
45 | 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
46 | 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
47 | 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
48 | 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
49 | 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
50 | 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
51 | 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
52 | 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
53 | 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
54 | 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
55 | 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
56 | 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
57 | 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
58 | 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
59 | 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
60 | };
61 |
62 | public SOEProtocol(SOEServer server, string protocol)
63 | {
64 | // This components settings
65 | Server = server;
66 | ProtocolString = protocol;
67 |
68 | // Make our protocol known
69 | MessageHandlers.MakeProtocol(protocol);
70 |
71 | // Log
72 | Log("Service constructed");
73 | Log("Using protocol -- {0}", ProtocolString);
74 | }
75 |
76 | public void HandlePacket(SOEClient sender, byte[] rawPacket)
77 | {
78 | // Read the packet
79 | SOEReader reader = new SOEReader(rawPacket);
80 | ushort opCode = reader.ReadUInt16();
81 | bool goodPacket = true;
82 |
83 | // Get the packet data
84 | byte[] data = new byte[rawPacket.Length - (int)sender.GetCRCLength()];
85 | for (int i = 0; i < data.Length; i++)
86 | {
87 | data[i] = rawPacket[i];
88 | }
89 |
90 | // Get the CRC32 checksum for the packet
91 | uint crc32 = sender.GetCRC32Checksum(data);
92 |
93 | // Get our expected CRC32 checksum
94 | uint expectedCRC32 = 0;
95 |
96 | uint place = sender.GetCRCLength() - 1;
97 | for (int i = rawPacket.Length - (int)sender.GetCRCLength(); i < rawPacket.Length; i++)
98 | {
99 | expectedCRC32 += (uint)(rawPacket[i] << (8 * (int)place));
100 | place--;
101 | }
102 |
103 | // We have our CRCs! Check if the packet is formed correctly!
104 | byte[] crc = BitConverter.GetBytes(crc32).Reverse().ToArray();
105 | byte[] expectedCRC = BitConverter.GetBytes(expectedCRC32).Reverse().ToArray();
106 |
107 | int start = 4 - (int)sender.GetCRCLength();
108 | for (int i = start; i < sender.GetCRCLength(); i++)
109 | {
110 | // If they're not the same, we're a bad packet
111 | if (crc[i] != expectedCRC[i])
112 | {
113 | // Set that we're bad, and get outta the loop
114 | goodPacket = false;
115 | break;
116 | }
117 | }
118 |
119 | // Are we malformed?
120 | if (!goodPacket)
121 | {
122 | Log("[WARNING] Received malformed packet! Incorrect CRC Appended!");
123 | return;
124 | }
125 |
126 | // Remove the CRC
127 | rawPacket = data;
128 |
129 | // Do we need to worry about decryption/decompression?
130 | if (sender.HasSession())
131 | {
132 | // Is the client encrypted?
133 | if (sender.IsEncrypted())
134 | {
135 | rawPacket = DecryptPacket(sender, rawPacket);
136 | }
137 |
138 | // Is the client compressable?
139 | if (sender.IsCompressable())
140 | {
141 | // It seems that all packets after 0x02 are compressable..
142 | rawPacket = DecompressPacket(sender, rawPacket);
143 | }
144 | }
145 |
146 | // Handle the decompressed/decrypted packet!
147 | HandlePacket(sender, new SOEPacket(opCode, rawPacket));
148 | }
149 |
150 | public void HandlePacket(SOEClient sender, SOEPacket packet)
151 | {
152 | // Operation
153 | ushort opCode = packet.GetOpCode();
154 |
155 | // Security Measure
156 | if (!sender.HasSession())
157 | {
158 | if (opCode != (ushort)SOEOPCodes.SESSION_REQUEST)
159 | {
160 | // We really don't care about this client.
161 | // They can try send stuff as much as possible.
162 | // TODO: Handle this (cuz attacks)
163 | return;
164 | }
165 | }
166 |
167 | // Handle!
168 | switch ((SOEOPCodes)opCode)
169 | {
170 | case SOEOPCodes.SESSION_REQUEST:
171 | HandleSessionRequest(sender, packet);
172 | break;
173 |
174 | case SOEOPCodes.MULTI:
175 | Console.WriteLine("MULTI!");
176 | // TODO
177 | break;
178 |
179 | case SOEOPCodes.DISCONNECT:
180 | HandleDisconnect(sender, packet);
181 | break;
182 |
183 | case SOEOPCodes.PING:
184 | HandlePing(sender);
185 | break;
186 |
187 | case SOEOPCodes.RELIABLE_DATA:
188 | case SOEOPCodes.FRAGMENTED_RELIABLE_DATA:
189 | case SOEOPCodes.ACK_RELIABLE_DATA:
190 | sender.DataChannel.Receive(packet);
191 | break;
192 |
193 | default:
194 | Log("Received Unknown SOEPacket 0x{0:X2}!", packet.GetOpCode());
195 | break;
196 | }
197 | }
198 |
199 | public void HandleMessage(SOEClient sender, byte[] rawMessage)
200 | {
201 | // Read the message...
202 | SOEReader reader = new SOEReader(rawMessage);
203 | ushort opCode = reader.ReadHostUInt16();
204 |
205 | // Make the message
206 | SOEMessage message = new SOEMessage(opCode, rawMessage);
207 |
208 | // Handle it
209 | HandleMessage(sender, message);
210 | }
211 |
212 | public void HandleMessage(SOEClient sender, SOEMessage message)
213 | {
214 | // Handlers
215 | if (MessageHandlers.HasHandler(ProtocolString, message.GetOpCode()))
216 | {
217 | // Log
218 | string messageName = MessageHandlers.Protocol2MessageName[ProtocolString][message.GetOpCode()];
219 | Log("Received 0x{0:X}, {1}_{2}!", message.GetOpCode(), Server.GAME_NAME, messageName);
220 |
221 | // Handle it
222 | MessageHandlers.GetHandler(ProtocolString, message.GetOpCode())(sender, message);
223 | }
224 | else
225 | {
226 | // Log
227 | Log("Received Unknown SOEMessage {0}!", message.GetOpCode());
228 | foreach (byte b in message.GetRaw())
229 | {
230 | Console.Write("0x{0:X2} ", b);
231 | }
232 | Console.WriteLine();
233 | }
234 | }
235 |
236 | public byte[] Encrypt(SOEClient client, byte[] data)
237 | {
238 | SOEReader reader = new SOEReader(data);
239 | SOEWriter newPacket = new SOEWriter();
240 |
241 | int blockCount = data.Length / 4;
242 | int byteCount = data.Length % 4;
243 |
244 | uint key = client.GetCRCSeed();
245 |
246 | // Encrypt the blocks of 4 bytes
247 | for (int i = 0; i < blockCount; i++)
248 | {
249 | uint value = key = reader.ReadUInt32() ^ key;
250 | newPacket.AddUInt32(value);
251 | }
252 |
253 | // Encrypt the rest of the singular bytes
254 | byte newKey = (byte)((key >> 24) & 0xFF);
255 | for (int i = 0; i < byteCount; i++)
256 | {
257 | byte value = (byte)(reader.ReadByte() ^ newKey);
258 | newPacket.AddByte(value);
259 | }
260 |
261 | // Return the encrypted packet
262 | return newPacket.GetRaw();
263 | }
264 |
265 | public byte[] Compress(byte[] data)
266 | {
267 | // Decompress the old packet..
268 | MemoryStream dataStream = new MemoryStream(data);
269 | MemoryStream compressed = new MemoryStream();
270 |
271 | using (ZlibStream deflateStream = new ZlibStream(compressed, CompressionMode.Compress))
272 | {
273 | dataStream.CopyTo(deflateStream);
274 | }
275 |
276 | // Return the compressed data
277 | return compressed.ToArray();
278 | }
279 |
280 | private byte[] DecryptPacket(SOEClient sender, byte[] packet)
281 | {
282 | // Compressable?
283 | if (!sender.IsEncrypted())
284 | {
285 | return packet;
286 | }
287 |
288 | // Setup our streams
289 | SOEReader reader = new SOEReader(packet);
290 | SOEWriter newPacket = new SOEWriter(reader.ReadUInt16());
291 |
292 | // Skip the compression flag
293 | reader.ReadByte();
294 |
295 | // Get our data
296 | int blockCount = (packet.Length - 3) / 4;
297 | int byteCount = (packet.Length - 3) % 4;
298 | uint key = sender.GetCRCSeed();
299 |
300 | // Decrypt the blocks of 4 bytes
301 | for (int i = 0; i < blockCount; i++)
302 | {
303 | uint temp = reader.ReadUInt32();
304 | uint value = temp ^ key;
305 | key = temp;
306 |
307 | newPacket.AddUInt32(value);
308 | }
309 |
310 | // Decrypt the rest of the singular bytes
311 | byte newKey = (byte)((key >> 24) & 0xFF);
312 | for (int i = 0; i < byteCount; i++)
313 | {
314 | byte value = (byte)(reader.ReadByte() ^ newKey);
315 | newPacket.AddByte(value);
316 | }
317 |
318 | // Return the decrypted packet
319 | return newPacket.GetRaw();
320 | }
321 |
322 | private byte[] DecompressPacket(SOEClient sender, byte[] packet)
323 | {
324 | // Compressable?
325 | if (!sender.IsCompressable())
326 | {
327 | return packet;
328 | }
329 |
330 | // Remove the OpCode and compression flag
331 | byte[] data = new byte[packet.Length - 3];
332 | for (int i = 3; i < packet.Length; i++)
333 | {
334 | data[i - 3] = packet[i];
335 | }
336 |
337 | byte[] decompressedData = data;
338 |
339 | // Decompress the old packet..
340 | MemoryStream dataStream = new MemoryStream(data);
341 | MemoryStream decompressed = new MemoryStream();
342 |
343 | if (packet[2] == 0x01)
344 | {
345 | using (ZlibStream zlibStream = new ZlibStream(dataStream, CompressionMode.Decompress))
346 | {
347 | zlibStream.CopyTo(decompressed);
348 | }
349 |
350 | // Reconstruct the packet..
351 | decompressedData = decompressed.ToArray();
352 | }
353 |
354 | // Reconstruct the packet..
355 | byte[] newPacket = new byte[decompressedData.Length + 2];
356 |
357 | // OpCode
358 | int place = 0;
359 | for (int i = 0; i < 2; i++)
360 | {
361 | newPacket[place] = packet[i];
362 | place++;
363 | }
364 |
365 | // Data
366 | for (int i = 0; i < decompressedData.Length; i++)
367 | {
368 | newPacket[place] = decompressedData[i];
369 | place++;
370 | }
371 |
372 | // And.. return!
373 | return newPacket;
374 | }
375 |
376 | public uint GetCRC32Checksum(uint crcSeed, byte[] packet)
377 | {
378 | uint nCrc = CRCTable[(~crcSeed) & 0xFF];
379 | nCrc ^= 0x00FFFFFF;
380 |
381 | uint nIndex = (crcSeed >> 8) ^ nCrc;
382 | nCrc = (nCrc >> 8) & 0x00FFFFFF;
383 | nCrc ^= CRCTable[nIndex & 0xFF];
384 |
385 | nIndex = (crcSeed >> 16) ^ nCrc;
386 | nCrc = (nCrc >> 8) & 0x00FFFFFF;
387 | nCrc ^= CRCTable[nIndex & 0xFF];
388 |
389 | nIndex = (crcSeed >> 24) ^ nCrc;
390 | nCrc = (nCrc >> 8) & 0x00FFFFFF;
391 | nCrc ^= CRCTable[nIndex & 0xFF];
392 |
393 | for (int i = 0; i < packet.Length; i++)
394 | {
395 | nIndex = packet[i] ^ nCrc;
396 | nCrc = (nCrc >> 8) & 0x00FFFFFF;
397 | nCrc ^= CRCTable[nIndex & 0xFF];
398 | }
399 |
400 | return ~nCrc;
401 | }
402 |
403 | public void Log(string message, params object[] args)
404 | {
405 | Console.WriteLine(":SOEProtocol: " + message, args);
406 | }
407 | }
408 | }
409 |
--------------------------------------------------------------------------------