fork from 1.3

This commit is contained in:
moux23333 2024-01-27 21:06:07 +08:00
commit 22fc0b0848
1507 changed files with 24139 additions and 0 deletions

398
.gitignore vendored Normal file
View file

@ -0,0 +1,398 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.tlog
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
*.vbp
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
*.dsw
*.dsp
# Visual Studio 6 technical files
*.ncb
*.aps
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# Visual Studio History (VSHistory) files
.vshistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# VS Code files for those working on multiple tools
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# Local History for Visual Studio Code
.history/
# Windows Installer files from build outputs
*.cab
*.msi
*.msix
*.msm
*.msp
# JetBrains Rider
*.sln.iml

View file

@ -0,0 +1,48 @@
namespace FreeSR.Admin
{
using FreeSR.Admin.Command.Handlers;
using FreeSR.Admin.Service;
using FreeSR.Shared.Command;
using FreeSR.Shared.Configuration;
using FreeSR.Shared.Exceptions;
using NLog;
internal static class AdminServer
{
private static readonly Logger s_log = LogManager.GetCurrentClassLogger();
private static void Main(string[] args)
{
Directory.SetCurrentDirectory(AppContext.BaseDirectory);
AppDomain.CurrentDomain.UnhandledException += OnFatalException;
s_log.Info("Initializing...");
CommandManager.Instance.Initialize(typeof(AccountCommandCategory));
ConfigurationManager<AdminServerConfiguration>.Instance.Initialize("AdminServer.json");
var serverConfiguration = ConfigurationManager<AdminServerConfiguration>.Instance.Model;
HttpAdminService.Initialize(serverConfiguration.Network);
s_log.Info("Server is ready!");
Thread.Sleep(-1);
}
private static void OnFatalException(object sender, UnhandledExceptionEventArgs args)
{
if (args.ExceptionObject is ServerInitializationException initException)
{
Console.WriteLine("Server initialization failed, unhandled exception!");
Console.WriteLine(initException);
}
else
{
Console.WriteLine("Unhandled exception in runtime!");
Console.WriteLine(args.ExceptionObject);
}
Console.WriteLine("Press enter to close this window...");
Console.ReadLine();
}
}
}

View file

@ -0,0 +1,7 @@
{
"Network": {
"Host": "0.0.0.0",
"Port": 1337
},
"DispatchUrl": "http://localhost:8888"
}

View file

@ -0,0 +1,10 @@
namespace FreeSR.Admin
{
using FreeSR.Shared.Configuration;
internal class AdminServerConfiguration
{
public NetworkConfiguration Network { get; set; }
public string DispatchUrl { get; set; }
}
}

View file

@ -0,0 +1,19 @@
namespace FreeSR.Admin.Command
{
using FreeSR.Shared.Command.Context;
internal class AdminCommandContext : ICommandContext
{
public string Message { get; private set; }
public void SendError(string message)
{
Message = "Error: " + message;
}
public void SendMessage(string message)
{
Message = message;
}
}
}

View file

@ -0,0 +1,17 @@
namespace FreeSR.Admin.Command.Handlers
{
using FreeSR.Shared.Command;
using FreeSR.Shared.Command.Context;
using FreeSR.Shared.Configuration;
[Command("account")]
internal class AccountCommandCategory : CommandCategory
{
[Command("create")]
public void AccountCreateCommandHandler(ICommandContext context, string username, string password)
{
var config = ConfigurationManager<AdminServerConfiguration>.Instance.Model;
context.SendMessage($"dohttpreq={config.DispatchUrl}/sdk/createaccount?user={username}&pass={password}");
}
}
}

View file

@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Ceen.Httpd" Version="0.9.10" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FreeSR.Shared\FreeSR.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="AdminServer.example.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="assets\console.html">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View file

@ -0,0 +1,25 @@
namespace FreeSR.Admin.Handlers
{
using Ceen;
using FreeSR.Admin.Service;
internal class ConsolePageRequestHandler : IHttpModule
{
public async Task<bool> HandleAsync(IHttpContext context)
{
context.Response.StatusCode = HttpStatusCode.OK;
await context.Response.WriteAllAsync(CreateHTMLDocument(), "text/html");
return true;
}
private static string CreateHTMLDocument()
{
string baseString = HttpAdminService.ConsoleHTML;
return baseString.Replace("%SERVER_VERSION%", "v0.1.0 dev - experimental open source")
.Replace("%GAME_VERSION%", "1.2.0");
}
}
}

View file

@ -0,0 +1,23 @@
namespace FreeSR.Admin.Handlers
{
using Ceen;
using FreeSR.Admin.Command;
using FreeSR.Shared.Command;
internal class ExecuteCommandRequestHandler : IHttpModule
{
public async Task<bool> HandleAsync(IHttpContext context)
{
var query = context.Request.QueryString;
string command = query["command"];
var ctx = new AdminCommandContext();
CommandManager.Instance.Invoke(ctx, command);
context.Response.StatusCode = HttpStatusCode.OK;
await context.Response.WriteAllAsync(ctx.Message, "text/plain");
return true;
}
}
}

View file

@ -0,0 +1,43 @@
namespace FreeSR.Admin.Service
{
using Ceen.Httpd;
using Ceen.Httpd.Logging;
using FreeSR.Admin.Handlers;
using FreeSR.Shared.Configuration;
using System.Net;
internal static class HttpAdminService
{
public static string ConsoleHTML { get; private set; }
private static ServerConfig s_httpdConfiguration;
public static void Initialize(NetworkConfiguration config)
{
LoadHtDocs();
s_httpdConfiguration = CreateConfiguration();
_ = BootHttpAsync(config);
}
private static void LoadHtDocs()
{
ConsoleHTML = File.ReadAllText("assets/console.html");
}
private static ServerConfig CreateConfiguration()
{
return new ServerConfig().AddLogger(new CLFStdOut())
.AddRoute("/console", new ConsolePageRequestHandler())
.AddRoute("/console/exec", new ExecuteCommandRequestHandler());
}
private static async Task BootHttpAsync(NetworkConfiguration config)
{
await HttpServer.ListenAsync(new IPEndPoint(
IPAddress.Parse(config.Host),
config.Port),
false, s_httpdConfiguration);
}
}
}

View file

@ -0,0 +1,101 @@
<!DOCTYPE html>
<html>
<head>
<title>FreeSR</title>
<style>
body {
font-family: Arial, sans-serif;
}
#status {
font-weight: bold;
}
#console {
width: 100%;
height: 300px;
font-family: monospace;
background-color: #f0f0f0;
overflow-y: scroll;
}
#commandInput {
width: 100%;
padding: 5px;
}
#submitBtn {
padding: 5px 10px;
margin-top: 10px;
cursor: pointer;
}
</style>
</head>
<body>
<h1>FreeSR Control Panel</h1>
<div>
<h2>Status:</h2>
<p id="status">Loading...</p>
</div>
<div>
<h2>Server Statistics:</h2>
<p>Server build: %SERVER_VERSION%</p>
<p>Supported game version: %GAME_VERSION%</p>
</div>
<div>
<h2>Admin Console:</h2>
<textarea id="console" readonly></textarea>
<input type="text" id="commandInput" placeholder="Enter admin command...">
<button id="submitBtn">Execute</button>
</div>
<script>
const consoleOutput = document.getElementById('console');
const commandInput = document.getElementById('commandInput');
const submitBtn = document.getElementById('submitBtn');
// Function to update the status section (replace with actual server status)
function updateStatus(statusText) {
const statusElement = document.getElementById('status');
statusElement.innerText = statusText;
}
// Function to add a new line to the console
function addToConsole(text) {
consoleOutput.value += text + '\n';
consoleOutput.scrollTop = consoleOutput.scrollHeight;
}
function sendCommand() {
const command = commandInput.value;
if (command.length == 0)
return;
addToConsole(`> ${command}`);
fetch(`/console/exec?command=${encodeURIComponent(command)}`)
.then(response => response.text())
.then(data => {
if (data.startsWith("dohttpreq")) {
fetch(data.replace("dohttpreq=", ""))
.then(response => response.text())
.then(data => {
addToConsole(data);
});
}
else {
addToConsole(data);
}
})
.catch(error => {
addToConsole(`Error: ${error.message}`);
});
commandInput.value = '';
}
submitBtn.addEventListener('click', sendCommand);
</script>
</body>
</html>

View file

@ -0,0 +1,59 @@
namespace FreeSR.Database.Account
{
using FreeSR.Database.Account.Model;
using FreeSR.Database.Account.Util;
using FreeSR.Database.Mongo;
using MongoDB.Driver;
public class AccountDatabase : SRMongoDatabase<AccountModel>
{
private int _maxUid;
public AccountDatabase(IMongoDatabase database, string collectionName) : base(database, collectionName)
{
// AccountDatabase.
}
public async Task<AccountModel> Create(string name, string password)
{
if (_maxUid == 0)
_maxUid = await FetchMaxUid();
if (await GetByName(name) != null)
return null;
var model = new AccountModel
{
Uid = Interlocked.Increment(ref _maxUid),
Name = name,
Password = password,
CreationDateUtc = DateTime.UtcNow,
Token = AccountTokenUtil.Generate()
};
await Insert(model);
return model;
}
public async Task<AccountModel> GetByUid(int uid)
{
return await FindOne(account => account.Uid == uid);
}
public async Task<AccountModel> GetByName(string name)
{
return await FindOne(account => account.Name == name);
}
public async Task Update(AccountModel account)
{
await Update(model => model.Uid == account.Uid, account);
}
private async Task<int> FetchMaxUid()
{
var maxUidAccount = await FindMax(account => account.Uid);
return maxUidAccount?.Uid ?? 0;
}
}
}

View file

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\FreeSR.Database\FreeSR.Database.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,14 @@
namespace FreeSR.Database.Account.Model
{
using MongoDB.Bson.Serialization.Attributes;
[BsonIgnoreExtraElements]
public class AccountModel
{
public int Uid { get; set; }
public string Name { get; set; }
public string Password { get; set; }
public string Token { get; set; }
public DateTime CreationDateUtc { get; set; }
}
}

View file

@ -0,0 +1,37 @@
namespace FreeSR.Database.Account.Util
{
using FreeSR.Database.Account.Model;
public static class AccountTokenUtil
{
private const int AccountTokenLength = 128;
private const string TokenCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
private static Random s_random;
static AccountTokenUtil()
{
s_random = new Random();
}
public static string Generate()
{
var token = "";
for (int i = 0; i < AccountTokenLength; i++)
{
token += TokenCharacters[s_random.Next(TokenCharacters.Length)];
}
return token;
}
public static bool Verify(AccountModel accountModel, string clientToken)
{
if (accountModel == null)
return false;
return string.Equals(accountModel.Token, clientToken);
}
}
}

View file

@ -0,0 +1,9 @@
namespace FreeSR.Database.Configuration
{
public class DatabaseConfiguration
{
public string ConnectionString { get; set; }
public string Name { get; set; }
public DatabaseEntry[] Entries { get; set; }
}
}

View file

@ -0,0 +1,8 @@
namespace FreeSR.Database.Configuration
{
public class DatabaseEntry
{
public string CollectionName { get; set; }
public DatabaseType Type { get; set; }
}
}

View file

@ -0,0 +1,12 @@
namespace FreeSR.Database.Configuration
{
using FreeSR.Shared.Exceptions;
internal class DatabaseMisconfiguredException : ServerInitializationException
{
public DatabaseMisconfiguredException(string message) : base(message)
{
// DatabaseMisconfiguredException.
}
}
}

View file

@ -0,0 +1,7 @@
namespace FreeSR.Database.Configuration
{
public enum DatabaseType
{
Account
}
}

View file

@ -0,0 +1,55 @@
namespace FreeSR.Database
{
using FreeSR.Database.Configuration;
using FreeSR.Shared;
using MongoDB.Driver;
using NLog;
public sealed class DatabaseManager : Singleton<DatabaseManager>
{
private static readonly Logger s_log = LogManager.GetCurrentClassLogger();
private DatabaseConfiguration _configuration;
private Dictionary<Type, object> _databases;
public IMongoDatabase MongoDatabase { get; private set; }
private DatabaseManager()
{
_databases = new Dictionary<Type, object>();
}
public void Initialize(DatabaseConfiguration configuration)
{
_configuration = configuration;
var mongoClient = new MongoClient(configuration.ConnectionString);
MongoDatabase = mongoClient.GetDatabase(configuration.Name);
}
public string GetCollectionName(DatabaseType databaseType)
{
foreach (var entry in _configuration.Entries)
{
if (entry.Type == databaseType)
return entry.CollectionName;
}
throw new DatabaseMisconfiguredException($"Can not find database of type {databaseType} in provided configuration.");
}
public DatabaseManager Add<T>(ISRDatabase<T> database) where T : class
{
_databases.Add(database.GetType(), database);
return this;
}
public T Get<T>() where T : class
{
if (_databases.TryGetValue(typeof(T), out var database))
return database as T;
return null;
}
}
}

View file

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MongoDB.Driver" Version="2.20.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FreeSR.Shared\FreeSR.Shared.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,18 @@
namespace FreeSR.Database
{
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
public interface ISRDatabase<T> where T : class
{
Task Insert(T document);
Task InsertMany(IEnumerable<T> documents);
Task<List<T>> Find(Expression<Func<T, bool>> filter);
Task<T> FindOne(Expression<Func<T, bool>> filter);
Task Update(Expression<Func<T, bool>> filter, T updatedDocument);
Task Delete(Expression<Func<T, bool>> filter);
Task<long> Count();
Task<T> FindMax(Expression<Func<T, object>> fieldSelector);
}
}

View file

@ -0,0 +1,58 @@
namespace FreeSR.Database.Mongo
{
using MongoDB.Driver;
using System.Linq.Expressions;
public class SRMongoDatabase<T> : ISRDatabase<T> where T : class
{
protected readonly IMongoCollection<T> _collection;
public SRMongoDatabase(IMongoDatabase database, string collectionName)
{
_collection = database.GetCollection<T>(collectionName);
}
public async Task Insert(T document)
{
await _collection.InsertOneAsync(document);
}
public async Task InsertMany(IEnumerable<T> documents)
{
await _collection.InsertManyAsync(documents);
}
public async Task<List<T>> Find(Expression<Func<T, bool>> filter)
{
var result = await _collection.FindAsync(filter);
return await result.ToListAsync();
}
public async Task<T> FindOne(Expression<Func<T, bool>> filter)
{
var result = await _collection.FindAsync(filter);
return await result.FirstOrDefaultAsync();
}
public async Task Update(Expression<Func<T, bool>> filter, T updatedDocument)
{
await _collection.ReplaceOneAsync(filter, updatedDocument);
}
public async Task Delete(Expression<Func<T, bool>> filter)
{
await _collection.DeleteOneAsync(filter);
}
public async Task<long> Count()
{
return await _collection.CountDocumentsAsync(Builders<T>.Filter.Empty);
}
public async Task<T> FindMax(Expression<Func<T, object>> fieldSelector)
{
var sortDefinition = Builders<T>.Sort.Descending(fieldSelector);
return await _collection.Find(Builders<T>.Filter.Empty).Sort(sortDefinition).FirstOrDefaultAsync();
}
}
}

View file

@ -0,0 +1,11 @@
namespace FreeSR.Dispatch.Configuration
{
using Newtonsoft.Json.Linq;
internal class RegionConfiguration
{
public string Name { get; set; }
public string EnvType { get; set; }
public string DispatchUrl { get; set; }
}
}

View file

@ -0,0 +1,59 @@
namespace FreeSR.Dispatch
{
using FreeSR.Database;
using FreeSR.Database.Account;
using FreeSR.Database.Configuration;
using FreeSR.Dispatch.Service;
using FreeSR.Dispatch.Service.Manager;
using FreeSR.Shared.Configuration;
using FreeSR.Shared.Exceptions;
using NLog;
internal static class DispatchServer
{
private const string Title = "FreeSR Dispatch Server (EXPERIMENTAL OPEN SOURCE BUILD)";
private static readonly Logger s_log = LogManager.GetCurrentClassLogger();
private static void Main(string[] args)
{
Directory.SetCurrentDirectory(AppContext.BaseDirectory);
AppDomain.CurrentDomain.UnhandledException += OnFatalException;
Console.Title = Title;
s_log.Info("Initializing...");
ConfigurationManager<DispatchServerConfiguration>.Instance.Initialize("DispatchServer.json");
var serverConfiguration = ConfigurationManager<DispatchServerConfiguration>.Instance.Model;
DatabaseManager.Instance.Initialize(serverConfiguration.Database);
var mongoDatabase = DatabaseManager.Instance.MongoDatabase;
DatabaseManager.Instance.Add(new AccountDatabase(mongoDatabase, DatabaseManager.Instance.GetCollectionName(DatabaseType.Account)));
RegionManager.Initialize(serverConfiguration.Region);
HttpDispatchService.Initialize(serverConfiguration.Network);
s_log.Info("Server is ready!");
Thread.Sleep(-1); // TODO: Console handler
}
private static void OnFatalException(object sender, UnhandledExceptionEventArgs args)
{
if (args.ExceptionObject is ServerInitializationException initException)
{
Console.WriteLine("Server initialization failed, unhandled exception!");
Console.WriteLine(initException);
}
else
{
Console.WriteLine("Unhandled exception in runtime!");
Console.WriteLine(args.ExceptionObject);
}
Console.WriteLine("Press enter to close this window...");
Console.ReadLine();
}
}
}

View file

@ -0,0 +1,21 @@
{
"Network": {
"Host": "0.0.0.0",
"Port": 8888
},
"Database": {
"ConnectionString": "mongodb://127.0.0.1:27017/",
"Name": "FreeSR",
"Entries": [
{
"CollectionName": "accounts",
"Type": "Account"
}
]
},
"Region": {
"Name": "FreeSR",
"EnvType": "2",
"DispatchUrl": "http://dispatch.starrails.com/query_gateway"
}
}

View file

@ -0,0 +1,13 @@
namespace FreeSR.Dispatch
{
using FreeSR.Database.Configuration;
using FreeSR.Dispatch.Configuration;
using FreeSR.Shared.Configuration;
internal class DispatchServerConfiguration
{
public NetworkConfiguration Network { get; set; }
public DatabaseConfiguration Database { get; set; }
public RegionConfiguration Region { get; set; }
}
}

View file

@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Ceen.Httpd" Version="0.9.10" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FreeSR.Database.Account\FreeSR.Database.Account.csproj" />
<ProjectReference Include="..\FreeSR.Proto\FreeSR.Proto.csproj" />
<ProjectReference Include="..\FreeSR.Shared\FreeSR.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="DispatchServer.example.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View file

@ -0,0 +1,32 @@
namespace FreeSR.Dispatch.Handlers
{
using Ceen;
using Newtonsoft.Json;
internal class ComboGranterApiGetConfigHandler : IHttpModule
{
public async Task<bool> HandleAsync(IHttpContext context)
{
context.Response.StatusCode = HttpStatusCode.OK;
context.Response.ContentType = "application/json";
await context.Response.WriteAllJsonAsync(JsonConvert.SerializeObject(new
{
retcode = 0,
message = "OK",
data =
new
{
protocol = true,
qr_enabled = true,
log_level = "INFO",
announce_url = "https://sdk.hoyoverse.com/hkrpg/announcement/index.html?sdk_presentation_style=fullscreen\u0026sdk_screen_transparent=true\u0026auth_appid=announcement\u0026authkey_ver=1\u0026sign_type=2#/",
push_alias_type = 0,
disable_ysdk_guard = true,
enable_announce_pic_popup = true
}
}));
return true;
}
}
}

View file

@ -0,0 +1,39 @@
namespace FreeSR.Dispatch.Handlers
{
using Ceen;
using FreeSR.Dispatch.Util;
using FreeSR.Proto;
using Newtonsoft.Json.Linq;
using System.Threading.Tasks;
internal class ComboTokenRequestHandler : IHttpModule
{
public async Task<bool> HandleAsync(IHttpContext context)
{
var data = await context.Request.Body.ReadAllAsStringAsync();
var json = JObject.Parse(data);
var clientData = JObject.Parse((string)json["data"]);
var dataObject = new JObject
{
{"combo_id", 1},
{"open_id", clientData["uid"]},
{"combo_token", clientData["token"]},
{"data", new JObject {{"guest", false}}},
{"heartbeat", false},
{"account_type", 1},
{"fatigue_remind", null}
};
context.Response.StatusCode = HttpStatusCode.OK;
context.Response.ContentType = "application/json";
await context.Response.WriteAllJsonAsync(DispatchResponseBuilder.Create()
.Retcode(Retcode.RETCODE_RET_SUCC)
.Message("OK")
.Object("data", dataObject)
.Build());
return true;
}
}
}

View file

@ -0,0 +1,34 @@
namespace FreeSR.Dispatch.Handlers
{
using Ceen;
using FreeSR.Dispatch.Util;
using FreeSR.Proto;
using Newtonsoft.Json.Linq;
internal class GetAgreementInfosHandler : IHttpModule
{
public async Task<bool> HandleAsync(IHttpContext context)
{
context.Response.StatusCode = HttpStatusCode.OK;
context.Response.ContentType = "application/json";
await context.Response.WriteAllJsonAsync(DispatchResponseBuilder.Create()
.Retcode(Retcode.RETCODE_RET_SUCC)
.Message("OK")
.Object("data", Data)
.Build());
return true;
}
private static JObject Data
{
get
{
return new JObject
{
{"marketing_agreements", new JArray()}
};
}
}
}
}

View file

@ -0,0 +1,22 @@
namespace FreeSR.Dispatch.Handlers
{
using Ceen;
using FreeSR.Dispatch.Util;
using FreeSR.Proto;
internal class GetExperimentListHandler : IHttpModule
{
public async Task<bool> HandleAsync(IHttpContext context)
{
context.Response.StatusCode = HttpStatusCode.OK;
context.Response.ContentType = "application/json";
await context.Response.WriteAllJsonAsync(DispatchResponseBuilder.Create()
.Retcode(Retcode.RETCODE_RET_SUCC)
.Boolean("success", true)
.String("message", "")
.Build());
return true;
}
}
}

View file

@ -0,0 +1,26 @@
namespace FreeSR.Dispatch.Handlers
{
using Ceen;
using FreeSR.Dispatch.Util;
using NLog;
using System.Threading.Tasks;
internal class HkrpgDataUploadHandler : IHttpModule
{
private static readonly Logger s_log = LogManager.GetCurrentClassLogger();
public async Task<bool> HandleAsync(IHttpContext context)
{
string logs = await context.Request.Body.ReadAllAsStringAsync();
s_log.Info(logs);
context.Response.StatusCode = HttpStatusCode.OK;
context.Response.ContentType = "application/json";
await context.Response.WriteAllJsonAsync(DispatchResponseBuilder.Create()
.Code(0)
.Build());
return true;
}
}
}

View file

@ -0,0 +1,57 @@
namespace FreeSR.Dispatch.Handlers
{
using Ceen;
using FreeSR.Database;
using FreeSR.Database.Account;
using FreeSR.Database.Account.Model;
using FreeSR.Dispatch.Util;
using FreeSR.Proto;
using Newtonsoft.Json.Linq;
using System.Threading.Tasks;
internal class LoginRequestHandler : IHttpModule
{
public async Task<bool> HandleAsync(IHttpContext context)
{
context.Response.StatusCode = HttpStatusCode.OK;
context.Response.ContentType = "application/json";
string data = await context.Request.Body.ReadAllAsStringAsync();
JObject loginJson = JObject.Parse(data);
AccountDatabase accountDatabase = DatabaseManager.Instance.Get<AccountDatabase>();
string accountName = (string)loginJson["account"];
string password = (string)loginJson["password"];
AccountModel account = await accountDatabase.GetByName(accountName);
if (account == null)
{
await context.Response.WriteAllJsonAsync(DispatchResponseBuilder.Create()
.Retcode(Retcode.RETCODE_RET_FAIL)
.Message("Account not found.")
.Object("data", null)
.Build());
return true;
}
// no password check, because client patch is closed-source for now.
await context.Response.WriteAllJsonAsync(DispatchResponseBuilder.Create()
.Retcode(Retcode.RETCODE_RET_SUCC)
.Message("OK")
.Object("data", new JObject
{
{"account", account.ToLoginResponseData()},
{"device_grant_required", false},
{"safe_moblie_required", false},
{"realperson_required", false},
{"reactivate_required", false},
{"realname_operation", "None"}
, })
.Build());
return true;
}
}
}

View file

@ -0,0 +1,36 @@
namespace FreeSR.Dispatch.Handlers
{
using Ceen;
using FreeSR.Dispatch.Service.Manager;
using FreeSR.Dispatch.Util;
using FreeSR.Proto;
using NLog;
using System.Threading.Tasks;
internal class QueryDispatchHandler : IHttpModule
{
private static readonly Logger s_log = LogManager.GetCurrentClassLogger();
public async Task<bool> HandleAsync(IHttpContext context)
{
var query = context.Request.QueryString;
var version = query["version"];
var timestamp = query["t"];
var languageType = query["language_type"];
var platformType = query["platform_type"];
s_log.Info($"query_dispatch: version: {version}, time: {timestamp}, language: {languageType}, platform: {platformType}");
context.Response.StatusCode = HttpStatusCode.OK;
context.Response.ContentType = "text/plain";
await context.Response.WriteAllAsync(Convert.ToBase64String(ProtobufUtil.Serialize(new DispatchRegionsData
{
Retcode = Retcode.RETCODE_RET_SUCC,
TopSeverRegionName = RegionManager.GetTopServerRegionName(),
RegionList = RegionManager.GetRegionList()
})));
return true;
}
}
}

View file

@ -0,0 +1,34 @@
namespace FreeSR.Dispatch.Handlers
{
using Ceen;
using FreeSR.Dispatch.Util;
using FreeSR.Proto;
using System.Threading.Tasks;
internal class QueryGatewayHandler : IHttpModule
{
public async Task<bool> HandleAsync(IHttpContext context)
{
context.Response.StatusCode = HttpStatusCode.OK;
context.Response.ContentType = "text/plain";
await context.Response.WriteAllAsync(Convert.ToBase64String(ProtobufUtil.Serialize(new Gateserver
{
Retcode = Retcode.RETCODE_RET_SUCC,
Msg = "OK",
Ip = "127.0.0.1",
RegionName = "FreeSR",
Port = 22301,
Mljaogdhcki = true,
LoginWhiteMsg = "Access verification failed. Please check if you have logged in to the correct account and server.",
Jcdlppbocpe = true,
Gifijbibiin = true,
Ibplbfhmgkf = true,
MbResVersion = "5335706",
AssetBundleUrl = "https://autopatchos.starrails.com/asb/V1.3Live/output_5355192_0007722cfc",
ExResourceUrl = "https://autopatchos.starrails.com/design_data/V1.3Live/output_5371504_9fdb2fe63e",
})));
return true;
}
}
}

View file

@ -0,0 +1,37 @@
namespace FreeSR.Dispatch.Handlers
{
using Ceen;
using FreeSR.Dispatch.Util;
using FreeSR.Proto;
using Newtonsoft.Json.Linq;
using System.Threading.Tasks;
internal class RiskyApiCheckHandler : IHttpModule
{
public async Task<bool> HandleAsync(IHttpContext context)
{
context.Response.StatusCode = HttpStatusCode.OK;
context.Response.ContentType = "application/json";
await context.Response.WriteAllJsonAsync(DispatchResponseBuilder.Create()
.Retcode(Retcode.RETCODE_RET_SUCC)
.Message("OK")
.Object("data", CaptchaData)
.Build());
return true;
}
private static JObject CaptchaData
{
get
{
return new JObject
{
{"id", ""},
{"action", "ACTION_NONE"},
{"geetest", null}
};
}
}
}
}

View file

@ -0,0 +1,32 @@
namespace FreeSR.Dispatch.Handlers.Sdk
{
using Ceen;
using FreeSR.Database;
using FreeSR.Database.Account;
internal class CreateAccountHandler : IHttpModule
{
public async Task<bool> HandleAsync(IHttpContext context)
{
var query = context.Request.QueryString;
var name = query["user"];
var password = query["pass"];
var database = DatabaseManager.Instance.Get<AccountDatabase>();
var account = await database.Create(name, password);
context.Response.StatusCode = HttpStatusCode.OK;
if (account == null)
{
await context.Response.WriteAllAsync("Sorry, this username is already taken.", "text/plain");
}
else
{
await context.Response.WriteAllAsync($"Successfully created account with name {account.Name}, uid: {account.Uid}", "text/plain");
}
return true;
}
}
}

View file

@ -0,0 +1,20 @@
namespace FreeSR.Dispatch.Handlers
{
using Ceen;
using FreeSR.Dispatch.Util;
using System.Threading.Tasks;
internal class SdkDataUploadHandler : IHttpModule
{
public async Task<bool> HandleAsync(IHttpContext context)
{
context.Response.StatusCode = HttpStatusCode.OK;
context.Response.ContentType = "application/json";
await context.Response.WriteAllJsonAsync(DispatchResponseBuilder.Create()
.Code(0)
.Build());
return true;
}
}
}

View file

@ -0,0 +1,61 @@
namespace FreeSR.Dispatch.Handlers
{
using Ceen;
using FreeSR.Database;
using FreeSR.Database.Account;
using FreeSR.Database.Account.Model;
using FreeSR.Dispatch.Util;
using FreeSR.Proto;
using Newtonsoft.Json.Linq;
using System.Threading.Tasks;
internal class TokenLoginRequestHandler : IHttpModule
{
public async Task<bool> HandleAsync(IHttpContext context)
{
var data = await context.Request.Body.ReadAllAsStringAsync();
var json = JObject.Parse(data);
var uid = int.Parse((string)json["uid"]);
var token = (string)json["token"];
AccountDatabase accountDatabase = DatabaseManager.Instance.Get<AccountDatabase>();
AccountModel account = await accountDatabase.GetByUid(uid);
if (account == null)
{
await context.Response.WriteAllJsonAsync(DispatchResponseBuilder.Create()
.Retcode(Retcode.RETCODE_RET_FAIL)
.Message("Account not found.")
.Object("data", null)
.Build());
return true;
}
else if (account.Token != token)
{
await context.Response.WriteAllJsonAsync(DispatchResponseBuilder.Create()
.Retcode(Retcode.RETCODE_RET_FAIL)
.Message("Invalid user token.")
.Object("data", null)
.Build());
return true;
}
await context.Response.WriteAllJsonAsync(DispatchResponseBuilder.Create()
.Retcode(Retcode.RETCODE_RET_SUCC)
.Message("OK")
.Object("data", new JObject
{
{"account", account.ToLoginResponseData()},
{"device_grant_required", false},
{"safe_moblie_required", false},
{"realperson_required", false},
{"reactivate_required", false},
{"realname_operation", "None"}
})
.Build());
return true;
}
}
}

View file

@ -0,0 +1,45 @@
namespace FreeSR.Dispatch.Service
{
using Ceen.Httpd;
using Ceen.Httpd.Logging;
using FreeSR.Dispatch.Handlers;
using FreeSR.Dispatch.Handlers.Sdk;
using FreeSR.Shared.Configuration;
using System.Net;
internal static class HttpDispatchService
{
private static ServerConfig s_httpdConfiguration;
public static void Initialize(NetworkConfiguration config)
{
s_httpdConfiguration = CreateConfiguration();
_ = BootHttpAsync(config);
}
private static ServerConfig CreateConfiguration()
{
return new ServerConfig().AddLogger(new CLFStdOut())
.AddRoute("/query_dispatch", new QueryDispatchHandler())
.AddRoute("/query_gateway", new QueryGatewayHandler())
.AddRoute("/hkrpg_global/mdk/shield/api/login", new LoginRequestHandler())
.AddRoute("/hkrpg_global/combo/granter/login/v2/login", new ComboTokenRequestHandler())
.AddRoute("/hkrpg_global/mdk/shield/api/verify", new TokenLoginRequestHandler())
.AddRoute("/sdk/dataUpload", new SdkDataUploadHandler())
.AddRoute("/hkrpg/dataUpload", new HkrpgDataUploadHandler())
.AddRoute("/account/risky/api/check", new RiskyApiCheckHandler())
.AddRoute("/hkrpg_global/mdk/agreement/api/getAgreementInfos", new GetAgreementInfosHandler())
.AddRoute("/data_abtest_api/config/experiment/list", new GetExperimentListHandler())
.AddRoute("/hkrpg_global/combo/granter/api/getConfig", new ComboGranterApiGetConfigHandler())
.AddRoute("/sdk/createaccount", new CreateAccountHandler());
}
private static async Task BootHttpAsync(NetworkConfiguration config)
{
await HttpServer.ListenAsync(new IPEndPoint(
IPAddress.Parse(config.Host),
config.Port),
false, s_httpdConfiguration);
}
}
}

View file

@ -0,0 +1,31 @@
namespace FreeSR.Dispatch.Service.Manager
{
using FreeSR.Dispatch.Configuration;
using FreeSR.Proto;
internal static class RegionManager
{
private static RegionConfiguration s_configuration;
public static void Initialize(RegionConfiguration configuration)
{
s_configuration = configuration;
}
public static List<RegionEntry> GetRegionList()
{
var region = new RegionEntry
{
EnvType = s_configuration.EnvType,
DispatchUrl = s_configuration.DispatchUrl,
Name = s_configuration.Name,
DisplayName = s_configuration.Name,
Title = s_configuration.Name
};
return new List<RegionEntry> { region };
}
public static string GetTopServerRegionName() => s_configuration.Name;
}
}

View file

@ -0,0 +1,38 @@
namespace FreeSR.Dispatch.Util
{
using FreeSR.Database.Account.Model;
using Newtonsoft.Json.Linq;
internal static class DispatchHelper
{
public static JObject ToLoginResponseData(this AccountModel model)
{
return new JObject
{
{"uid", model.Uid},
{"name", model.Name},
{"email", ""},
{"mobile", ""},
{"is_email_verify", "0"},
{"realname", ""},
{"identity_card", ""},
{"safe_mobile", ""},
{"facebook_name", ""},
{"google_name", ""},
{"twitter_name", ""},
{"game_center_name", ""},
{"apple_name", ""},
{"sony_name", ""},
{"tap_name", ""},
{"country", "CN"},
{"reactivate_ticket", ""},
{"area_code", "**"},
{"device_grant_ticket", ""},
{"steam_name", ""},
{"unmasked_email", ""},
{"unmasked_email_type", 0},
{"token", model.Token}
};
}
}
}

View file

@ -0,0 +1,69 @@
namespace FreeSR.Dispatch.Util
{
using Newtonsoft.Json.Linq;
internal class DispatchResponseBuilder
{
private readonly JObject _jsonObject;
private DispatchResponseBuilder()
{
_jsonObject = new JObject();
}
public string Build()
{
return _jsonObject.ToString();
}
public DispatchResponseBuilder Code(int code)
{
_jsonObject["code"] = code;
return this;
}
public DispatchResponseBuilder Retcode(int retcode)
{
_jsonObject["retcode"] = retcode;
return this;
}
public DispatchResponseBuilder Message(string message)
{
_jsonObject["message"] = message;
return this;
}
public DispatchResponseBuilder Boolean(string field, bool value)
{
_jsonObject[field] = value;
return this;
}
public DispatchResponseBuilder String(string field, string value)
{
_jsonObject[field] = value;
return this;
}
public DispatchResponseBuilder Int(string field, int value)
{
_jsonObject[field] = value;
return this;
}
public DispatchResponseBuilder Array(string field, JArray array)
{
_jsonObject[field] = array;
return this;
}
public DispatchResponseBuilder Object(string field, JObject jsonObject)
{
_jsonObject[field] = jsonObject;
return this;
}
public static DispatchResponseBuilder Create() => new DispatchResponseBuilder();
}
}

View file

@ -0,0 +1,15 @@
namespace FreeSR.Dispatch.Util
{
using ProtoBuf;
internal static class ProtobufUtil
{
public static byte[] Serialize<T>(T obj) where T : class
{
var stream = new MemoryStream();
Serializer.Serialize(stream, obj);
return stream.ToArray();
}
}
}

View file

@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DotNetty.Handlers" Version="0.7.5" />
<PackageReference Include="Nito.AsyncEx.Context" Version="5.1.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FreeSR.Proto\FreeSR.Proto.csproj" />
<ProjectReference Include="..\FreeSR.Shared\FreeSR.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="GateServer.example.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View file

@ -0,0 +1,47 @@
namespace FreeSR.Gateserver
{
using FreeSR.Gateserver.Network;
using FreeSR.Shared.Configuration;
using FreeSR.Shared.Exceptions;
using NLog;
internal static class GateServer
{
private const string Title = "FreeSR Gate Server (EXPERIMENTAL OPEN SOURCE BUILD)";
private static readonly Logger s_log = LogManager.GetCurrentClassLogger();
private static void Main(string[] args)
{
Directory.SetCurrentDirectory(AppContext.BaseDirectory);
AppDomain.CurrentDomain.UnhandledException += OnFatalException;
Console.Title = Title;
s_log.Info("Initializing...");
ConfigurationManager<GateServerConfiguration>.Instance.Initialize("GateServer.json");
var serverConfiguration = ConfigurationManager<GateServerConfiguration>.Instance.Model;
NetworkManager.Instance.Initialize(serverConfiguration.Network).GetAwaiter().GetResult();
s_log.Info("Server is ready!");
Thread.Sleep(-1); // TODO: Console handler
}
private static void OnFatalException(object sender, UnhandledExceptionEventArgs args)
{
if (args.ExceptionObject is ServerInitializationException initException)
{
Console.WriteLine("Server initialization failed, unhandled exception!");
Console.WriteLine(initException);
}
else
{
Console.WriteLine("Unhandled exception in runtime!");
Console.WriteLine(args.ExceptionObject);
}
Console.WriteLine("Press enter to close this window...");
Console.ReadLine();
}
}
}

View file

@ -0,0 +1,6 @@
{
"Network": {
"Host": "0.0.0.0",
"Port": 22301
}
}

View file

@ -0,0 +1,9 @@
namespace FreeSR.Gateserver
{
using FreeSR.Shared.Configuration;
internal class GateServerConfiguration
{
public NetworkConfiguration Network { get; set; }
}
}

View file

@ -0,0 +1,40 @@
namespace FreeSR.Gateserver.Manager.Handlers
{
using FreeSR.Gateserver.Manager.Handlers.Core;
using FreeSR.Gateserver.Network;
using FreeSR.Proto;
internal static class AvatarReqGroup
{
[Handler(CmdType.GetAvatarDataCsReq)]
public static void OnGetAvatarDataCsReq(NetSession session, int cmdId, object data)
{
var request = data as GetAvatarDataCsReq;
var response = new GetAvatarDataScRsp
{
Retcode = Retcode.RETCODE_RET_SUCC,
IsAll = request.IsGetAll,
AvatarList = new List<Avatar>()
};
int[] characters = new int[] { 8001, 1005, 1003 };
foreach (int id in characters)
{
response.AvatarList.Add(new Avatar
{
BaseAvatarId = id,
Exp = 0,
Level = 1,
Promotion = 0,
Rank = 6,
SkilltreeList = new List<AvatarSkillTree>(),
EquipmentUniqueId = 0
});
}
session.Send(CmdType.GetAvatarDataScRsp, response);
}
}
}

View file

@ -0,0 +1,7 @@
namespace FreeSR.Gateserver.Manager.Handlers
{
internal static class ChallengeReqGroup
{
// ChallengeReqGroup.
}
}

View file

@ -0,0 +1,18 @@
namespace FreeSR.Gateserver.Manager.Handlers.Core
{
using FreeSR.Gateserver.Network;
using FreeSR.Proto;
[AttributeUsage(AttributeTargets.Method)]
internal class HandlerAttribute : Attribute
{
public int CmdID { get; }
public HandlerAttribute(CmdType cmdID)
{
this.CmdID = (int)cmdID;
}
public delegate void HandlerDelegate(NetSession session, int cmdId, object data);
}
}

View file

@ -0,0 +1,343 @@
namespace FreeSR.Gateserver.Manager.Handlers
{
using FreeSR.Gateserver.Manager.Handlers.Core;
using FreeSR.Gateserver.Network;
using FreeSR.Proto;
using static System.Net.WebRequestMethods;
using System.Collections.Generic;
using System.Net.Http.Headers;
using System.ComponentModel.Design;
internal static class GachaReqGroup
{
[Handler(CmdType.GetGachaInfoCsReq)]
public static void OnGetGachaInfoCsReq(NetSession session, int cmdId, object data)
{
var Gacha1List = new List<int>();
session.Send(CmdType.GetGachaInfoScRsp, new GetGachaInfoScRsp
{
Retcode = Retcode.RETCODE_RET_SUCC,
GachaRandom = 0,
GachaInfoList = new List<GachaInfo>
{
new GachaInfo
{
//ENJHJHKNGOG = "https://webstatic-sea.hoyoverse.com/hkrpg/event/e20211215gacha-v2/index.html?authkey_ver=1&sign_type=2&auth_appid=webview_gacha&win_mode=fullscreen#/log",
Dmdldgldfdj = new List<int>(1208),
//JDMIIMJFAPK = "https://webstatic-sea.hoyoverse.com/hkrpg/event/e20211215gacha-v2/index.html?authkey_ver=1&sign_type=2&auth_appid=webview_gacha&win_mode=fullscreen&gacha_id=ad9815cdf2308104c377aac42c7f0cdd8d&timestamp=1689725163",
Pldioknjpjj = new List<int>{1208, 1110, 1109, 1106},
BeginTime = 1689719400,
EndTime = 4070880000,
GachaId = 2010
}
}
});
}
[Handler(CmdType.DoGachaCsReq)]
public static void OnDoGachaCsReq(NetSession session, int cmdId, object data)
{
var gachaReq = data as DoGachaCsReq;
if (gachaReq.GachaNum == 1)
{
session.Send(CmdType.DoGachaScRsp, new DoGachaScRsp
{
GachaId = gachaReq.GachaId,
Nmaojeiedak = gachaReq.GachaRandom + 1,
GachaNum = gachaReq.GachaNum,
GachaItemList = new List<GachaItem>
{
new GachaItem
{
Item = new List<Item>
{
new Item
{
ItemId = 1107,
Num = 1,
}
}[0],
Dpjjnjflnjm = new List<ItemList>
{
new ItemList
{
List = new List<Item>
{
new Item
{
ItemId = 252,
Num = 40,
}
}
}
}[0]
}
},
});
}
if (gachaReq.GachaNum == 10)
{
session.Send(CmdType.DoGachaScRsp, new DoGachaScRsp
{
GachaId = gachaReq.GachaId,
Nmaojeiedak = gachaReq.GachaRandom + 10,
GachaNum = gachaReq.GachaNum,
GachaItemList = new List<GachaItem>
{
new GachaItem
{
Item = new List<Item>
{
new Item
{
ItemId = 1208,
Num = 1,
}
}[0],
Dpjjnjflnjm = new List<ItemList>
{
new ItemList
{
List = new List<Item>
{
new Item
{
ItemId = 252,
Num = 40,
}
}
}
}[0]
},
new GachaItem
{
Item = new List<Item>
{
new Item
{
ItemId = 1208,
Num = 1,
}
}[0],
Dpjjnjflnjm = new List<ItemList>
{
new ItemList
{
List = new List<Item>
{
new Item
{
ItemId = 252,
Num = 40,
}
}
}
}[0]
},
new GachaItem
{
Item = new List<Item>
{
new Item
{
ItemId = 1208,
Num = 1,
}
}[0],
Dpjjnjflnjm = new List<ItemList>
{
new ItemList
{
List = new List<Item>
{
new Item
{
ItemId = 252,
Num = 40,
}
}
}
}[0]
},
new GachaItem
{
Item = new List<Item>
{
new Item
{
ItemId = 1208,
Num = 1,
}
}[0],
Dpjjnjflnjm = new List<ItemList>
{
new ItemList
{
List = new List<Item>
{
new Item
{
ItemId = 252,
Num = 40,
}
}
}
}[0]
},
new GachaItem
{
Item = new List<Item>
{
new Item
{
ItemId = 1208,
Num = 1,
}
}[0],
Dpjjnjflnjm = new List<ItemList>
{
new ItemList
{
List = new List<Item>
{
new Item
{
ItemId = 252,
Num = 40,
}
}
}
}[0]
},
new GachaItem
{
Item = new List<Item>
{
new Item
{
ItemId = 1208,
Num = 1,
}
}[0],
Dpjjnjflnjm = new List<ItemList>
{
new ItemList
{
List = new List<Item>
{
new Item
{
ItemId = 252,
Num = 40,
}
}
}
}[0]
},
new GachaItem
{
Item = new List<Item>
{
new Item
{
ItemId = 1208,
Num = 1,
}
}[0],
Dpjjnjflnjm = new List<ItemList>
{
new ItemList
{
List = new List<Item>
{
new Item
{
ItemId = 252,
Num = 40,
}
}
}
}[0]
},
new GachaItem
{
Item = new List<Item>
{
new Item
{
ItemId = 1208,
Num = 1,
}
}[0],
Dpjjnjflnjm = new List<ItemList>
{
new ItemList
{
List = new List<Item>
{
new Item
{
ItemId = 252,
Num = 40,
}
}
}
}[0]
},
new GachaItem
{
Item = new List<Item>
{
new Item
{
ItemId = 1208,
Num = 1,
}
}[0],
Dpjjnjflnjm = new List<ItemList>
{
new ItemList
{
List = new List<Item>
{
new Item
{
ItemId = 252,
Num = 40,
}
}
}
}[0]
},
new GachaItem
{
Item = new List<Item>
{
new Item
{
ItemId = 1208,
Num = 1,
}
}[0],
Dpjjnjflnjm = new List<ItemList>
{
new ItemList
{
List = new List<Item>
{
new Item
{
ItemId = 252,
Num = 40,
}
}
}
}[0]
}
},
});
}
}
}
}

View file

@ -0,0 +1,30 @@
namespace FreeSR.Gateserver.Manager.Handlers
{
using FreeSR.Gateserver.Manager.Handlers.Core;
using FreeSR.Gateserver.Network;
using FreeSR.Proto;
internal static class ItemReqGroup
{
[Handler(CmdType.GetBagCsReq)]
public static void OnGetBagCsReq(NetSession session, int cmdId, object _)
{
session.Send(CmdType.GetBagScRsp, new GetBagScRsp
{
Retcode = Retcode.RETCODE_RET_SUCC,
MaterialList = new List<Material>
{
new Material
{
Tid = 101,
Num = 10
},
new Material
{
Tid = 102,
Num = 10
}
}
});
}
}
}

View file

@ -0,0 +1,92 @@
namespace FreeSR.Gateserver.Manager.Handlers
{
using FreeSR.Gateserver.Manager.Handlers.Core;
using FreeSR.Gateserver.Network;
using FreeSR.Proto;
internal static class LineupReqGroup
{
[Handler(CmdType.GetCurLineupDataCsReq)]
public static void OnGetCurLineupDataCsReq(NetSession session, int cmdId, object _)
{
var response = new GetCurLineupDataScRsp
{
Retcode = Retcode.RETCODE_RET_SUCC
};
response.Lineup = new LineupInfo
{
ExtraLineupType = ExtraLineupType.LINEUP_NONE,
Name = "Squad 1",
AvatarList = new List<LineupAvatar>(),
LeaderSlot = 5,
Foafdgjflmc = 5
};
var characters = new int[] { 8001, 1005, 1003 };
foreach (int id in characters)
{
response.Lineup.AvatarList.Add(new LineupAvatar
{
AvatarType = AvatarType.AVATAR_FORMAL_TYPE,
CurHealth = new HealthBarInfo { CurHp = 10000, MaxHp = 10000 },
Sp = 10000,
Satiety = 100,
Id = id,
Slot = response.Lineup.AvatarList.Count
});
}
session.Send(CmdType.GetCurLineupDataScRsp, response);
}
[Handler(CmdType.GetAllLineupDataCsReq)]
public static void OnGetAllLineupDataCsReq(NetSession session, int cmdId, object data)
{
var response = new GetAllLineupDataScRsp
{
Retcode = Retcode.RETCODE_RET_SUCC,
CurIndex = 0,
LineupList = new List<LineupInfo>()
};
response.LineupList.Add(new LineupInfo
{
ExtraLineupType = ExtraLineupType.LINEUP_NONE,
Name = "Squad 1",
AvatarList = new List<LineupAvatar>(),
Foafdgjflmc = 5,
LeaderSlot = 3
});
var characters = new int[] { 8001, 1005, 1003 };
foreach (int id in characters)
{
response.LineupList[0].AvatarList.Add(new LineupAvatar
{
AvatarType = AvatarType.AVATAR_FORMAL_TYPE,
CurHealth = new HealthBarInfo { CurHp = 10000, MaxHp = 10000 },
Sp = 10000,
Satiety = 100,
Id = id,
Slot = response.LineupList[0].AvatarList.Count
});
}
session.Send(CmdType.GetAllLineupDataScRsp, response);
}
[Handler(CmdType.ChangeLineupLeaderCsReq)]
public static void OnChangeLineupLeaderCsReq(NetSession session, int cmdId, object data)
{
var request = data as ChangeLineupLeaderCsReq;
session.Send(CmdType.ChangeLineupLeaderScRsp, new ChangeLineupLeaderScRsp
{
Slot = request.Slot,
Retcode = Retcode.RETCODE_RET_SUCC
});
}
}
}

View file

@ -0,0 +1,7 @@
namespace FreeSR.Gateserver.Manager.Handlers
{
internal static class MailReqGroup
{
// MailReqGroup
}
}

View file

@ -0,0 +1,87 @@
namespace FreeSR.Gateserver.Manager.Handlers
{
using FreeSR.Gateserver.Manager.Handlers.Core;
using FreeSR.Gateserver.Network;
using FreeSR.Proto;
internal static class MissionReqGroup
{
[Handler(CmdType.GetMissionStatusCsReq)]
public static void OnGetMissionStatusCsReq(NetSession session, int cmdId, object data)
{
var request = data as GetMissionStatusCsReq;
GetMissionStatusScRsp response = new GetMissionStatusScRsp
{
Retcode = Retcode.RETCODE_RET_SUCC,
DisabledMainMissionIdList = new List<int>(),
FinishedMainMissionIdList = new List<int>(),
MissionEventStatusList = new List<Mission>(),
SubMissionStatusList = new List<Mission>(),
UnfinishedMainMissionIdList = new List<int>()
};
response.FinishedMainMissionIdList = new List<int>
{
1000101,
1000112,
1000113,
1000201,
1000202,
1000204,
1000301,
1000401,
1000402,
1000410,
1000510,
1000601,
1010301,
1010302,
1010401,
1010403,
1010701,
1011403,
1010202,
1010902,
1011102,
4010101
};
if (request.MissionEventIdList != null)
{
foreach (int id in request.MissionEventIdList)
{
response.UnfinishedMainMissionIdList.Add(id);
}
}
if (request.SubMissionIdList != null)
{
foreach (int id in request.SubMissionIdList)
{
response.MissionEventStatusList.Add(new Mission()
{
Id = id,
Progress = 0,
Status = MissionStatus.MISSION_FINISH
});
}
}
if (request.MainMissionIdList != null)
{
foreach (int id in request.MainMissionIdList)
{
response.SubMissionStatusList.Add(new Mission()
{
Id = id,
Progress = 0,
Status = MissionStatus.MISSION_FINISH
});
}
}
session.Send(CmdType.GetMissionStatusScRsp, response);
}
}
}

View file

@ -0,0 +1,41 @@
namespace FreeSR.Gateserver.Manager.Handlers
{
using FreeSR.Gateserver.Manager.Handlers.Core;
using FreeSR.Gateserver.Network;
using FreeSR.Proto;
using System.ComponentModel.Design;
internal static class NPCReqGroup
{
//maybe useless
[Handler(CmdType.GetNpcTakenRewardCsReq)]
public static void OnGetNpcTakenRewardCsReq(NetSession session, int cmdId, object data)
{
var npcRewardReq = data as GetNpcTakenRewardCsReq;
session.Send(CmdType.GetNpcTakenRewardScRsp, new GetNpcTakenRewardScRsp
{
NpcId = npcRewardReq.NpcId,
Retcode = Retcode.RETCODE_RET_SUCC
});
}
[Handler(CmdType.GetFirstTalkByPerformanceNpcCsReq)]
public static void OnGetFirstTalkByPerformanceNpcCsReq(NetSession session, int cmdId, object data)
{
session.Send(CmdType.GetFirstTalkByPerformanceNpcScRsp, new GetFirstTalkByPerformanceNpcScRsp
{
Retcode = Retcode.RETCODE_RET_SUCC,
NpcMeetStatusList = new List<OCLEPLBNNPA>
{
new OCLEPLBNNPA
{
IsMeet = true,
Jljhobhmaof = 1
},
}
});
}
}
}

View file

@ -0,0 +1,113 @@
namespace FreeSR.Gateserver.Manager.Handlers
{
using FreeSR.Gateserver.Manager.Handlers.Core;
using FreeSR.Gateserver.Network;
using FreeSR.Proto;
using NLog;
internal static class PlayerReqGroup
{
private static readonly Logger s_log = LogManager.GetCurrentClassLogger();
[Handler(CmdType.PlayerHeartBeatCsReq)]
public static void OnPlayerHeartBeatCsReq(NetSession session, int cmdId, object data)
{
var heartbeatReq = data as PlayerHeartBeatCsReq;
session.Send(CmdType.PlayerHeartBeatScRsp, new PlayerHeartBeatScRsp
{
Retcode = Retcode.RETCODE_RET_SUCC,
DownloadData = new ClientDownloadData(),
ClientTimeMs = heartbeatReq.ClientTimeMs,
ServerTimeMs = DateTimeOffset.Now.ToUnixTimeMilliseconds()
});
}
[Handler(CmdType.GetHeroBasicTypeInfoCsReq)]
public static void OnGetHeroBasicTypeInfoCsReq(NetSession session, int cmdId, object _)
{
session.Send(CmdType.GetHeroBasicTypeInfoScRsp, new GetHeroBasicTypeInfoScRsp
{
Retcode = Retcode.RETCODE_RET_SUCC,
Gender = Gender.GenderMan,
BasicTypeInfoList = new List<HeroBasicTypeInfo>
{
new HeroBasicTypeInfo
{
BasicType = HeroBasicType.BoyWarrior,
Rank = 1,
SkillTreeList = new List<AvatarSkillTree>()
}
},
CurBasicType = HeroBasicType.BoyWarrior,
IsPlayerInfoModified = false,
IsGenderModified = false
});
}
[Handler(CmdType.GetBasicInfoCsReq)]
public static void OnGetBasicInfoCsReq(NetSession session, int cmdId, object _)
{
session.Send(CmdType.GetBasicInfoScRsp, new GetBasicInfoScRsp
{
CurDay = 1,
ExchangeTimes = 0,
Retcode = 0,
NextRecoverTime = 2281337,
WeekCocoonFinishedCount = 0
});
}
[Handler(CmdType.PlayerLoginCsReq)]
public static void OnPlayerLoginCsReq(NetSession session, int cmdId, object data)
{
var request = data as PlayerLoginCsReq;
session.Send(CmdType.PlayerLoginScRsp, new PlayerLoginScRsp
{
Retcode = Retcode.RETCODE_RET_SUCC,
IsNewPlayer = false,
LoginRandom = request.LoginRandom,
Stamina = 100,
ServerTimestampMs = DateTimeOffset.Now.ToUnixTimeSeconds() * 1000,
BasicInfo = new PlayerBasicInfo
{
Nickname = "xeondev",
Level = 30,
Exp = 0,
Stamina = 100,
MCoin = 0,
HCoin = 0,
SCoin = 0,
WorldLevel = 0
}
});
}
[Handler(CmdType.PlayerGetTokenCsReq)]
public static void OnPlayerGetTokenCsReq(NetSession session, int cmdId, object data)
{
var request = data as PlayerGetTokenCsReq;
session.Send(CmdType.PlayerGetTokenScRsp, new PlayerGetTokenScRsp
{
Retcode = Retcode.RETCODE_RET_SUCC,
Uid = int.Parse(request.AccountUid),
BlackInfo = null,
Msg = null,
SecretKeySeed = 0
});
session.Send(CmdType.BattlePassInfoNotify, new BattlePassInfoNotify
{
Ibkdaabmege = ILGFODEJBBH.BP_TIER_TYPE_PREMIUM_2,
Caajdlolcml = 0,
Ipneaeepcmk = 4,
Okffhjicndl = 0,
Exp = 1000,
Level = 50
});
}
}
}

View file

@ -0,0 +1,7 @@
namespace FreeSR.Gateserver.Manager.Handlers
{
internal static class QuestReqGroup
{
// QuestReqGroup
}
}

View file

@ -0,0 +1,45 @@
namespace FreeSR.Gateserver.Manager.Handlers
{
using FreeSR.Gateserver.Manager.Handlers.Core;
using FreeSR.Gateserver.Network;
using FreeSR.Proto;
internal static class SceneReqGroup
{
[Handler(CmdType.GetCurSceneInfoCsReq)]
public static void OnGetCurSceneInfoCsReq(NetSession session, int cmdId, object data)
{
SceneInfo scene = new SceneInfo
{
GameModeType = 1,
Bkmbkahohif = 1,
Admbbnbnibk = 1,
EntryId = 1000101,
PlaneId = 10001,
FloorId = 10001001,
EntityList = new List<SceneEntityInfo>(),
EnvBuffList = new List<BuffInfo>(),
LightenSectionList = new List<int>()
};
scene.EntityList.Add(new SceneEntityInfo
{
EntityId = 0,
GroupId = 0,
InstId = 0,
Motion = new MotionInfo()
{
Pos = new Vector(),
Rot = new Vector()
}
});
session.Send(CmdType.GetCurSceneInfoScRsp, new GetCurSceneInfoScRsp
{
Scene = scene,
Retcode = Retcode.RETCODE_RET_SUCC
});
}
}
}

View file

@ -0,0 +1,70 @@
namespace FreeSR.Gateserver.Manager.Handlers
{
using FreeSR.Gateserver.Manager.Handlers.Core;
using FreeSR.Gateserver.Network;
using FreeSR.Proto;
using NLog;
internal static class TutorialReqGroup
{
private static readonly Logger s_log = LogManager.GetCurrentClassLogger();
[Handler(CmdType.GetTutorialGuideCsReq)]
public static void OnGetTutorialGuideCsReq(NetSession session, int cmdId, object _)
{
var response = new GetTutorialGuideScRsp
{
Retcode = Retcode.RETCODE_RET_SUCC,
TutorialGuideList = new List<TutorialGuide>()
};
int[] guides = new int[]
{
1101, 1102, 1104, 1105, 1116, 1117, 2006, 2007, 2101, 2105, 2106, 2107, 3007, 3105, 3106, 4001, 4101, 4102, 4103, 4104, 4105, 4106, 4107, 4108, 4109, 5101, 5102, 5103, 5104, 5105, 6001, 6002, 6003, 6004, 6005, 6006, 6007, 9101, 9102, 9103, 9104, 9105, 9106, 9107, 9108
};
foreach (int id in guides)
{
response.TutorialGuideList.Add(new TutorialGuide
{
Id = id,
Status = TutorialStatus.TUTORIAL_FINISH
});
}
session.Send(CmdType.GetTutorialGuideScRsp, response);
}
[Handler(CmdType.GetTutorialCsReq)]
public static void OnGetTutorialCsReq(NetSession session, int cmdId, object _)
{
int[] completedTutorials = new int[]
{
1001, 1002, 1003, 1004, 1005, 1007, 1008, 1010, 1011,
2001, 2002, 2003, 2004, 2005, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015,
3001, 3002, 3003, 3004, 3005, 3006,
4002, 4003, 4004, 4005, 4006, 4007, 4008, 4009,
5001, 5002, 5003, 5004, 5005, 5006, 5007, 5008, 5009, 5010, 5011, 5012,
7001,
9001, 9002, 9003, 9004, 9005, 9006
};
var response = new GetTutorialScRsp
{
Retcode = Retcode.RETCODE_RET_SUCC,
TutorialList = new List<Tutorial>()
};
foreach (int id in completedTutorials)
{
response.TutorialList.Add(new Tutorial
{
Id = id,
Status = TutorialStatus.TUTORIAL_FINISH
});
}
session.Send(CmdType.GetTutorialScRsp, response);
}
}
}

View file

@ -0,0 +1,68 @@
namespace FreeSR.Gateserver.Manager
{
using FreeSR.Gateserver.Manager.Handlers.Core;
using FreeSR.Gateserver.Network;
using Nito.AsyncEx;
using NLog;
using System.Collections.Immutable;
using System.Linq.Expressions;
using System.Reflection;
internal static class NotifyManager
{
private static readonly Logger s_log = LogManager.GetCurrentClassLogger();
private static List<Type> s_handlerTypes = new List<Type>();
private static ImmutableDictionary<int, (HandlerAttribute, HandlerAttribute.HandlerDelegate)> s_notifyReqGroup;
public static void Init()
{
var handlers = ImmutableDictionary.CreateBuilder<int, (HandlerAttribute, HandlerAttribute.HandlerDelegate)>();
foreach (var type in s_handlerTypes)
{
foreach (var method in type.GetMethods())
{
var attribute = method.GetCustomAttribute<HandlerAttribute>();
if (attribute == null)
continue;
var parameterInfo = method.GetParameters();
var sessionParameter = Expression.Parameter(typeof(NetSession));
var cmdIdParameter = Expression.Parameter(typeof(int));
var dataParameter = Expression.Parameter(typeof(object));
var call = Expression.Call(method,
Expression.Convert(sessionParameter, parameterInfo[0].ParameterType),
Expression.Convert(cmdIdParameter, parameterInfo[1].ParameterType),
Expression.Convert(dataParameter, parameterInfo[2].ParameterType));
var lambda = Expression.Lambda<HandlerAttribute.HandlerDelegate>(call, sessionParameter, cmdIdParameter, dataParameter);
if (!handlers.TryGetKey(attribute.CmdID, out _))
handlers.Add(attribute.CmdID, (attribute, lambda.Compile()));
}
}
s_notifyReqGroup = handlers.ToImmutable();
}
public static void Notify(NetSession session, int cmdId, object data)
{
if (s_notifyReqGroup.TryGetValue(cmdId, out var handler))
{
AsyncContext.Run(() => handler.Item2.Invoke(session, cmdId, data));
}
else
{
s_log.Warn($"Can't find handler, cmdId: {cmdId}");
}
}
public static void AddReqGroupHandler(Type type)
{
s_handlerTypes.Add(type);
}
}
}

View file

@ -0,0 +1,53 @@
namespace FreeSR.Gateserver.Network.Factory
{
using FreeSR.Proto;
using ProtoBuf;
using System.Collections.Immutable;
internal static class ProtoFactory
{
private static readonly ImmutableDictionary<CmdType, Type> s_types;
static ProtoFactory()
{
var builder = ImmutableDictionary.CreateBuilder<CmdType, Type>();
builder.AddRange(new Dictionary<CmdType, Type>()
{
{CmdType.PlayerGetTokenCsReq, typeof(PlayerGetTokenCsReq)},
{CmdType.PlayerLoginCsReq, typeof(PlayerLoginCsReq)},
{CmdType.GetAvatarDataCsReq, typeof(GetAvatarDataCsReq)},
{CmdType.GetAllLineupDataCsReq, typeof(GetAllLineupDataCsReq)},
{CmdType.GetCurLineupDataCsReq, typeof(GetCurLineupDataCsReq)},
{CmdType.ChangeLineupLeaderCsReq, typeof(ChangeLineupLeaderCsReq)},
{CmdType.GetMissionStatusCsReq, typeof(GetMissionStatusCsReq)},
{CmdType.GetQuestDataCsReq, typeof(GetQuestDataCsReq)},
{CmdType.GetChallengeCsReq, typeof(GetChallengeCsReq)},
{CmdType.GetCurSceneInfoCsReq, typeof(GetCurSceneInfoCsReq)},
{CmdType.GetBasicInfoCsReq, typeof(GetBasicInfoCsReq)},
{CmdType.GetHeroBasicTypeInfoCsReq, typeof(GetHeroBasicTypeInfoCsReq)},
{CmdType.PlayerHeartBeatCsReq, typeof(PlayerHeartBeatCsReq)},
{CmdType.GetGachaInfoCsReq, typeof(GetGachaInfoCsReq)},
{CmdType.DoGachaCsReq, typeof(DoGachaCsReq)},
{CmdType.GetNpcTakenRewardCsReq, typeof(GetNpcTakenRewardCsReq)},
{CmdType.GetFirstTalkByPerformanceNpcCsReq, typeof(GetFirstTalkByPerformanceNpcCsReq)},
{CmdType.GetBagCsReq, typeof(GetBagCsReq)}
});
s_types = builder.ToImmutable();
}
public static object Deserialize(int id, byte[] rawData)
{
if (s_types.TryGetValue((CmdType)id, out var type))
return Serializer.Deserialize(type, new MemoryStream(rawData));
return null;
}
}
}

View file

@ -0,0 +1,29 @@
namespace FreeSR.Gateserver.Network.Handlers.Decoder
{
using DotNetty.Buffers;
using DotNetty.Codecs;
using DotNetty.Transport.Channels;
using FreeSR.Gateserver.Network.Packet;
using NLog;
internal class PacketDecoder : MessageToMessageDecoder<IByteBuffer>
{
private static readonly Logger s_log = LogManager.GetCurrentClassLogger();
protected override void Decode(IChannelHandlerContext context, IByteBuffer message, List<object> output)
{
var netPacket = new NetPacket();
DeserializationResult result;
if ((result = netPacket.Deserialize(message)) != DeserializationResult.SUCC)
{
context.CloseAsync();
s_log.Info("Closing connection, reason: " + result);
return;
}
output.Add(netPacket);
}
}
}

View file

@ -0,0 +1,53 @@
namespace FreeSR.Gateserver.Network.Handlers.Decoder
{
using DotNetty.Buffers;
using DotNetty.Codecs;
using DotNetty.Transport.Channels;
internal class StarRailHeaderDecoder : ByteToMessageDecoder
{
private IByteBuffer _current;
public StarRailHeaderDecoder()
{
_current = Unpooled.Buffer();
}
protected override void Decode(IChannelHandlerContext context, IByteBuffer input, List<object> output)
{
var allocated = Unpooled.Buffer();
allocated.WriteBytes(_current);
allocated.WriteBytes(input);
_current = allocated;
byte[] lands = new byte[_current.ReadableBytes];
_current.ReadBytes(lands);
_current.ResetReaderIndex();
IByteBuffer packet;
while ((packet = Process()) != null)
output.Add(packet);
}
private IByteBuffer Process()
{
if (_current.ReadableBytes < 12)
return null;
_current.ReadInt(); // headMagic
_current.ReadShort(); // CmdID
int headerLength = _current.ReadShort();
int payloadLength = _current.ReadInt();
int totalLength = 12 + headerLength + payloadLength + 4;
_current.ResetReaderIndex();
if (_current.ReadableBytes < totalLength)
return null;
IByteBuffer result = _current.ReadBytes(totalLength);
_current = _current.ReadBytes(_current.ReadableBytes);
return result;
}
}
}

View file

@ -0,0 +1,19 @@
namespace FreeSR.Gateserver.Network.Handlers.Encoder
{
using DotNetty.Buffers;
using DotNetty.Codecs;
using DotNetty.Transport.Channels;
using FreeSR.Gateserver.Network.Packet;
using NLog;
internal class PacketEncoder : MessageToByteEncoder<NetPacket>
{
private static readonly Logger s_log = LogManager.GetCurrentClassLogger();
protected override void Encode(IChannelHandlerContext context, NetPacket message, IByteBuffer output)
{
output.WriteBytes(message.Buf);
s_log.Info($"Sent packet with cmdId {message.CmdId}");
}
}
}

View file

@ -0,0 +1,103 @@
namespace FreeSR.Gateserver.Network.Handlers.Manager
{
using DotNetty.Transport.Channels;
using FreeSR.Gateserver.Manager;
using FreeSR.Gateserver.Network.Packet;
using FreeSR.Proto;
using NLog;
using ProtoBuf;
internal class PacketHandler : ChannelHandlerAdapter
{
private static readonly Logger s_log = LogManager.GetCurrentClassLogger();
private readonly NetSession _session;
public PacketHandler(NetSession session)
{
_session = session;
}
public override void ChannelRead(IChannelHandlerContext context, object message)
{
NetPacket packet = message as NetPacket;
if (packet.Data == null)
{
if (!SendDummyResponse(packet.CmdId))
s_log.Warn($"CmdID {packet.CmdId} is undefined.");
return;
}
s_log.Info($"Received packet {packet.CmdId}!");
NotifyManager.Notify(_session, packet.CmdId, packet.Data);
}
private bool SendDummyResponse(int id)
{
if (s_dummyTable.TryGetValue((CmdType)id, out CmdType rspId))
{
_session.Send(rspId, new DummyPacket());
return true;
}
return false;
}
private static Dictionary<CmdType, CmdType> s_dummyTable = new Dictionary<CmdType, CmdType>
{
{CmdType.GetLevelRewardTakenListCsReq, CmdType.GetLevelRewardTakenListScRsp},
{CmdType.GetRogueScoreRewardInfoCsReq, CmdType.GetRogueScoreRewardInfoScRsp},
{CmdType.GetGachaInfoCsReq, CmdType.GetGachaInfoScRsp},
{CmdType.QueryProductInfoCsReq, CmdType.QueryProductInfoScRsp},
{CmdType.GetQuestDataCsReq, CmdType.GetQuestDataScRsp},
{CmdType.GetQuestRecordCsReq, CmdType.GetQuestRecordScRsp},
{CmdType.GetFriendListInfoCsReq, CmdType.GetFriendListInfoScRsp},
{CmdType.GetFriendApplyListInfoCsReq, CmdType.GetFriendApplyListInfoScRsp},
{CmdType.GetCurAssistCsReq, CmdType.GetCurAssistScRsp},
{CmdType.GetRogueHandbookDataCsReq, CmdType.GetRogueHandbookDataScRsp},
{CmdType.GetDailyActiveInfoCsReq, CmdType.GetDailyActiveInfoScRsp},
{CmdType.GetFightActivityDataCsReq, CmdType.GetFightActivityDataScRsp},
{CmdType.GetMultipleDropInfoCsReq, CmdType.GetMultipleDropInfoScRsp},
{CmdType.GetPlayerReturnMultiDropInfoCsReq, CmdType.GetPlayerReturnMultiDropInfoScRsp},
{CmdType.GetShareDataCsReq, CmdType.GetShareDataScRsp},
{CmdType.GetTreasureDungeonActivityDataCsReq, CmdType.GetTreasureDungeonActivityDataScRsp},
{CmdType.PlayerReturnInfoQueryCsReq, CmdType.PlayerReturnInfoQueryScRsp},
{CmdType.GetBasicInfoCsReq, CmdType.GetBasicInfoScRsp},
{CmdType.GetHeroBasicTypeInfoCsReq, CmdType.GetHeroBasicTypeInfoScRsp},
{CmdType.GetBagCsReq, CmdType.GetBagScRsp},
{CmdType.GetPlayerBoardDataCsReq, CmdType.GetPlayerBoardDataScRsp},
{CmdType.GetAvatarDataCsReq, CmdType.GetAvatarDataScRsp},
{CmdType.GetAllLineupDataCsReq, CmdType.GetAllLineupDataScRsp},
{CmdType.GetActivityScheduleConfigCsReq, CmdType.GetActivityScheduleConfigScRsp},
{CmdType.GetMissionDataCsReq, CmdType.GetMissionDataScRsp},
{CmdType.GetMissionEventDataCsReq, CmdType.GetMissionEventDataScRsp},
{CmdType.GetChallengeCsReq, CmdType.GetChallengeScRsp},
{CmdType.GetCurChallengeCsReq, CmdType.GetCurChallengeScRsp},
{CmdType.GetRogueInfoCsReq, CmdType.GetRogueInfoScRsp},
{CmdType.GetExpeditionDataCsReq, CmdType.GetExpeditionDataScRsp},
{CmdType.GetRogueDialogueEventDataCsReq, CmdType.GetRogueDialogueEventDataScRsp},
{CmdType.GetJukeboxDataCsReq, CmdType.GetJukeboxDataScRsp},
{CmdType.SyncClientResVersionCsReq, CmdType.SyncClientResVersionScRsp},
{CmdType.DailyFirstMeetPamCsReq, CmdType.DailyFirstMeetPamScRsp},
{CmdType.GetMuseumInfoCsReq, CmdType.GetMuseumInfoScRsp},
{CmdType.GetLoginActivityCsReq, CmdType.GetLoginActivityScRsp},
{CmdType.GetRaidInfoCsReq, CmdType.GetRaidInfoScRsp},
{CmdType.GetTrialActivityDataCsReq, CmdType.GetTrialActivityDataScRsp},
{CmdType.GetBoxingClubInfoCsReq, CmdType.GetBoxingClubInfoScRsp},
{CmdType.GetNpcStatusCsReq, CmdType.GetNpcStatusScRsp},
{CmdType.TextJoinQueryCsReq, CmdType.TextJoinQueryScRsp},
{CmdType.GetSpringRecoverDataCsReq, CmdType.GetSpringRecoverDataScRsp},
{CmdType.GetChatFriendHistoryCsReq, CmdType.GetChatFriendHistoryScRsp},
{CmdType.GetSecretKeyInfoCsReq, CmdType.GetSecretKeyInfoScRsp},
{CmdType.GetVideoVersionKeyCsReq, CmdType.GetVideoVersionKeyScRsp},
{CmdType.GetCurLineupDataCsReq, CmdType.GetCurLineupDataScRsp},
{CmdType.GetCurBattleInfoCsReq, CmdType.GetCurBattleInfoScRsp},
{CmdType.GetCurSceneInfoCsReq, CmdType.GetCurSceneInfoScRsp},
{CmdType.GetPhoneDataCsReq, CmdType.GetPhoneDataScRsp},
{CmdType.PlayerLoginFinishCsReq, CmdType.PlayerLoginFinishScRsp}
};
[ProtoContract]
private class DummyPacket { }
}
}

View file

@ -0,0 +1,32 @@
namespace FreeSR.Gateserver.Network
{
using DotNetty.Buffers;
using DotNetty.Transport.Channels;
using FreeSR.Gateserver.Network.Packet;
using FreeSR.Proto;
internal class NetSession
{
private IChannel _channel;
public NetSession(IChannel channel)
{
_channel = channel;
}
public async void Send<T>(CmdType cmdId, T data) where T : class
{
var packet = new NetPacket()
{
CmdId = (int)cmdId,
Data = data
};
var buffer = Unpooled.Buffer();
packet.Serialize<T>(buffer);
packet.Buf = buffer;
await _channel.WriteAndFlushAsync(packet);
}
}
}

View file

@ -0,0 +1,65 @@
namespace FreeSR.Gateserver.Network
{
using DotNetty.Transport.Bootstrapping;
using DotNetty.Transport.Channels;
using DotNetty.Transport.Channels.Sockets;
using FreeSR.Gateserver.Manager;
using FreeSR.Gateserver.Manager.Handlers;
using FreeSR.Gateserver.Network.Handlers.Decoder;
using FreeSR.Gateserver.Network.Handlers.Encoder;
using FreeSR.Gateserver.Network.Handlers.Manager;
using FreeSR.Shared;
using FreeSR.Shared.Configuration;
using System.Net;
internal sealed class NetworkManager : Singleton<NetworkManager>
{
private ServerBootstrap _bootstrap;
private IChannel _serverChannel;
public async Task Initialize(NetworkConfiguration config)
{
// Notify handlers
{
NotifyManager.AddReqGroupHandler(typeof(PlayerReqGroup));
NotifyManager.AddReqGroupHandler(typeof(MailReqGroup));
NotifyManager.AddReqGroupHandler(typeof(TutorialReqGroup));
NotifyManager.AddReqGroupHandler(typeof(ItemReqGroup));
NotifyManager.AddReqGroupHandler(typeof(AvatarReqGroup));
NotifyManager.AddReqGroupHandler(typeof(LineupReqGroup));
NotifyManager.AddReqGroupHandler(typeof(MissionReqGroup));
NotifyManager.AddReqGroupHandler(typeof(QuestReqGroup));
NotifyManager.AddReqGroupHandler(typeof(ChallengeReqGroup));
NotifyManager.AddReqGroupHandler(typeof(SceneReqGroup));
NotifyManager.AddReqGroupHandler(typeof(GachaReqGroup));
NotifyManager.AddReqGroupHandler(typeof(NPCReqGroup));
NotifyManager.Init();
}
var bossGroup = new MultithreadEventLoopGroup();
var workerGroup = new MultithreadEventLoopGroup();
_bootstrap = new ServerBootstrap()
.Group(bossGroup, workerGroup)
.Channel<TcpServerSocketChannel>()
.Option(ChannelOption.SoBacklog, 120)
.Option(ChannelOption.TcpNodelay, true)
.Option(ChannelOption.SoKeepalive, true)
.ChildHandler(
new ActionChannelInitializer<IChannel>(channel =>
{
var session = new NetSession(channel);
var pipeline = channel.Pipeline;
pipeline.AddFirst(new StarRailHeaderDecoder());
pipeline.AddLast(new PacketDecoder());
pipeline.AddLast(new PacketEncoder());
pipeline.AddLast(new PacketHandler(session));
})
);
_serverChannel = await _bootstrap.BindAsync(IPAddress.Parse(config.Host), config.Port);
}
}
}

View file

@ -0,0 +1,77 @@
namespace FreeSR.Gateserver.Network.Packet
{
using DotNetty.Buffers;
using FreeSR.Gateserver.Network.Factory;
using ProtoBuf;
internal class NetPacket
{
private const uint HeadMagicConst = 0x9d74c714;
private const uint TailMagicConst = 0xd7a152c8;
public int CmdId { get; set; }
public int HeadLen { get; set; }
public uint HeadMagic { get; set; }
public int PacketLen { get; set; }
public byte[] RawData { get; set; }
public uint TailMagic { get; set; }
public object Data { get; set; }
public IByteBuffer Buf { get; set; }
public NetPacket()
{
// NetPacket.
}
public DeserializationResult Deserialize(IByteBuffer buf)
{
HeadMagic = buf.ReadUnsignedInt();
if (HeadMagic != HeadMagicConst)
return DeserializationResult.MAGIC_MISMATCH;
CmdId = buf.ReadShort();
HeadLen = buf.ReadShort();
PacketLen = buf.ReadInt();
if (buf.ReadableBytes < HeadLen + PacketLen + 4)
return DeserializationResult.INVALID_LENGTH;
RawData = new byte[PacketLen];
_ = buf.ReadBytes(HeadLen);
buf.ReadBytes(RawData);
TailMagic = buf.ReadUnsignedInt();
if (TailMagic != TailMagicConst)
return DeserializationResult.MAGIC_MISMATCH;
Data = ProtoFactory.Deserialize(CmdId, RawData);
return DeserializationResult.SUCC;
}
public void Serialize<T>(IByteBuffer buf) where T : class
{
var stream = new MemoryStream();
Serializer.Serialize(stream, Data as T);
RawData = stream.ToArray();
PacketLen = RawData.Length;
buf.WriteUnsignedShort((ushort)(HeadMagicConst >> 16));
buf.WriteUnsignedShort(0xc714);
buf.WriteShort(CmdId);
buf.WriteShort(HeadLen);
buf.WriteInt(PacketLen);
buf.WriteBytes(RawData);
buf.WriteUnsignedShort((ushort)(TailMagicConst >> 16));
buf.WriteUnsignedShort(0x52c8);
}
}
internal enum DeserializationResult
{
SUCC = 1,
INVALID_LENGTH,
MAGIC_MISMATCH
}
}

View file

@ -0,0 +1,12 @@
namespace FreeSR.Proto
{
using ProtoBuf;
[ProtoContract]
public class AACCGBNDAIO
{
[ProtoMember(5)] public int Bepmagjiopb;
[ProtoMember(8)] public AvatarType AvatarType;
}
}

View file

@ -0,0 +1,17 @@
namespace FreeSR.Proto
{
using ProtoBuf;
[ProtoContract]
public class AAEALIKMCLG
{
[ProtoMember(4)] public int Damage;
[ProtoMember(6)] public int Nbeecnlkomn;
[ProtoMember(10)] public int Bepmagjiopb;
[ProtoMember(2)] public int StageId;
[ProtoMember(12)] public string Nickname;
[ProtoMember(8)] public long Time;
[ProtoMember(7)] public int Uid;
}
}

View file

@ -0,0 +1,12 @@
namespace FreeSR.Proto
{
using ProtoBuf;
[ProtoContract]
public class ABCCDLAAOOO
{
[ProtoMember(15)] public int Hmekpnciefb;
[ProtoMember(2)] public int Cdgdnnefneb;
}
}

View file

@ -0,0 +1,11 @@
namespace FreeSR.Proto
{
using ProtoBuf;
[ProtoContract]
public class ABJKDBCKFCC
{
[ProtoMember(6)] public int Retcode;
}
}

View file

@ -0,0 +1,10 @@
namespace FreeSR.Proto
{
using ProtoBuf;
[ProtoContract]
public class ABMGNABMJGH
{
}
}

View file

@ -0,0 +1,14 @@
namespace FreeSR.Proto
{
using ProtoBuf;
[ProtoContract]
public class ACCJMILCGPF
{
[ProtoMember(9)] public int Retcode;
[ProtoMember(13)] public ItemList Reward;
[ProtoMember(10)] public int TakeDays;
[ProtoMember(4)] public int Id;
}
}

View file

@ -0,0 +1,12 @@
namespace FreeSR.Proto
{
using ProtoBuf;
[ProtoContract]
public class ACHPPEPELLJ
{
[ProtoMember(6)] public JOGGEDDHDHG Ngkebflomii;
[ProtoMember(13)] public int Retcode;
}
}

View file

@ -0,0 +1,13 @@
namespace FreeSR.Proto
{
using ProtoBuf;
[ProtoContract]
public class ACIEPALMNIK
{
[ProtoMember(12)] public int Hcekcjdooch;
[ProtoMember(14)] public int Retcode;
[ProtoMember(11)] public BBNEDLDINPO Lihledalfil;
}
}

View file

@ -0,0 +1,11 @@
namespace FreeSR.Proto
{
using ProtoBuf;
[ProtoContract]
public class ACJBCDBLJEG
{
[ProtoMember(2)] public int Malnbhckeni;
}
}

View file

@ -0,0 +1,15 @@
namespace FreeSR.Proto
{
using ProtoBuf;
[ProtoContract]
public class ACJFOAHHHFN
{
[ProtoMember(11)] public int Khobiklbdnl;
[ProtoMember(9)] public int Pjhbeoiiddl;
[ProtoMember(10)] public int Ipfabmcjdmn;
[ProtoMember(12)] public int Dfffenfgffn;
[ProtoMember(1)] public int Dpnklgjojpl;
}
}

View file

@ -0,0 +1,11 @@
namespace FreeSR.Proto
{
using ProtoBuf;
[ProtoContract]
public class ACKDBJMBHCJ
{
[ProtoMember(2)] public int StageId;
}
}

View file

@ -0,0 +1,15 @@
namespace FreeSR.Proto
{
using ProtoBuf;
[ProtoContract]
public class ACNAFDDOMOG
{
[ProtoMember(1)] public bool Hgjpcbddcma;
[ProtoMember(2)] public int Kfjkgomionh;
[ProtoMember(3)] public bool Okeokgaicel;
[ProtoMember(4)] public string Bamfbgadfik;
[ProtoMember(5)] public bool Peohgknpoji;
}
}

View file

@ -0,0 +1,11 @@
namespace FreeSR.Proto
{
using ProtoBuf;
[ProtoContract]
public class ACPCLKJPLEA
{
[ProtoMember(2)] public int Retcode;
}
}

View file

@ -0,0 +1,10 @@
namespace FreeSR.Proto
{
using ProtoBuf;
[ProtoContract]
public class ADBPNOOODLJ
{
}
}

View file

@ -0,0 +1,12 @@
namespace FreeSR.Proto
{
using ProtoBuf;
public enum ADJGAMMCGGM
{
SECRET_KEY_NONE = 0,
SECRET_KEY_SERVER_CHECK = 1,
SECRET_KEY_VIDEO = 2,
SECRET_KEY_BATTLE_TIME = 3,
}
}

View file

@ -0,0 +1,12 @@
namespace FreeSR.Proto
{
using ProtoBuf;
[ProtoContract]
public class ADMFMNOFEEJ
{
[ProtoMember(2)] public IEHIDDGOALL Ckajekffbdp;
[ProtoMember(14)] public DOFPJABOAAH Cjdfiiacefo;
}
}

View file

@ -0,0 +1,10 @@
namespace FreeSR.Proto
{
using ProtoBuf;
[ProtoContract]
public class ADPDFNPMFOG
{
}
}

View file

@ -0,0 +1,12 @@
namespace FreeSR.Proto
{
using ProtoBuf;
[ProtoContract]
public class AEAFLDGKLCM
{
[ProtoMember(4)] public int Opcfmgjjfia;
[ProtoMember(6)] public int Retcode;
}
}

View file

@ -0,0 +1,11 @@
namespace FreeSR.Proto
{
using ProtoBuf;
[ProtoContract]
public class AEEBKCJEIDE
{
[ProtoMember(15)] public JDOFGIPGPBC Kiiipnflhkf;
}
}

View file

@ -0,0 +1,13 @@
namespace FreeSR.Proto
{
using ProtoBuf;
[ProtoContract]
public class AEHDBIMPFKO
{
[ProtoMember(14)] public int Retcode;
[ProtoMember(9)] public BBMHAKPCLDC Agodpmobkah;
[ProtoMember(11)] public BBMHAKPCLDC Hajhcfooipp;
}
}

View file

@ -0,0 +1,13 @@
namespace FreeSR.Proto
{
using ProtoBuf;
[ProtoContract]
public class AEJGPEBIEKD
{
[ProtoMember(8)] public int Nmanfpffopk;
[ProtoMember(11)] public int Ddpdfgfcdnf;
[ProtoMember(5)] public int Kjoacmenfbd;
}
}

View file

@ -0,0 +1,11 @@
namespace FreeSR.Proto
{
using ProtoBuf;
[ProtoContract]
public class AEMFCABAKNJ
{
[ProtoMember(4)] public int Id;
}
}

View file

@ -0,0 +1,12 @@
namespace FreeSR.Proto
{
using ProtoBuf;
public enum AFCELKDNCJB
{
ROGUE_HANDBOOK_MIRACLE_STATUS_LOCK = 0,
ROGUE_HANDBOOK_MIRACLE_STATUS_UNLOCK = 1,
ROGUE_HANDBOOK_MIRACLE_STATUS_MEET = 2,
ROGUE_HANDBOOK_MIRACLE_STATUS_GET = 3,
}
}

View file

@ -0,0 +1,12 @@
namespace FreeSR.Proto
{
using ProtoBuf;
[ProtoContract]
public class AFDEBFHBBBE
{
[ProtoMember(7)] public List<int> Ddhddkakfah;
[ProtoMember(10)] public bool Ilblcgbhkhm;
}
}

View file

@ -0,0 +1,10 @@
namespace FreeSR.Proto
{
using ProtoBuf;
[ProtoContract]
public class AFHINEFMGON
{
}
}

View file

@ -0,0 +1,11 @@
namespace FreeSR.Proto
{
using ProtoBuf;
[ProtoContract]
public class AGAIEHANEIC
{
[ProtoMember(6)] public IALMMKMPNCC Fhgojeafidj;
}
}

View file

@ -0,0 +1,11 @@
namespace FreeSR.Proto
{
using ProtoBuf;
[ProtoContract]
public class AGFOJHLHEOC
{
[ProtoMember(1)] public int Ppenknblbnh;
}
}

View file

@ -0,0 +1,11 @@
namespace FreeSR.Proto
{
using ProtoBuf;
[ProtoContract]
public class AGGKOALBGBI
{
[ProtoMember(11)] public List<HLLNFIHFNDP> ItemList;
}
}

View file

@ -0,0 +1,11 @@
namespace FreeSR.Proto
{
using ProtoBuf;
[ProtoContract]
public class AGKDJLJMMJL
{
[ProtoMember(1)] public int Uid;
}
}

Some files were not shown because too many files have changed in this diff Show more