mirror of
https://github.com/EpinelPS/EpinelPS.git
synced 2025-12-13 07:24:52 +01:00
Compare commits
275 Commits
v0.1.2
...
775092b652
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
775092b652 | ||
|
|
f470e054cf | ||
|
|
29427ab515 | ||
|
|
50d4d23e00 | ||
|
|
17e843ecdb | ||
|
|
04e661b2b7 | ||
|
|
71be17a043 | ||
|
|
de1b4081b2 | ||
|
|
996b585500 | ||
|
|
793e55f9de | ||
|
|
2e4310edf1 | ||
|
|
ee15420257 | ||
|
|
bb46cf584f | ||
|
|
216bc75f83 | ||
|
|
bb603c44f3 | ||
|
|
c464107149 | ||
|
|
74a2829773 | ||
|
|
689c568180 | ||
|
|
5725de9fd5 | ||
|
|
622c6d49ba | ||
|
|
6310916920 | ||
|
|
f217be263a | ||
|
|
ba3215745c | ||
|
|
84766e1c34 | ||
|
|
0af41389ab | ||
|
|
5fd4da9d69 | ||
|
|
a143da53e6 | ||
|
|
b4920e435b | ||
|
|
6bdcd08240 | ||
|
|
04a9fe29c4 | ||
|
|
2c5d8acc93 | ||
|
|
77b111519c | ||
|
|
5b0c2c7d92 | ||
|
|
88d3061132 | ||
|
|
bb3e245205 | ||
|
|
10f2a9ebf4 | ||
|
|
86f9328eaa | ||
|
|
b6ac385149 | ||
|
|
39ad16a2dd | ||
|
|
2b4f460087 | ||
|
|
6bf008146f | ||
|
|
027f76d745 | ||
|
|
63495aa5e7 | ||
|
|
08b644463b | ||
|
|
2b0784f4db | ||
|
|
4e568390ed | ||
|
|
bd4e0d3e3c | ||
|
|
7dab32c5be | ||
|
|
cb1fb566af | ||
|
|
bb7411643c | ||
|
|
13086f6d19 | ||
|
|
1ba1a89110 | ||
|
|
3bd955eebd | ||
|
|
7955c2f4db | ||
|
|
0133f7f55b | ||
|
|
ffbb5c792c | ||
|
|
4592f57742 | ||
|
|
f904dc50cb | ||
|
|
059821deb7 | ||
|
|
03aca327b0 | ||
|
|
c7ccb92659 | ||
|
|
b7425b6fde | ||
|
|
a0b7f33c80 | ||
|
|
24a0bc1d81 | ||
|
|
ca787a384c | ||
|
|
e4969c723d | ||
|
|
b30869953f | ||
|
|
58750facc2 | ||
|
|
0ba37e5d97 | ||
|
|
9304f86603 | ||
|
|
0b936fa6e5 | ||
|
|
4265c43b4f | ||
|
|
734349a297 | ||
|
|
2c37167fad | ||
|
|
98311f73d2 | ||
|
|
6298471c09 | ||
|
|
9539c0c440 | ||
|
|
e4e8f801ce | ||
|
|
24b4d11862 | ||
|
|
cb27bd926a | ||
|
|
2c8401fea0 | ||
|
|
ba2d725dff | ||
|
|
187326cc21 | ||
|
|
4fbfa3e8ec | ||
|
|
652b7e0598 | ||
|
|
56b7ffc4bd | ||
|
|
2709e67b6c | ||
|
|
c9c334c2ff | ||
|
|
e37f08c232 | ||
|
|
fb274baebf | ||
|
|
3769b8ef58 | ||
|
|
cd15896be1 | ||
|
|
5d16214a78 | ||
|
|
0e4ca4320b | ||
|
|
3d3c32007c | ||
|
|
d67a7d64cd | ||
|
|
2922e59e31 | ||
|
|
09ad13fabd | ||
|
|
07fab687e7 | ||
|
|
fe40ffb717 | ||
|
|
0e823a3bd4 | ||
|
|
343b93db9e | ||
|
|
87b56abca6 | ||
|
|
7237e3d4a1 | ||
|
|
8f65cacdda | ||
|
|
659d99aa0b | ||
|
|
0fbfb36dcc | ||
|
|
95d5bae671 | ||
|
|
1cb1e34674 | ||
|
|
675eda043f | ||
|
|
cbd58df62f | ||
|
|
3e21137a4c | ||
|
|
7a4542b97b | ||
|
|
7be612ab9d | ||
|
|
2f37fa3a3f | ||
|
|
df9b52565e | ||
|
|
ec4ddc46b8 | ||
|
|
9d8c47972a | ||
|
|
c8dc1ef511 | ||
|
|
5ad06556d8 | ||
|
|
506a17cf54 | ||
|
|
1fb3b922ae | ||
|
|
3b5171a37c | ||
|
|
b725d461f3 | ||
|
|
97fe2f7b3c | ||
|
|
8bb270d786 | ||
|
|
e9bcd7adfb | ||
|
|
a85b3fcc83 | ||
|
|
5ff6264e21 | ||
|
|
684be1b26a | ||
|
|
f0b13ed134 | ||
|
|
8f28585083 | ||
|
|
d6f9381607 | ||
|
|
86a8d6d78f | ||
|
|
4b157363f8 | ||
|
|
2f7c12713c | ||
|
|
e2ca6f7ec3 | ||
|
|
e2f0807ab1 | ||
|
|
3fa3915c2b | ||
|
|
c8a47a39d0 | ||
|
|
faead3eba1 | ||
|
|
0e13c96c4d | ||
|
|
b7a655c169 | ||
|
|
3b05037be5 | ||
|
|
8622eb5879 | ||
|
|
d4deb4aa49 | ||
|
|
5391a2da3a | ||
|
|
1e7de0ef8b | ||
|
|
8030745337 | ||
|
|
5028d24b5c | ||
|
|
b022eb688c | ||
|
|
6c58b3e137 | ||
|
|
9877d29e79 | ||
|
|
5e54cfb3f3 | ||
|
|
94e6c6faa0 | ||
|
|
7110cb0e50 | ||
|
|
4d2fcfeaa6 | ||
|
|
1f289c8d70 | ||
|
|
de1f688831 | ||
|
|
93dc699b1e | ||
|
|
3be38c0d2a | ||
|
|
2e790e0861 | ||
|
|
2a089a035b | ||
|
|
c8649335f6 | ||
|
|
34ce89446e | ||
|
|
f1e6899fc9 | ||
|
|
3a6cb469a6 | ||
|
|
71ef1cf174 | ||
|
|
eeab17d497 | ||
|
|
8078da421c | ||
|
|
c2a50d5682 | ||
|
|
d3a0e9dcd3 | ||
|
|
1d11cee520 | ||
|
|
419a7d0a6a | ||
|
|
d64de8e995 | ||
|
|
54e4304868 | ||
|
|
a59c9a8baa | ||
|
|
38299bbfa0 | ||
|
|
bb2132de81 | ||
|
|
7170abc770 | ||
|
|
f7574aeb3a | ||
|
|
d4b276fc6a | ||
|
|
f368e36c69 | ||
|
|
6ca8f00368 | ||
|
|
c56c65dbd3 | ||
|
|
d8fdef4e5b | ||
|
|
3c2080f393 | ||
|
|
899a56eac5 | ||
|
|
1e5c124033 | ||
|
|
dd81797327 | ||
|
|
2c8050a19b | ||
|
|
6587bb8b71 | ||
|
|
d76cd5c095 | ||
|
|
d7d1d9b240 | ||
|
|
319b169bfa | ||
|
|
74ddb93c0f | ||
|
|
c1d0292fe5 | ||
|
|
9f9ec8f5f8 | ||
|
|
70867cc95f | ||
|
|
7ac711b027 | ||
|
|
3f32e569d9 | ||
|
|
b7a9d0de32 | ||
|
|
9e49fd31ae | ||
|
|
92b702116c | ||
|
|
43abfe1aa8 | ||
|
|
bb0e0315ab | ||
|
|
f6ce7d06be | ||
|
|
32d329eafa | ||
|
|
e14ede5295 | ||
|
|
745e6ca164 | ||
|
|
10e0aa6386 | ||
|
|
84c0d18255 | ||
|
|
61075aae37 | ||
|
|
ed3c6bb6a0 | ||
|
|
a68a201c13 | ||
|
|
f199ca63e0 | ||
|
|
1c733dfb5e | ||
|
|
a94878bce8 | ||
|
|
52b66947fc | ||
|
|
fa531385d1 | ||
|
|
6325cdf70a | ||
|
|
d5ae01af40 | ||
|
|
7a09c5960e | ||
|
|
d00ab6d185 | ||
|
|
7ddaef38df | ||
|
|
107e6b2eed | ||
|
|
d09b887879 | ||
|
|
01a0e70ae3 | ||
|
|
eee538f97f | ||
|
|
e2a0712889 | ||
|
|
42b17682b8 | ||
|
|
fc0e2801ed | ||
|
|
10fc4c3941 | ||
|
|
a1c910f444 | ||
|
|
f41cb1533d | ||
|
|
c1dc421e16 | ||
|
|
8a9447dc09 | ||
|
|
1c29003eaa | ||
|
|
239c4f293f | ||
|
|
0990f46266 | ||
|
|
6fb6c7b1ee | ||
|
|
467a20170f | ||
|
|
8157d5ea3c | ||
|
|
0ebc235a93 | ||
|
|
6f8497e60b | ||
|
|
a139373320 | ||
|
|
8467a5fce9 | ||
|
|
e185b7d87e | ||
|
|
35221356f8 | ||
|
|
766a8605b7 | ||
|
|
9d203958c3 | ||
|
|
78659f9c6e | ||
|
|
cce8179e8d | ||
|
|
8ba6d69ade | ||
|
|
9e331c487a | ||
|
|
e83dcdbfa8 | ||
|
|
8ea17003e6 | ||
|
|
643d3a5c7c | ||
|
|
49df23798f | ||
|
|
a8b13f8539 | ||
|
|
dea8d98db4 | ||
|
|
22d19cf38b | ||
|
|
1670142226 | ||
|
|
6ed393714f | ||
|
|
fcf9396b86 | ||
|
|
10f0641e77 | ||
|
|
9cf99754d6 | ||
|
|
7a1f361d25 | ||
|
|
db70b5068e | ||
|
|
005ad80095 | ||
|
|
15778751af | ||
|
|
645697f713 | ||
|
|
56c526cec1 | ||
|
|
702995bcfd | ||
|
|
181d1433cf |
4
.editorconfig
Normal file
4
.editorconfig
Normal file
@@ -0,0 +1,4 @@
|
||||
[*.cs]
|
||||
|
||||
# CA5397: Do not use deprecated SslProtocols values
|
||||
dotnet_diagnostic.CA5397.severity = none
|
||||
3
.gitattributes
vendored
3
.gitattributes
vendored
@@ -1,3 +1,2 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
cert.sh eol=lf
|
||||
* text=auto
|
||||
8
.github/workflows/dotnet-desktop.yml
vendored
8
.github/workflows/dotnet-desktop.yml
vendored
@@ -23,10 +23,10 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
# Install the .NET Core workload
|
||||
- name: Install .NET 8
|
||||
- name: Install .NET 9
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
dotnet-version: 9.0.x
|
||||
|
||||
# Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild
|
||||
- name: Setup MSBuild
|
||||
@@ -39,10 +39,10 @@ jobs:
|
||||
run: dotnet publish ServerSelector.Desktop
|
||||
|
||||
- name: Publish Server
|
||||
run: dotnet publish nksrv
|
||||
run: dotnet publish EpinelPS
|
||||
|
||||
- name: Copy to output
|
||||
run: echo ${{ github.workspace }} && md ${{ github.workspace }}/out/ && xcopy "${{ github.workspace }}\ServerSelector.Desktop\bin\Release\net8.0\win-x64\publish\" "${{ github.workspace }}\out\" && xcopy "${{ github.workspace }}\nksrv\bin\Release\net8.0\win-x64\publish\" "${{ github.workspace }}\out\" && copy "${{ github.workspace }}\ServerSelector.Desktop\sodium.dll" "${{ github.workspace }}\out\sodium.dll"
|
||||
run: echo ${{ github.workspace }} && md ${{ github.workspace }}/out/ && xcopy /s /e "${{ github.workspace }}\ServerSelector.Desktop\bin\Release\net9.0\win-x64\publish\" "${{ github.workspace }}\out\" && xcopy /s /e "${{ github.workspace }}\EpinelPS\bin\Release\net9.0\win-x64\publish\" "${{ github.workspace }}\out\" && copy "${{ github.workspace }}\ServerSelector.Desktop\sodium.dll" "${{ github.workspace }}\out\sodium.dll"
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -454,6 +454,8 @@ $RECYCLE.BIN/
|
||||
!.vscode/extensions.json
|
||||
|
||||
# Allow pregenerated ssl cert
|
||||
!nksrv/site.pfx
|
||||
!EpinelPS/site.pfx
|
||||
!ServerSelector/myCA.pfx
|
||||
!ServerSelector/sodium.dll
|
||||
!ServerSelector/sodium.dll
|
||||
|
||||
EpinelPS/Protos/allmsgs.proto
|
||||
29
.vscode/launch.json
vendored
Normal file
29
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": ".NET Core Launch (web)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "build",
|
||||
"program": "${workspaceFolder}/EpinelPS/bin/Debug/net9.0/linux-x64/EpinelPS.dll",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}/EpinelPS",
|
||||
"stopAtEntry": false,
|
||||
"env": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"sourceFileMap": {
|
||||
"/Views": "${workspaceFolder}/Views"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": ".NET Core Attach",
|
||||
"type": "coreclr",
|
||||
"request": "attach"
|
||||
}
|
||||
]
|
||||
}
|
||||
41
.vscode/tasks.json
vendored
Normal file
41
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "build",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"build",
|
||||
"${workspaceFolder}/EpinelPS/EpinelPS.csproj",
|
||||
"/property:GenerateFullPaths=true",
|
||||
"/consoleloggerparameters:NoSummary;ForceNoAlign"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "publish",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"publish",
|
||||
"${workspaceFolder}/EpinelPS/EpinelPS.csproj",
|
||||
"/property:GenerateFullPaths=true",
|
||||
"/consoleloggerparameters:NoSummary;ForceNoAlign"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "watch",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"watch",
|
||||
"run",
|
||||
"--project",
|
||||
"${workspaceFolder}/EpinelPS/EpinelPS.csproj"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,57 +0,0 @@
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
|
||||
namespace DataFixupUtil
|
||||
{
|
||||
internal class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
Console.WriteLine("Hello, World!");
|
||||
|
||||
foreach(var arg in Directory.GetFiles("C:\\NIKKE\\NIKKE\\Game"))
|
||||
{
|
||||
var fileName = Path.GetFileName(arg);
|
||||
if (fileName.StartsWith("input"))
|
||||
{
|
||||
byte[] FileContents = File.ReadAllBytes(arg);
|
||||
using MemoryStream ms = new MemoryStream(FileContents);
|
||||
File.WriteAllBytes("fullPkt-decr", ms.ToArray());
|
||||
|
||||
var unkVal1 = ms.ReadByte();
|
||||
var pktLen = ms.ReadByte() & 0x1f;
|
||||
|
||||
|
||||
var seqNumB = ms.ReadByte();
|
||||
var seqNum = seqNumB;
|
||||
if (seqNumB >= 24)
|
||||
{
|
||||
var b = ms.ReadByte();
|
||||
|
||||
seqNum = BitConverter.ToUInt16(new byte[] { (byte)b, (byte)seqNumB }, 0);
|
||||
|
||||
// todo support uint32
|
||||
}
|
||||
|
||||
var startPos = (int)ms.Position;
|
||||
|
||||
var contents = FileContents.Skip(startPos).ToArray();
|
||||
if (contents.Length > 2 && contents[0] == 0x1f && contents[1] == 0x8b)
|
||||
{
|
||||
// gzip compression is used
|
||||
using Stream csStream = new GZipStream(new MemoryStream(contents), CompressionMode.Decompress);
|
||||
using MemoryStream decoded = new MemoryStream();
|
||||
csStream.CopyTo(decoded);
|
||||
|
||||
contents = decoded.ToArray();
|
||||
File.WriteAllBytes(arg, contents);
|
||||
}
|
||||
else
|
||||
{
|
||||
File.WriteAllBytes(arg, contents);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
24
EpinelPS.Analyzers/EpinelPS.Analyzers.csproj
Normal file
24
EpinelPS.Analyzers/EpinelPS.Analyzers.csproj
Normal file
@@ -0,0 +1,24 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<IsRoslynComponent>true</IsRoslynComponent>
|
||||
<IncludeBuildOutput>false</IncludeBuildOutput>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<DebugType>none</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
108
EpinelPS.Analyzers/LoadRecordInitializerGenerator.cs
Normal file
108
EpinelPS.Analyzers/LoadRecordInitializerGenerator.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using System.Collections.Immutable;
|
||||
using System.Text;
|
||||
|
||||
namespace EpinelPS.Analyzers;
|
||||
|
||||
[Generator]
|
||||
public class LoadRecordInitializerGenerator : IIncrementalGenerator
|
||||
{
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
// Step 1: Filter for field declarations with attributes
|
||||
IncrementalValueProvider<ImmutableArray<LoadFieldInfo?>> fieldDeclarations = context.SyntaxProvider
|
||||
.CreateSyntaxProvider(
|
||||
predicate: static (node, _) => node is FieldDeclarationSyntax fds && fds.AttributeLists.Count > 0,
|
||||
transform: static (ctx, _) => GetTargetFieldInfo(ctx)
|
||||
)
|
||||
.Where(static m => m is not null)
|
||||
.Collect();
|
||||
|
||||
|
||||
// Step 2: Generate the code
|
||||
context.RegisterSourceOutput(fieldDeclarations, (spc, fieldInfos) =>
|
||||
{
|
||||
string source = GenerateInitializerCode(fieldInfos!);
|
||||
spc.AddSource("GameDataInitializer.g.cs", SourceText.From(source, Encoding.UTF8));
|
||||
});
|
||||
}
|
||||
|
||||
private static LoadFieldInfo? GetTargetFieldInfo(GeneratorSyntaxContext context)
|
||||
{
|
||||
if (context.Node is not FieldDeclarationSyntax fieldDecl)
|
||||
return null;
|
||||
|
||||
VariableDeclaratorSyntax? variable = fieldDecl.Declaration.Variables.FirstOrDefault();
|
||||
if (variable == null)
|
||||
return null;
|
||||
|
||||
if (context.SemanticModel.GetDeclaredSymbol(variable) is not IFieldSymbol symbol)
|
||||
return null;
|
||||
|
||||
if (symbol.Type is not INamedTypeSymbol namedSymbol)
|
||||
return null;
|
||||
|
||||
foreach (AttributeData attr in symbol.GetAttributes())
|
||||
{
|
||||
|
||||
if (attr.ConstructorArguments.Length == 2)
|
||||
{
|
||||
if (attr.ConstructorArguments[0].Value is not string fileName || attr.ConstructorArguments[1].Value is not string key)
|
||||
return null;
|
||||
|
||||
return new LoadFieldInfo
|
||||
{
|
||||
ContainingClass = symbol.ContainingType.ToDisplayString(),
|
||||
FieldName = symbol.Name,
|
||||
FileName = fileName,
|
||||
Key = key,
|
||||
RecordTypeName = namedSymbol.TypeArguments[1].Name
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string GenerateInitializerCode(ImmutableArray<LoadFieldInfo> fieldInfos)
|
||||
{
|
||||
StringBuilder sb = new();
|
||||
sb.AppendLine("using System.Collections.Generic;");
|
||||
sb.AppendLine("using System.Threading.Tasks;");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("namespace EpinelPS.Data;");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("public static class GameDataInitializer");
|
||||
sb.AppendLine("{");
|
||||
sb.AppendLine($"\tpublic static int TotalFiles = {fieldInfos.Length};");
|
||||
sb.AppendLine("\tpublic static async Task InitializeGameData(IProgress<double> progress = null)");
|
||||
sb.AppendLine("\t{");
|
||||
|
||||
foreach (LoadFieldInfo info in fieldInfos)
|
||||
{
|
||||
string tempVar = $"data_{info.FieldName}";
|
||||
sb.AppendLine($"\t\tvar {tempVar} = await {info.ContainingClass}.Instance.LoadZip<{info.RecordTypeName}>(\"{info.FileName}\", progress);");
|
||||
sb.AppendLine($"\t\tforeach (var obj in {tempVar})");
|
||||
sb.AppendLine("\t\t{");
|
||||
sb.AppendLine($"\t\t\t{info.ContainingClass}.Instance.{info.FieldName}.Add(obj.{info.Key}, obj);");
|
||||
sb.AppendLine("\t\t}");
|
||||
}
|
||||
|
||||
sb.AppendLine("\t}");
|
||||
sb.AppendLine("}");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private class LoadFieldInfo
|
||||
{
|
||||
public string ContainingClass = "";
|
||||
public string FieldName = "";
|
||||
public string FileName = "";
|
||||
public string Key = "";
|
||||
public string RecordTypeName = "";
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.10.34928.147
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "nksrv", "nksrv\nksrv.csproj", "{5C24A07E-9B8D-4625-BF91-0E9CCBEF5CB0}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DataFixupUtil", "DataFixupUtil\DataFixupUtil.csproj", "{13124DFB-448B-4F4F-A479-537EE067D836}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProtobufViewUtil", "ProtobufViewUtil\ProtobufViewUtil.csproj", "{FDEDD0D6-9C02-4E58-8110-04E8D5639881}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EpinelPS", "EpinelPS\EpinelPS.csproj", "{5C24A07E-9B8D-4625-BF91-0E9CCBEF5CB0}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServerSelector", "ServerSelector\ServerSelector.csproj", "{EC613C24-8A35-42E8-92C1-9A8431F74F58}"
|
||||
EndProject
|
||||
@@ -15,6 +11,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServerSelector.Desktop", "S
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Client", "Client", "{4BB2E77F-84A6-4644-9FB3-38E2A7DCDEC0}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EpinelPS.Analyzers", "EpinelPS.Analyzers\EpinelPS.Analyzers.csproj", "{E3B18A3D-B20B-447D-BCBE-12931E18B41E}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.editorconfig = .editorconfig
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -55,54 +58,6 @@ Global
|
||||
{5C24A07E-9B8D-4625-BF91-0E9CCBEF5CB0}.ReleaseDLL|x64.Build.0 = Release|Any CPU
|
||||
{5C24A07E-9B8D-4625-BF91-0E9CCBEF5CB0}.ReleaseDLL|x86.ActiveCfg = Release|Any CPU
|
||||
{5C24A07E-9B8D-4625-BF91-0E9CCBEF5CB0}.ReleaseDLL|x86.Build.0 = Release|Any CPU
|
||||
{13124DFB-448B-4F4F-A479-537EE067D836}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{13124DFB-448B-4F4F-A479-537EE067D836}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{13124DFB-448B-4F4F-A479-537EE067D836}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{13124DFB-448B-4F4F-A479-537EE067D836}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{13124DFB-448B-4F4F-A479-537EE067D836}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{13124DFB-448B-4F4F-A479-537EE067D836}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{13124DFB-448B-4F4F-A479-537EE067D836}.DebugDLL|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{13124DFB-448B-4F4F-A479-537EE067D836}.DebugDLL|Any CPU.Build.0 = Debug|Any CPU
|
||||
{13124DFB-448B-4F4F-A479-537EE067D836}.DebugDLL|x64.ActiveCfg = Debug|Any CPU
|
||||
{13124DFB-448B-4F4F-A479-537EE067D836}.DebugDLL|x64.Build.0 = Debug|Any CPU
|
||||
{13124DFB-448B-4F4F-A479-537EE067D836}.DebugDLL|x86.ActiveCfg = Debug|Any CPU
|
||||
{13124DFB-448B-4F4F-A479-537EE067D836}.DebugDLL|x86.Build.0 = Debug|Any CPU
|
||||
{13124DFB-448B-4F4F-A479-537EE067D836}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{13124DFB-448B-4F4F-A479-537EE067D836}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{13124DFB-448B-4F4F-A479-537EE067D836}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{13124DFB-448B-4F4F-A479-537EE067D836}.Release|x64.Build.0 = Release|Any CPU
|
||||
{13124DFB-448B-4F4F-A479-537EE067D836}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{13124DFB-448B-4F4F-A479-537EE067D836}.Release|x86.Build.0 = Release|Any CPU
|
||||
{13124DFB-448B-4F4F-A479-537EE067D836}.ReleaseDLL|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{13124DFB-448B-4F4F-A479-537EE067D836}.ReleaseDLL|Any CPU.Build.0 = Release|Any CPU
|
||||
{13124DFB-448B-4F4F-A479-537EE067D836}.ReleaseDLL|x64.ActiveCfg = Release|Any CPU
|
||||
{13124DFB-448B-4F4F-A479-537EE067D836}.ReleaseDLL|x64.Build.0 = Release|Any CPU
|
||||
{13124DFB-448B-4F4F-A479-537EE067D836}.ReleaseDLL|x86.ActiveCfg = Release|Any CPU
|
||||
{13124DFB-448B-4F4F-A479-537EE067D836}.ReleaseDLL|x86.Build.0 = Release|Any CPU
|
||||
{FDEDD0D6-9C02-4E58-8110-04E8D5639881}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FDEDD0D6-9C02-4E58-8110-04E8D5639881}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FDEDD0D6-9C02-4E58-8110-04E8D5639881}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{FDEDD0D6-9C02-4E58-8110-04E8D5639881}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{FDEDD0D6-9C02-4E58-8110-04E8D5639881}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{FDEDD0D6-9C02-4E58-8110-04E8D5639881}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{FDEDD0D6-9C02-4E58-8110-04E8D5639881}.DebugDLL|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FDEDD0D6-9C02-4E58-8110-04E8D5639881}.DebugDLL|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FDEDD0D6-9C02-4E58-8110-04E8D5639881}.DebugDLL|x64.ActiveCfg = Debug|Any CPU
|
||||
{FDEDD0D6-9C02-4E58-8110-04E8D5639881}.DebugDLL|x64.Build.0 = Debug|Any CPU
|
||||
{FDEDD0D6-9C02-4E58-8110-04E8D5639881}.DebugDLL|x86.ActiveCfg = Debug|Any CPU
|
||||
{FDEDD0D6-9C02-4E58-8110-04E8D5639881}.DebugDLL|x86.Build.0 = Debug|Any CPU
|
||||
{FDEDD0D6-9C02-4E58-8110-04E8D5639881}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{FDEDD0D6-9C02-4E58-8110-04E8D5639881}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{FDEDD0D6-9C02-4E58-8110-04E8D5639881}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{FDEDD0D6-9C02-4E58-8110-04E8D5639881}.Release|x64.Build.0 = Release|Any CPU
|
||||
{FDEDD0D6-9C02-4E58-8110-04E8D5639881}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{FDEDD0D6-9C02-4E58-8110-04E8D5639881}.Release|x86.Build.0 = Release|Any CPU
|
||||
{FDEDD0D6-9C02-4E58-8110-04E8D5639881}.ReleaseDLL|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{FDEDD0D6-9C02-4E58-8110-04E8D5639881}.ReleaseDLL|Any CPU.Build.0 = Release|Any CPU
|
||||
{FDEDD0D6-9C02-4E58-8110-04E8D5639881}.ReleaseDLL|x64.ActiveCfg = Release|Any CPU
|
||||
{FDEDD0D6-9C02-4E58-8110-04E8D5639881}.ReleaseDLL|x64.Build.0 = Release|Any CPU
|
||||
{FDEDD0D6-9C02-4E58-8110-04E8D5639881}.ReleaseDLL|x86.ActiveCfg = Release|Any CPU
|
||||
{FDEDD0D6-9C02-4E58-8110-04E8D5639881}.ReleaseDLL|x86.Build.0 = Release|Any CPU
|
||||
{EC613C24-8A35-42E8-92C1-9A8431F74F58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{EC613C24-8A35-42E8-92C1-9A8431F74F58}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{EC613C24-8A35-42E8-92C1-9A8431F74F58}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
@@ -151,6 +106,30 @@ Global
|
||||
{01D0A73A-A881-439D-9318-54DB5B00D6F5}.ReleaseDLL|x64.Build.0 = Release|Any CPU
|
||||
{01D0A73A-A881-439D-9318-54DB5B00D6F5}.ReleaseDLL|x86.ActiveCfg = Release|Any CPU
|
||||
{01D0A73A-A881-439D-9318-54DB5B00D6F5}.ReleaseDLL|x86.Build.0 = Release|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.DebugDLL|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.DebugDLL|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.DebugDLL|x64.ActiveCfg = Debug|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.DebugDLL|x64.Build.0 = Debug|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.DebugDLL|x86.ActiveCfg = Debug|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.DebugDLL|x86.Build.0 = Debug|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.Release|x64.Build.0 = Release|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.Release|x86.Build.0 = Release|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.ReleaseDLL|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.ReleaseDLL|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.ReleaseDLL|x64.ActiveCfg = Debug|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.ReleaseDLL|x64.Build.0 = Debug|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.ReleaseDLL|x86.ActiveCfg = Debug|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.ReleaseDLL|x86.Build.0 = Debug|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
123
EpinelPS/Controllers/AccountController.cs
Normal file
123
EpinelPS/Controllers/AccountController.cs
Normal file
@@ -0,0 +1,123 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace EpinelPS.Controllers
|
||||
{
|
||||
[Route("account")]
|
||||
[ApiController]
|
||||
public class AccountController : ControllerBase
|
||||
{
|
||||
private const string BadAuthToken = "{\"msg\":\"the account does not exists!\",\"ret\":2001,\"seq\":\"123" + "\"}";
|
||||
|
||||
[HttpPost]
|
||||
[Route("login")]
|
||||
public string Login(string seq, [FromBody] LoginEndpoint2Req req)
|
||||
{
|
||||
foreach (User item in JsonDb.Instance.Users)
|
||||
{
|
||||
if (item.Username == req.account && item.Password == req.password)
|
||||
{
|
||||
AccessToken tok = CreateLauncherTokenForUser(item);
|
||||
item.LastLogin = DateTime.UtcNow;
|
||||
JsonDb.Save();
|
||||
|
||||
return "{\"expire\":" + tok.ExpirationTime + ",\"is_login\":true,\"msg\":\"Success\",\"register_time\":" + item.RegisterTime + ",\"ret\":0,\"seq\":\"" + seq + "\",\"token\":\"" + tok.Token + "\",\"uid\":\"" + item.ID + "\"}";
|
||||
}
|
||||
}
|
||||
|
||||
return "{\"msg\":\"the account does not exists!\",\"ret\":2001,\"seq\":\"" + seq + "\"}";
|
||||
}
|
||||
|
||||
|
||||
[HttpPost]
|
||||
[Route("sendcode")]
|
||||
public string SendCode(string seq, [FromBody] SendCodeRequest req)
|
||||
{
|
||||
// Pretend that we send a code.
|
||||
return "{\"expire_time\":898,\"msg\":\"Success\",\"ret\":0,\"seq\":\"" + seq + "\"}";
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("codestatus")]
|
||||
public string CodeStatus(string seq, [FromBody] SendCodeRequest req)
|
||||
{
|
||||
// Pretend that code is valid
|
||||
return "{\"expire_time\":759,\"msg\":\"Success\",\"ret\":0,\"seq\":\"" + seq + "\"}";
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("getuserinfo")]
|
||||
public string GetUserInfo(string seq, [FromBody] AuthPkt2 req)
|
||||
{
|
||||
(User?, AccessToken?) res;
|
||||
if ((res = NetUtils.GetUser(req.token)).Item1 == null) return BadAuthToken;
|
||||
User user = res.Item1;
|
||||
AccessToken? tok = res.Item2;
|
||||
|
||||
if (tok == null)
|
||||
{
|
||||
// TODO: better error handling
|
||||
return "{}";
|
||||
}
|
||||
|
||||
// Pretend that code is valid
|
||||
return "{\"account_type\":1,\"birthday\":\"1970-01\",\"email\":\"" + user.Username + "\",\"expire\":" + tok.ExpirationTime + ",\"is_receive_email\":1,\"is_receive_email_in_night\":0,\"is_receive_video\":-1,\"lang_type\":\"en\",\"msg\":\"Success\",\"nick_name\":\"\",\"phone\":\"\",\"phone_area_code\":\"\",\"privacy_policy\":\"1\",\"privacy_update_time\":1717783097,\"region\":\"724\",\"ret\":0,\"seq\":\"" + seq + "\",\"terms_of_service\":\"\",\"terms_update_time\":0,\"uid\":\"" + user.ID + "\",\"user_agreed_dt\":\"\",\"user_agreed_pp\":\"1\",\"user_agreed_tos\":\"\",\"user_name\":\"" + user.PlayerName + "\",\"username_pass_verify\":0}";
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("register")]
|
||||
public string RegisterAccount(string seq, [FromBody] RegisterEPReq req)
|
||||
{
|
||||
// check if the account already exists
|
||||
foreach (User item in JsonDb.Instance.Users)
|
||||
{
|
||||
if (item.Username == req.account)
|
||||
{
|
||||
return "{\"msg\":\"send code failed; invalid account\",\"ret\":2112,\"seq\":\"" + seq + "\"}";
|
||||
}
|
||||
}
|
||||
|
||||
ulong uid = (ulong)new Random().Next(1, int.MaxValue);
|
||||
|
||||
// Check if we havent generated a UID that exists
|
||||
foreach (User item in JsonDb.Instance.Users)
|
||||
{
|
||||
if (item.ID == uid)
|
||||
{
|
||||
uid -= (ulong)new Random().Next(1, 1221);
|
||||
}
|
||||
}
|
||||
|
||||
User user = new()
|
||||
{
|
||||
ID = uid,
|
||||
Password = req.password,
|
||||
RegisterTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
|
||||
Username = req.account,
|
||||
PlayerName = "Player_" + Rng.RandomString(8),
|
||||
IsAdmin = JsonDb.Instance.Users.Count == 0
|
||||
};
|
||||
|
||||
JsonDb.Instance.Users.Add(user);
|
||||
|
||||
AccessToken tok = CreateLauncherTokenForUser(user);
|
||||
|
||||
return "{\"expire\":" + tok.ExpirationTime + ",\"is_login\":false,\"msg\":\"Success\",\"register_time\":" + user.RegisterTime + ",\"ret\":0,\"seq\":\"" + seq + "\",\"token\":\"" + tok.Token + "\",\"uid\":\"" + user.ID + "\"}";
|
||||
}
|
||||
public static AccessToken CreateLauncherTokenForUser(User user)
|
||||
{
|
||||
// TODO: implement access token expiration
|
||||
AccessToken token = new()
|
||||
{
|
||||
ExpirationTime = DateTimeOffset.UtcNow.AddYears(1).ToUnixTimeSeconds(),
|
||||
Token = Rng.RandomString(64),
|
||||
UserID = user.ID
|
||||
};
|
||||
JsonDb.Instance.LauncherAccessTokens.Add(token);
|
||||
JsonDb.Save();
|
||||
|
||||
return token;
|
||||
}
|
||||
}
|
||||
}
|
||||
151
EpinelPS/Controllers/AdminApiController.cs
Normal file
151
EpinelPS/Controllers/AdminApiController.cs
Normal file
@@ -0,0 +1,151 @@
|
||||
using EpinelPS.Controllers.AdminPanel;
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.LobbyServer.Stage;
|
||||
using EpinelPS.Models.Admin;
|
||||
using EpinelPS.Utils;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace EpinelPS.Controllers
|
||||
{
|
||||
[Route("adminapi")]
|
||||
[ApiController]
|
||||
public class AdminApiController : ControllerBase
|
||||
{
|
||||
private static readonly MD5 md5 = MD5.Create();
|
||||
|
||||
[HttpPost]
|
||||
[Route("login")]
|
||||
public LoginApiResponse Login([FromBody] LoginApiBody b)
|
||||
{
|
||||
User? user = null;
|
||||
bool nullusernames = false;
|
||||
if (b.Username != null && b.Password != null)
|
||||
{
|
||||
string passwordHash = Convert.ToHexString(md5.ComputeHash(Encoding.ASCII.GetBytes(b.Password))).ToLower();
|
||||
foreach (User item in JsonDb.Instance.Users)
|
||||
{
|
||||
if (item.Username == b.Username && item.Password != null)
|
||||
{
|
||||
if (item.Password.Equals(passwordHash, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
user = item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
nullusernames = true;
|
||||
}
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
return nullusernames
|
||||
? new LoginApiResponse() { Message = "Please enter a username and password" }
|
||||
: new LoginApiResponse() { Message = "Username or password is incorrect" };
|
||||
}
|
||||
else
|
||||
{
|
||||
if (user.IsAdmin)
|
||||
{
|
||||
string tok = CreateAuthToken(user);
|
||||
HttpContext.Response.Cookies.Append("token", tok);
|
||||
return new LoginApiResponse() { OK = true, Token = tok };
|
||||
}
|
||||
else
|
||||
{
|
||||
return new LoginApiResponse() { Message = "User is not an administrator." };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("RunCmd")]
|
||||
public async Task<RunCmdResponse> RunCmd([FromBody] RunCmdRequest req)
|
||||
{
|
||||
if (!AdminController.CheckAuth(HttpContext)) return new RunCmdResponse() { error = "bad token" };
|
||||
|
||||
switch (req.cmdName)
|
||||
{
|
||||
case "reloadDb":
|
||||
JsonDb.Reload();
|
||||
return RunCmdResponse.OK;
|
||||
case "completestage":
|
||||
return AdminCommands.CompleteStage(ulong.Parse(req.p1), req.p2);
|
||||
case "addallcharacters":
|
||||
{
|
||||
User? user = JsonDb.Instance.Users.FirstOrDefault(x => x.ID == ulong.Parse(req.p1));
|
||||
if (user == null) return new RunCmdResponse() { error = "invalid user ID" };
|
||||
return AdminCommands.AddAllCharacters(user);
|
||||
}
|
||||
case "addallmaterials":
|
||||
{
|
||||
User? user = JsonDb.Instance.Users.FirstOrDefault(x => x.ID == ulong.Parse(req.p1));
|
||||
if (user == null) return new RunCmdResponse() { error = "invalid user ID" };
|
||||
return AdminCommands.AddAllMaterials(user, int.Parse(req.p2));
|
||||
}
|
||||
case "SetLevel":
|
||||
{
|
||||
User? user = JsonDb.Instance.Users.FirstOrDefault(x => x.ID == ulong.Parse(req.p1));
|
||||
if (user == null) return new RunCmdResponse() { error = "invalid user ID" };
|
||||
return AdminCommands.SetCharacterLevel(user, int.Parse(req.p2));
|
||||
}
|
||||
case "SetSkillLevel":
|
||||
{
|
||||
User? user = JsonDb.Instance.Users.FirstOrDefault(x => x.ID == ulong.Parse(req.p1));
|
||||
if (user == null) return new RunCmdResponse() { error = "invalid user ID" };
|
||||
return AdminCommands.SetSkillLevel(user, int.Parse(req.p2));
|
||||
}
|
||||
case "SetCoreLevel":
|
||||
{
|
||||
User? user = JsonDb.Instance.Users.FirstOrDefault(x => x.ID == ulong.Parse(req.p1));
|
||||
if (user == null) return new RunCmdResponse() { error = "invalid user ID" };
|
||||
return AdminCommands.SetCoreLevel(user, int.Parse(req.p2));
|
||||
}
|
||||
case "finishalltutorials":
|
||||
{
|
||||
User? user = JsonDb.Instance.Users.FirstOrDefault(x => x.ID == ulong.Parse(req.p1));
|
||||
if (user == null) return new RunCmdResponse() { error = "invalid user ID" };
|
||||
return AdminCommands.FinishAllTutorials(user);
|
||||
}
|
||||
case "AddCharacter":
|
||||
{
|
||||
User? user = JsonDb.Instance.Users.FirstOrDefault(x => x.ID == ulong.Parse(req.p1));
|
||||
if (user == null) return new RunCmdResponse() { error = "invalid user ID" };
|
||||
return AdminCommands.AddCharacter(user, int.Parse(req.p2));
|
||||
}
|
||||
case "AddItem":
|
||||
{
|
||||
User? user = JsonDb.Instance.Users.FirstOrDefault(x => x.ID == ulong.Parse(req.p1));
|
||||
if (user == null) return new RunCmdResponse() { error = "invalid user ID" };
|
||||
|
||||
string[] s = req.p2.Split("-");
|
||||
return AdminCommands.AddItem(user, int.Parse(s[0]), int.Parse(s[1]));
|
||||
}
|
||||
case "updateServer":
|
||||
{
|
||||
return await AdminCommands.UpdateResources();
|
||||
}
|
||||
}
|
||||
return new RunCmdResponse() { error = "Not implemented" };
|
||||
}
|
||||
|
||||
private static string CreateAuthToken(User user)
|
||||
{
|
||||
string tok = RandomString(128);
|
||||
// 只保留一个token
|
||||
JsonDb.Instance.AdminAuthTokens.Clear();
|
||||
JsonDb.Instance.AdminAuthTokens.Add(tok, user.ID);
|
||||
JsonDb.Save();
|
||||
return tok;
|
||||
}
|
||||
|
||||
public static string RandomString(int length)
|
||||
{
|
||||
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
return new string([.. Enumerable.Repeat(chars, length).Select(static s => s[new Random().Next(s.Length)])]);
|
||||
}
|
||||
}
|
||||
}
|
||||
94
EpinelPS/Controllers/AdminPanel/AdminController.cs
Normal file
94
EpinelPS/Controllers/AdminPanel/AdminController.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Models;
|
||||
using EpinelPS.Models.Admin;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Diagnostics;
|
||||
using System.Net.Security;
|
||||
using System.Linq;
|
||||
|
||||
namespace EpinelPS.Controllers.AdminPanel
|
||||
{
|
||||
[Route("admin")]
|
||||
public class AdminController(ILogger<AdminController> logger) : Controller
|
||||
{
|
||||
private readonly ILogger<AdminController> _logger = logger;
|
||||
|
||||
public static bool CheckAuth(HttpContext context)
|
||||
{
|
||||
string? token = context.Request.Cookies["token"] ?? context.Request.Headers.Authorization.ToString().Replace("Bearer ", "");
|
||||
|
||||
// TODO better authentication
|
||||
if (JsonDb.Instance.AdminAuthTokens.TryGetValue(token, out ulong userId))
|
||||
{
|
||||
User? user = JsonDb.Instance.Users.FirstOrDefault(x => x.ID == userId);
|
||||
if (user != null && user.IsAdmin)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[Route("dashboard")]
|
||||
public IActionResult Dashboard()
|
||||
{
|
||||
if (!CheckAuth(HttpContext)) return Redirect("/admin/");
|
||||
|
||||
return View();
|
||||
}
|
||||
[Route("Events")]
|
||||
public IActionResult Events()
|
||||
{
|
||||
if (!CheckAuth(HttpContext)) return Redirect("/admin/");
|
||||
|
||||
return View();
|
||||
}
|
||||
|
||||
[Route("Configuration")]
|
||||
public IActionResult Configuration()
|
||||
{
|
||||
if (!CheckAuth(HttpContext)) return Redirect("/admin/");
|
||||
|
||||
ServerConfiguration model = new()
|
||||
{
|
||||
LogType = JsonDb.Instance.LogLevel
|
||||
};
|
||||
|
||||
return View(model);
|
||||
}
|
||||
|
||||
[Route("Configuration"), ActionName("Configuration")]
|
||||
[HttpPost]
|
||||
public IActionResult ConfigurationSave([FromForm] ServerConfiguration cfg)
|
||||
{
|
||||
if (!CheckAuth(HttpContext)) return Redirect("/admin/");
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return View();
|
||||
|
||||
JsonDb.Instance.LogLevel = cfg.LogType;
|
||||
JsonDb.Save();
|
||||
|
||||
return View(new ServerConfiguration() { LogType = cfg.LogType });
|
||||
}
|
||||
|
||||
[Route("Mail")]
|
||||
public IActionResult Mail()
|
||||
{
|
||||
if (!CheckAuth(HttpContext)) return Redirect("/admin/");
|
||||
|
||||
return View();
|
||||
}
|
||||
[Route("Database")]
|
||||
public IActionResult Database()
|
||||
{
|
||||
if (!CheckAuth(HttpContext)) return Redirect("/admin/");
|
||||
|
||||
return View();
|
||||
}
|
||||
|
||||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||
public IActionResult Error()
|
||||
{
|
||||
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
|
||||
}
|
||||
}
|
||||
}
|
||||
132
EpinelPS/Controllers/AdminPanel/UsersController.cs
Normal file
132
EpinelPS/Controllers/AdminPanel/UsersController.cs
Normal file
@@ -0,0 +1,132 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Models.Admin;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace EpinelPS.Controllers.AdminPanel
|
||||
{
|
||||
[Route("admin/Users")]
|
||||
public class UsersController(ILogger<UsersController> logger) : Controller
|
||||
{
|
||||
private readonly ILogger<UsersController> _logger = logger;
|
||||
private static readonly MD5 sha = MD5.Create();
|
||||
|
||||
public IActionResult Index()
|
||||
{
|
||||
if (!AdminController.CheckAuth(HttpContext)) return Redirect("/admin/");
|
||||
|
||||
return View(JsonDb.Instance.Users);
|
||||
}
|
||||
|
||||
[Route("Modify/{id}")]
|
||||
public IActionResult Modify(ulong id)
|
||||
{
|
||||
if (!AdminController.CheckAuth(HttpContext)) return Redirect("/admin/");
|
||||
|
||||
User? user = JsonDb.Instance.Users.Where(x => x.ID == id).FirstOrDefault();
|
||||
if (user == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return View(
|
||||
new ModUserModel()
|
||||
{
|
||||
IsAdmin = user.IsAdmin,
|
||||
IsBanned = user.IsBanned,
|
||||
Nickname = user.Nickname ?? "Unknown nickname",
|
||||
sickpulls = user.sickpulls,
|
||||
Username = user.Username ?? "Unknown username",
|
||||
ID = user.ID
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
[Route("Modify/{id}"), ActionName("Modify")]
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public IActionResult DoModifyUser(ulong id, [FromForm] ModUserModel toSet)
|
||||
{
|
||||
if (!AdminController.CheckAuth(HttpContext)) return Redirect("/admin/");
|
||||
|
||||
if (!ModelState.IsValid) throw new Exception("model state invalid");
|
||||
|
||||
User? user = JsonDb.Instance.Users.Where(x => x.ID == id).FirstOrDefault();
|
||||
if (user == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(toSet.Username))
|
||||
throw new Exception("username cannot be empty");
|
||||
|
||||
user.Username = toSet.Username;
|
||||
user.IsAdmin = toSet.IsAdmin;
|
||||
user.sickpulls = toSet.sickpulls;
|
||||
user.IsBanned = toSet.IsBanned;
|
||||
user.Nickname = toSet.Nickname;
|
||||
JsonDb.Save();
|
||||
|
||||
return View(new ModUserModel()
|
||||
{
|
||||
IsAdmin = user.IsAdmin,
|
||||
IsBanned = user.IsBanned,
|
||||
Nickname = user.Nickname,
|
||||
sickpulls = user.sickpulls,
|
||||
Username = user.Username,
|
||||
ID = user.ID
|
||||
});
|
||||
}
|
||||
|
||||
[Route("SetPassword/{id}")]
|
||||
public IActionResult SetPassword(ulong id)
|
||||
{
|
||||
if (!AdminController.CheckAuth(HttpContext)) return Redirect("/admin/");
|
||||
|
||||
User? user = JsonDb.Instance.Users.Where(x => x.ID == id).FirstOrDefault();
|
||||
if (user == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
user.Password = ""; // do not return the password
|
||||
|
||||
return View(user);
|
||||
}
|
||||
|
||||
|
||||
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
|
||||
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
|
||||
[Route("SetPassword")]
|
||||
[HttpPost, ActionName("SetPassword")]
|
||||
[ValidateAntiForgeryToken]
|
||||
public IActionResult SetPasswordConfirm(ulong? id)
|
||||
{
|
||||
if (!AdminController.CheckAuth(HttpContext)) return Redirect("/admin/");
|
||||
|
||||
if (id == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
string? newPw = Request.Form["PasswordHash"];
|
||||
if (string.IsNullOrEmpty(newPw))
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
// TODO: use bcrypt
|
||||
|
||||
User? userToUpdate = JsonDb.Instance.Users.Where(s => s.ID == id).FirstOrDefault();
|
||||
if (userToUpdate == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
userToUpdate.Password = Convert.ToHexString(sha.ComputeHash(Encoding.ASCII.GetBytes(newPw))).ToLower();
|
||||
|
||||
return View(userToUpdate);
|
||||
}
|
||||
}
|
||||
}
|
||||
108
EpinelPS/Controllers/LauncherController.cs
Normal file
108
EpinelPS/Controllers/LauncherController.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace EpinelPS.Controllers
|
||||
{
|
||||
[Route("/api/v1")]
|
||||
[ApiController]
|
||||
public class LauncherController : Controller
|
||||
{
|
||||
[HttpPost]
|
||||
[Route("fleet.auth.game.AuthSvr/Login")]
|
||||
public string LauncherLogin()
|
||||
{
|
||||
return @"{
|
||||
""result"": {
|
||||
""error_code"": 0,
|
||||
""error_message"": ""COMM_SUCC""
|
||||
},
|
||||
""channel"": 0,
|
||||
""game_id"": ""0"",
|
||||
""openid"": """",
|
||||
""uid"": """",
|
||||
""biz_ticket"": """",
|
||||
""expire_interval"": 0,
|
||||
""refresh_interval"": 0,
|
||||
""login_key"": """",
|
||||
""login_ticket"": """",
|
||||
""third_uid"": """"
|
||||
}";
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("fleet.repo.game.RepoSVC/GetRegion")]
|
||||
public string LauncherGetRegion()
|
||||
{
|
||||
return @"{
|
||||
""result"": {
|
||||
""error_code"": 0,
|
||||
""error_message"": ""success""
|
||||
},
|
||||
""region_info"": [
|
||||
{
|
||||
""game_id"": ""16601"",
|
||||
""region_id"": ""10001"",
|
||||
""region_name_en_us"": ""Global"",
|
||||
""region_name_i18n"": """",
|
||||
""region_description_en_us"": ""Nikke Global Version"",
|
||||
""region_description_i18n"": """",
|
||||
""bind_branches"": """",
|
||||
""meta_data"": """",
|
||||
""sequence"": 0,
|
||||
""status"": 2,
|
||||
""branch_info"": [
|
||||
{
|
||||
""game_id"": ""16601"",
|
||||
""branch_id"": ""1"",
|
||||
""branch_name"": ""Official_release"",
|
||||
""branch_type"": 0,
|
||||
""description"": ""正式发布环境 release包""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}";
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("fleet.repo.game.RepoSVC/GetGameLauncher")]
|
||||
public string LauncherGetLauncher()
|
||||
{
|
||||
return @"{
|
||||
""result"": {
|
||||
""error_code"": 0,
|
||||
""error_message"": ""COMM_SUCC""
|
||||
},
|
||||
""game_launcher_info"": [
|
||||
{
|
||||
""id"": 27,
|
||||
""execute_file"": ""NIKKE\\Game\\NIKKE.exe"",
|
||||
""param"": """",
|
||||
""description"": ""Nikke main process"",
|
||||
""os"": ""any"",
|
||||
""branch_id"": 0,
|
||||
""status"": 1,
|
||||
""param_type"": 1
|
||||
}
|
||||
]
|
||||
}";
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("fleet.repo.game.RepoSVC/GetVersion")]
|
||||
public string LauncherGetVersion([FromBody] LauncherVersionRequest? body)
|
||||
{
|
||||
if (body == null)
|
||||
{
|
||||
return "{}";
|
||||
}
|
||||
|
||||
return System.IO.File.ReadAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "gameversion.json"));
|
||||
}
|
||||
|
||||
public class LauncherVersionRequest
|
||||
{
|
||||
public int game_id {get;set;}
|
||||
public int branch_id { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
196
EpinelPS/Controllers/LevelInfiniteControlller.cs
Normal file
196
EpinelPS/Controllers/LevelInfiniteControlller.cs
Normal file
@@ -0,0 +1,196 @@
|
||||
using System.Reflection;
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Org.BouncyCastle.Ocsp;
|
||||
|
||||
namespace EpinelPS.Controllers
|
||||
{
|
||||
[Route("/v2")]
|
||||
[ApiController]
|
||||
public class LevelInfiniteControlller : Controller
|
||||
{
|
||||
private const string BadAuthToken = "{\"msg\":\"the account does not exists!\",\"ret\":2001,\"seq\":\"123" + "\"}";
|
||||
|
||||
[HttpPost]
|
||||
[Route("conf/get_conf")]
|
||||
public string GetConfig(string sig)
|
||||
{
|
||||
return "{\"conf_version\":\"102\",\"msg\":\"\",\"ret\":1,\"seq\":\"" + sig + "\"}";
|
||||
}
|
||||
|
||||
|
||||
[HttpPost]
|
||||
[Route("auth/login")]
|
||||
public string AuthLogin(string seq, [FromBody] LoginEndpoint1Req req)
|
||||
{
|
||||
foreach (AccessToken tok in JsonDb.Instance.LauncherAccessTokens)
|
||||
{
|
||||
if (tok.Token == req.channel_info.account_token)
|
||||
{
|
||||
User? user = JsonDb.Instance.Users.Find(x => x.ID == tok.UserID);
|
||||
if (user != null)
|
||||
{
|
||||
// todo: they use another token here, but we will reuse the same one.
|
||||
// todo: use a class for this, this is a mess
|
||||
return "{\"birthday\":\"1970-01\",\"channel_info\":{\"account\":\"" + user.Username + "\",\"account_plat_type\":131,\"account_token\":\"" + req.channel_info.account_token + "\",\"account_type\":1,\"account_uid\":\"" + user.ID + "\",\"expire_ts\":1721667004,\"is_login\":true,\"lang_type\":\"en\",\"phone_area_code\":\"\",\"token\":\"" + req.channel_info.account_token + "\"},\"del_account_info\":\"{\\\"ret\\\":0,\\\"msg\\\":\\\"\\\",\\\"status\\\":0,\\\"created_at\\\":\\\"0\\\",\\\"target_destroy_at\\\":\\\"0\\\",\\\"destroyed_at\\\":\\\"0\\\",\\\"err_code\\\":0,\\\"seq\\\":\\\"1719075066-0339089836-025921-1161847390\\\"}\",\"del_account_status\":0,\"del_li_account_status\":0,\"email\":\"" + user.Username + "\",\"extra_json\":{\"del_li_account_info\":\"{\\\"ret\\\":0,\\\"msg\\\":\\\"\\\",\\\"status\\\":0,\\\"created_at\\\":\\\"0\\\",\\\"target_destroy_at\\\":\\\"0\\\",\\\"destroyed_at\\\":\\\"0\\\",\\\"err_code\\\":0,\\\"seq\\\":\\\"" + seq + "\\\"}\",\"get_status_rsp\":{\"adult_age\":14,\"adult_age_map\":{},\"adult_check_status\":1,\"adult_check_status_expiration\":\"0\",\"adult_status_map\":{},\"certificate_type\":3,\"email\":\"\",\"eu_user_agree_status\":0,\"game_grade\":0,\"game_grade_map\":{},\"is_dma\":true,\"is_eea\":false,\"is_need_li_cert\":false,\"msg\":\"success\",\"need_parent_control\":0,\"need_realname_auth\":0,\"parent_certificate_status\":0,\"parent_certificate_status_expiration\":\"0\",\"parent_control_map\":{},\"qr_code_ret\":0,\"realname_auth_status\":0,\"region\":\"724\",\"ret\":0,\"ts\":\"1719075065\"},\"need_notify_rsp\":{\"game_sacc_openid\":\"\",\"game_sacc_uid\":\"\",\"has_game_sacc_openid\":false,\"has_game_sacc_uid\":false,\"has_li_openid\":true,\"has_li_uid\":true,\"is_receive_email\":1,\"is_receive_email_in_night\":0,\"li_openid\":\"" + user.ID + "\",\"li_uid\":\"2752409592679849\",\"need_notify\":false,\"user_agreed_game_dma\":\"2\",\"user_agreed_game_pp\":\"1\",\"user_agreed_game_tos\":\"1\",\"user_agreed_li_dt\":\"\",\"user_agreed_li_pp\":\"1\",\"user_agreed_li_tos\":\"1\"}},\"first_login\":0,\"gender\":0,\"msg\":\"success\",\"need_name_auth\":false,\"openid\":\"" + user.ID + "\",\"pf\":\"LevelInfinite_LevelInfinite-Windows-windows-Windows-LevelInfinite-09af79d65d6e4fdf2d2569f0d365739d-" + user.ID + "\",\"pf_key\":\"abc\",\"picture_url\":\"\",\"reg_channel_dis\":\"Windows\",\"ret\":0,\"seq\":\"29080-2d28ea26-d71f-4822-9118-0156f1e2dba4-1719075060-99\",\"token\":\"" + tok.Token + "\",\"token_expire_time\":" + tok.ExpirationTime + ",\"uid\":\"" + user.ID + "\",\"user_name\":\"" + user.PlayerName + "\"}";
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: proper token expired message
|
||||
return "{\"msg\":\"the account does not exists!\",\"ret\":2001,\"seq\":\"" + seq + "\"}";
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("auth/auto_login")]
|
||||
public string AutoLogin(string seq, [FromBody] AuthPkt2 req)
|
||||
{
|
||||
User? user;
|
||||
if ((user = NetUtils.GetUser(req.token).Item1) == null) return BadAuthToken;
|
||||
|
||||
|
||||
return "{\"del_account_info\":\"{\\\"ret\\\":0,\\\"msg\\\":\\\"\\\",\\\"status\\\":0,\\\"created_at\\\":\\\"0\\\",\\\"target_destroy_at\\\":\\\"0\\\",\\\"destroyed_at\\\":\\\"0\\\",\\\"err_code\\\":0,\\\"seq\\\":\\\"" + seq + "\\\"}\",\"del_account_status\":0,\"del_li_account_status\":0,\"extra_json\":{\"del_li_account_info\":\"{\\\"ret\\\":0,\\\"msg\\\":\\\"\\\",\\\"status\\\":0,\\\"created_at\\\":\\\"0\\\",\\\"target_destroy_at\\\":\\\"0\\\",\\\"destroyed_at\\\":\\\"0\\\",\\\"err_code\\\":0,\\\"seq\\\":\\\"" + seq + "\\\"}\",\"get_status_msg\":\"success\",\"get_status_ret\":0,\"get_status_rsp\":{\"adult_age\":14,\"adult_age_map\":{},\"adult_check_status\":1,\"adult_check_status_expiration\":\"0\",\"adult_status_map\":{},\"certificate_type\":3,\"email\":\"\",\"eu_user_agree_status\":0,\"game_grade\":0,\"game_grade_map\":{},\"is_dma\":true,\"is_eea\":false,\"is_need_li_cert\":false,\"msg\":\"success\",\"need_parent_control\":0,\"need_realname_auth\":0,\"parent_certificate_status\":0,\"parent_certificate_status_expiration\":\"0\",\"parent_control_map\":{},\"qr_code_ret\":0,\"realname_auth_status\":0,\"region\":\"724\",\"ret\":0,\"ts\":\"" + DateTimeOffset.UtcNow.ToUnixTimeSeconds()
|
||||
+ "\"},\"need_notify_msg\":\"success\",\"need_notify_ret\":0,\"need_notify_rsp\":{\"has_bind_li\":true,\"is_receive_email\":1,\"is_receive_email_in_night\":0,\"user_agreed_game_dma\":\"2\",\"user_agreed_game_pp\":\"1\",\"user_agreed_game_tos\":\"1\",\"user_agreed_li_dt\":\"\",\"user_agreed_li_pp\":\"1\",\"user_agreed_li_tos\":\"1\"}},\"msg\":\"success\",\"ret\":0,\"seq\":\"" + seq + "\"}";
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("minorcer/get_status")]
|
||||
public string MinorcerStatus(string seq)
|
||||
{
|
||||
return "{\"adult_age\":15,\"adult_age_map\":{},\"adult_check_status\":1,\"adult_check_status_expiration\":\"0\",\"adult_status_map\":{},\"certificate_type\":3,\"email\":\"\",\"eu_user_agree_status\":0,\"game_grade\":0,\"game_grade_map\":{},\"is_dma\":true,\"is_eea\":false,\"is_need_li_cert\":false,\"msg\":\"success\",\"need_parent_control\":0,\"need_realname_auth\":0,\"parent_certificate_status\":0,\"parent_certificate_status_expiration\":\"0\",\"parent_control_map\":{},\"qr_code_ret\":0,\"realname_auth_status\":0,\"region\":\"300\",\"ret\":0,\"seq\":\"" + seq + "\",\"ts\":\"1719156511\"}";
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("profile/userinfo")]
|
||||
public string QueryUserInfo(string seq, [FromBody] AuthPkt2 req)
|
||||
{
|
||||
User? user;
|
||||
if ((user = NetUtils.GetUser(req.token).Item1) == null) return BadAuthToken;
|
||||
|
||||
return "{\"bind_list\":[{\"channel_info\":{\"birthday\":\"1970-01\",\"email\":\"" + user.Username + "\",\"is_receive_email\":1,\"lang_type\":\"en\",\"last_login_time\":1719075003,\"nick_name\":\"\",\"phone\":\"\",\"phone_area_code\":\"\",\"region\":\"724\",\"register_account\":\"" + user.Username + "\",\"register_account_type\":1,\"register_time\":" + user.RegisterTime + ",\"seq\":\"abc\",\"uid\":\"" + user.ID + "\",\"user_name\":\"" + user.PlayerName + "\",\"username_pass_verify\":0},\"channelid\":131,\"email\":\"" + user.Username + "\",\"picture_url\":\"\",\"user_name\":\"" + user.PlayerName + "\"}],\"birthday\":\"1970-01\",\"email\":\"" + user.Username + "\",\"gender\":0,\"msg\":\"success\",\"picture_url\":\"\",\"ret\":0,\"seq\":\"" + seq + "\",\"user_name\":\"" + user.PlayerName + "\"}";
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("profile/query_account_info")]
|
||||
public string QueryAccountInfo(string seq, [FromBody] AuthPkt req)
|
||||
{
|
||||
User? user;
|
||||
if ((user = NetUtils.GetUser(req.channel_info.token).Item1) == null) return BadAuthToken;
|
||||
|
||||
// Pretend that code is valid
|
||||
return "{\"game_sacc_openid\":\"\",\"game_sacc_uid\":\"\",\"has_game_sacc_openid\":false,\"has_game_sacc_uid\":false,\"has_li_openid\":false,\"has_li_uid\":true,\"is_receive_email\":-1,\"is_receive_email_in_night\":-1,\"li_openid\":\"\",\"li_uid\":\"" + user.ID + "\",\"msg\":\"success\",\"need_notify\":false,\"ret\":0,\"seq\":\"" + seq + "\",\"user_agreed_game_dma\":\"\",\"user_agreed_game_pp\":\"\",\"user_agreed_game_tos\":\"\",\"user_agreed_li_dt\":\"\",\"user_agreed_li_pp\":\"\",\"user_agreed_li_tos\":\"1\"}";
|
||||
}
|
||||
|
||||
|
||||
[HttpPost]
|
||||
[Route("reward/send")]
|
||||
public string SendDailyReward(string seq)
|
||||
{
|
||||
// Level infinite pass daily reward coints, not implemented as they are inaccessible currently
|
||||
return "{\"msg\":\"success\",\"ret\":0,\"seq\":\"" + seq + "\"}";
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("profile/set_protocol")]
|
||||
public string SetProtocol(string seq)
|
||||
{
|
||||
// Enable encryption, not used in this server.
|
||||
return "{\"msg\":\"success\",\"ret\":0,\"seq\":\"" + seq + "\"}";
|
||||
}
|
||||
private static IntlNotice CreateNotice(int id, NoticeType type, string contentText, string title = "", string picture = "")
|
||||
{
|
||||
IntlNotice notice = new()
|
||||
{
|
||||
app_id = "3001001",
|
||||
app_notice_id = "post-" + id,
|
||||
area_list = "[\"81\",\"82\",\"83\",\"84\",\"85\"]",
|
||||
extra_data = "{\"NoticeType\":\"" + type.ToString() + "\",\"Order\":\"11\",\"extra_reserved\":\"{\\\"Author\\\":\\\"\\\",\\\"Category\\\":\\\"\\\",\\\"CreateType\\\":\\\"4\\\",\\\"IsOpenService\\\":\\\"0\\\",\\\"IsToping\\\":true,\\\"Keyword\\\":\\\"\\\",\\\"Sort\\\":\\\"\\\",\\\"TopEnd\\\":\\\"2030-01-01 00:00:01\\\",\\\"TopStart\\\":\\\"2000-01-01 00:00:01\\\"}\"}",
|
||||
id = id,
|
||||
start_time = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
|
||||
end_time = (int)DateTimeOffset.UtcNow.AddDays(1).ToUnixTimeSeconds(),
|
||||
update_time = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
|
||||
status = 1
|
||||
};
|
||||
|
||||
ContentList content = new()
|
||||
{
|
||||
app_content_id = "post-" + id,
|
||||
content = contentText,
|
||||
extra_data = "{}",
|
||||
id = id,
|
||||
lang_type = "en",
|
||||
title = title,
|
||||
update_time = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(picture))
|
||||
{
|
||||
content.picture_list.Add(new PictureList()
|
||||
{
|
||||
extra_data = "{\"id\":\"TitleImage\"}",
|
||||
hash = "",
|
||||
redirect_url = "",
|
||||
url = picture
|
||||
});
|
||||
}
|
||||
|
||||
notice.content_list.Add(content);
|
||||
return notice;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("notice/get_notice_content")]
|
||||
public IntlNoticeListResponse GetNotices(string seq)
|
||||
{
|
||||
IntlNoticeListResponse rsp = new()
|
||||
{
|
||||
seq = seq,
|
||||
ret = 0,
|
||||
msg = "success"
|
||||
};
|
||||
|
||||
rsp.notice_list.Add(CreateNotice(2, NoticeType.System, "You are running EpinelPS v" + Assembly.GetExecutingAssembly().GetName().Version, "Server version"));
|
||||
|
||||
return rsp;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("lbs/ipregion")]
|
||||
public string GetIpRegion(string seq)
|
||||
{
|
||||
return "{\"alpha2\":\"GR\",\"extra_json\":{\"certificate_type_map\":{}},\"msg\":\"success\",\"region\":\"300\",\"ret\":0,\"seq\":\"" + seq + "\",\"timestamp\":324234322}";
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("gnconfig/acquire_config")]
|
||||
public string AcquireConfig(string seq)
|
||||
{
|
||||
return "{\"ret\":23111202,\"msg\":\"no matched config error( [match logic]no match )\",\"rule_id\":\"\",\"resource_list\":\"\",\"sdk_enable\":0,\"sdk_debug_enable\":0,\"report_log_enable\":0,\"log_level\":0,\"inner_seq\":\"" + seq + "\",\"ab_test\":{\"id\":\"\",\"group\":\"\"},\"seq\":\"" + seq + "\"}";
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("profile/get_bind_info")]
|
||||
public string GetProfileBindInfo(string seq, [FromBody] AuthPkt2 req)
|
||||
{
|
||||
User? user;
|
||||
if ((user = NetUtils.GetUser(req.token).Item1) == null) return BadAuthToken;
|
||||
|
||||
return "{\"bind_list\":[{\"bind_ts\":1717783095,\"channel_info\":{\"birthday\":\"1970-01\",\"email\":\"" + user.Username + "\",\"is_receive_email\":1,\"lang_type\":\"en\",\"last_login_time\":171000000,\"nick_name\":\"\",\"phone\":\"\",\"phone_area_code\":\"\",\"region\":\"724\",\"register_account\":\"" + user.Username + "\",\"register_account_type\":1,\"register_time\":" + user.RegisterTime + ",\"seq\":\"" + seq + "\",\"uid\":\"2752409592679849\",\"user_name\":\"" + user.PlayerName + "\",\"username_pass_verify\":0},\"channelid\":131,\"email\":\"" + user.Username + "\",\"history_scopes\":[],\"is_primary\":1,\"picture_url\":\"\",\"user_name\":\"" + user.PlayerName + "\"}],\"create_ts\":" + user.RegisterTime + ",\"last_login_ts\":171000000,\"msg\":\"success\",\"ret\":0,\"seq\":\"" + seq + "\"}";
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("auth/refresh_sacc_token")]
|
||||
public string RefreshAuthToken(string seq, [FromBody] AuthPkt2 req)
|
||||
{
|
||||
// TODO redo auth token system
|
||||
AccessToken? user;
|
||||
if ((user = NetUtils.GetUser(req.token).Item2) == null) return BadAuthToken;
|
||||
user.ExpirationTime = DateTimeOffset.UtcNow.AddYears(1).ToUnixTimeSeconds();
|
||||
|
||||
return "{\"msg\":\"success\",\"ret\":0,\"seq\":\"" + seq + "\"}";
|
||||
}
|
||||
}
|
||||
}
|
||||
24
EpinelPS/Controllers/LobbyApiController.cs
Normal file
24
EpinelPS/Controllers/LobbyApiController.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using EpinelPS.LobbyServer;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Diagnostics;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.Controllers
|
||||
{
|
||||
[Route("v1")]
|
||||
[ApiController]
|
||||
public class LobbyApiController : ControllerBase
|
||||
{
|
||||
[HttpPost]
|
||||
[Route("{**all}", Order = int.MaxValue)]
|
||||
[Consumes("application/octet-stream+protobuf")]
|
||||
public async Task CatchAll(string all)
|
||||
{
|
||||
Stopwatch st = Stopwatch.StartNew();
|
||||
await LobbyHandler.DispatchSingle(HttpContext);
|
||||
st.Stop();
|
||||
|
||||
Logging.WriteLine($"POST {HttpContext.Request.Path.Value}: {HttpContext.Response.StatusCode}", LogType.Info);
|
||||
}
|
||||
}
|
||||
}
|
||||
755
EpinelPS/Data/GameData.cs
Normal file
755
EpinelPS/Data/GameData.cs
Normal file
@@ -0,0 +1,755 @@
|
||||
using System.Data;
|
||||
using System.Diagnostics;
|
||||
using System.Security.Cryptography;
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using MemoryPack;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace EpinelPS.Data
|
||||
{
|
||||
public class GameData
|
||||
{
|
||||
private static GameData? _instance;
|
||||
public static GameData Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
_instance ??= BuildAsync().Result;
|
||||
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] Sha256Hash;
|
||||
public byte[] MpkHash = [];
|
||||
public int Size;
|
||||
public int MpkSize;
|
||||
|
||||
private ZipFile MainZip;
|
||||
private MemoryStream ZipStream;
|
||||
private int totalFiles = 1;
|
||||
private int currentFile;
|
||||
|
||||
// TODO: all of the data types need to be changed to match the game
|
||||
private bool UseMemoryPack = true;
|
||||
|
||||
public readonly Dictionary<string, FieldMapRecord> MapData = [];
|
||||
|
||||
[LoadRecord("MainQuestTable.json", "Id")]
|
||||
public readonly Dictionary<int, MainQuestRecord> QuestDataRecords = [];
|
||||
|
||||
[LoadRecord("CampaignStageTable.json", "Id")]
|
||||
public readonly Dictionary<int, CampaignStageRecord> StageDataRecords = [];
|
||||
|
||||
[LoadRecord("RewardTable.json", "Id")]
|
||||
public readonly Dictionary<int, RewardRecord> RewardDataRecords = [];
|
||||
|
||||
[LoadRecord("UserExpTable.json", "Level")]
|
||||
public readonly Dictionary<int, UserExpRecord> UserExpDataRecords = [];
|
||||
|
||||
[LoadRecord("CampaignChapterTable.json", "Chapter")]
|
||||
public readonly Dictionary<int, CampaignChapterRecord> ChapterCampaignData = [];
|
||||
|
||||
[LoadRecord("CharacterCostumeTable.json", "Id")]
|
||||
public readonly Dictionary<int, CharacterCostumeRecord> CharacterCostumeTable = [];
|
||||
|
||||
[LoadRecord("CharacterTable.json", "Id")]
|
||||
public readonly Dictionary<int, CharacterRecord> CharacterTable = [];
|
||||
|
||||
[LoadRecord("ContentsTutorialTable.json", "Id")]
|
||||
public readonly Dictionary<int, ContentsTutorialRecord> TutorialTable = [];
|
||||
[LoadRecord("ItemEquipTable.json", "Id")]
|
||||
|
||||
public readonly Dictionary<int, ItemEquipRecord> ItemEquipTable = [];
|
||||
|
||||
[LoadRecord("ItemMaterialTable.json", "Id")]
|
||||
public readonly Dictionary<int, ItemMaterialRecord> itemMaterialTable = [];
|
||||
|
||||
[LoadRecord("ItemEquipExpTable.json", "Id")]
|
||||
public readonly Dictionary<int, ItemEquipExpRecord> itemEquipExpTable = [];
|
||||
|
||||
[LoadRecord("ItemEquipGradeExpTable.json", "Id")]
|
||||
public readonly Dictionary<int, ItemEquipGradeExpRecord> ItemEquipGradeExpTable = [];
|
||||
|
||||
[LoadRecord("CharacterLevelTable.json", "Level")]
|
||||
public readonly Dictionary<int, CharacterLevelRecord> LevelData = [];
|
||||
|
||||
[LoadRecord("TacticAcademyFunctionTable.json", "Id")]
|
||||
public readonly Dictionary<int, TacticAcademyFunctionRecord> TacticAcademyLessons = [];
|
||||
|
||||
[LoadRecord("SIdeStoryStageTable.json", "Id")]
|
||||
public readonly Dictionary<int, SideStoryStageRecord> SidestoryRewardTable = [];
|
||||
|
||||
[LoadRecord("FieldItemTable.json", "Id")]
|
||||
public readonly Dictionary<int, FieldItemRecord> FieldItems = [];
|
||||
|
||||
[LoadRecord("OutpostBattleTable.json", "Id")]
|
||||
public readonly Dictionary<int, OutpostBattleRecord> OutpostBattle = [];
|
||||
|
||||
[LoadRecord("JukeboxListTable.json", "Id")]
|
||||
public readonly Dictionary<int, JukeboxListRecord> jukeboxListDataRecords = [];
|
||||
|
||||
[LoadRecord("JukeboxThemeTable.json", "Id")]
|
||||
public readonly Dictionary<int, JukeboxThemeRecord> jukeboxThemeDataRecords = [];
|
||||
|
||||
[LoadRecord("GachaTypeTable.json", "Id")]
|
||||
public readonly Dictionary<int, GachaTypeRecord> gachaTypes = [];
|
||||
|
||||
[LoadRecord("EventManagerTable.json", "Id")]
|
||||
public readonly Dictionary<int, EventManagerRecord> eventManagers = [];
|
||||
|
||||
[LoadRecord("LiveWallpaperTable.json", "Id")]
|
||||
public readonly Dictionary<int, LiveWallpaperRecord> lwptablemgrs = [];
|
||||
|
||||
[LoadRecord("AlbumResourceTable.json", "Id")]
|
||||
public readonly Dictionary<int, AlbumResourceRecord> albumResourceRecords = [];
|
||||
|
||||
[LoadRecord("UserFrameTable.json", "Id")]
|
||||
public readonly Dictionary<int, UserFrameRecord> userFrameTable = [];
|
||||
|
||||
[LoadRecord("ArchiveRecordManagerTable.json", "Id")]
|
||||
public readonly Dictionary<int, ArchiveRecordManagerRecord> archiveRecordManagerTable = [];
|
||||
|
||||
[LoadRecord("ArchiveEventStoryTable.json", "Id")]
|
||||
public readonly Dictionary<int, ArchiveEventStoryRecord> archiveEventStoryRecords = [];
|
||||
|
||||
[LoadRecord("ArchiveEventQuestTable.json", "Id")]
|
||||
public readonly Dictionary<int, ArchiveEventQuestRecord_Raw> archiveEventQuestRecords = [];
|
||||
|
||||
[LoadRecord("ArchiveEventDungeonStageTable.json", "Id")]
|
||||
public readonly Dictionary<int, ArchiveEventDungeonStageRecord> archiveEventDungeonStageRecords = [];
|
||||
|
||||
[LoadRecord("UserTitleTable.json", "Id")]
|
||||
public readonly Dictionary<int, UserTitleRecord> userTitleRecords = [];
|
||||
|
||||
[LoadRecord("ArchiveMessengerConditionTable.json", "Id")]
|
||||
public readonly Dictionary<int, ArchiveMessengerConditionRecord> archiveMessengerConditionRecords = [];
|
||||
|
||||
[LoadRecord("CharacterStatTable.json", "Id")]
|
||||
public readonly Dictionary<int, CharacterStatRecord> characterStatTable = [];
|
||||
|
||||
[LoadRecord("SkillInfoTable.json", "Id")]
|
||||
public readonly Dictionary<int, SkillInfoRecord> skillInfoTable = [];
|
||||
|
||||
[LoadRecord("CostTable.json", "Id")]
|
||||
public readonly Dictionary<int, CostRecord> costTable = [];
|
||||
|
||||
[LoadRecord("MidasProductTable.json", "MidasProductIdProximabeta")]
|
||||
public readonly Dictionary<string, MidasProductRecord> mediasProductTable = [];
|
||||
|
||||
[LoadRecord("TowerTable.json", "Id")]
|
||||
public readonly Dictionary<int, TowerRecord> towerTable = [];
|
||||
|
||||
[LoadRecord("TriggerTable.json", "Id")]
|
||||
public readonly Dictionary<int, TriggerRecord> TriggerTable = [];
|
||||
|
||||
[LoadRecord("InfraCoreGradeTable.json", "Id")]
|
||||
public readonly Dictionary<int, InfraCoreGradeRecord> InfracoreTable = [];
|
||||
|
||||
[LoadRecord("AttractiveCounselCharacterTable.json", "NameCode")]
|
||||
public readonly Dictionary<int, AttractiveCounselCharacterRecord_Raw> AttractiveCounselCharacterTable = [];
|
||||
|
||||
[LoadRecord("AttractiveLevelRewardTable.json", "Id")]
|
||||
public readonly Dictionary<int, AttractiveLevelRewardRecord> AttractiveLevelReward = [];
|
||||
|
||||
[LoadRecord("AttractiveLevelTable.json", "Id")]
|
||||
public readonly Dictionary<int, AttractiveLevelRecord> AttractiveLevelTable = [];
|
||||
|
||||
[LoadRecord("SubQuestTable.json", "Id")]
|
||||
public readonly Dictionary<int, SubQuestRecord> Subquests = [];
|
||||
|
||||
[LoadRecord("MessengerDialogTable.json", "Id")]
|
||||
public readonly Dictionary<string, MessengerDialogRecord> Messages = [];
|
||||
|
||||
[LoadRecord("MessengerConditionTriggerTable.json", "Id")]
|
||||
public readonly Dictionary<int, MessengerConditionTriggerRecord> MessageConditions = [];
|
||||
|
||||
[LoadRecord("ScenarioRewardsTable.json", "ConditionId")]
|
||||
public readonly Dictionary<string, ScenarioRewardsRecord> ScenarioRewards = [];
|
||||
|
||||
// Note: same data types are intentional
|
||||
[LoadRecord("ProductOfferTable.json", "Id")]
|
||||
public readonly Dictionary<int, ProductOfferRecord> ProductOffers = [];
|
||||
|
||||
[LoadRecord("PopupPackageListTable.json", "Id")]
|
||||
public readonly Dictionary<int, PopupPackageListRecord> PopupPackages = [];
|
||||
|
||||
[LoadRecord("InterceptNormalTable.json", "Id")]
|
||||
public readonly Dictionary<int, InterceptNormalRecord> InterceptNormal = [];
|
||||
|
||||
[LoadRecord("InterceptSpecialTable.json", "Id")]
|
||||
public readonly Dictionary<int, InterceptSpecialRecord> InterceptSpecial = [];
|
||||
|
||||
[LoadRecord("ConditionRewardTable.json", "Id")]
|
||||
public readonly Dictionary<int, ConditionRewardRecord> ConditionRewards = [];
|
||||
[LoadRecord("ItemConsumeTable.json", "Id")]
|
||||
public readonly Dictionary<int, ItemConsumeRecord> ConsumableItems = [];
|
||||
[LoadRecord("ItemRandomTable.json", "Id")]
|
||||
public readonly Dictionary<int, ItemRandomRecord> RandomItem = [];
|
||||
[LoadRecord("LostSectorTable.json", "Id")]
|
||||
public readonly Dictionary<int, LostSectorRecord> LostSector = [];
|
||||
[LoadRecord("LostSectorStageTable.json", "Id")]
|
||||
public readonly Dictionary<int, LostSectorStageRecord> LostSectorStages = [];
|
||||
[LoadRecord("ItemPieceTable.json", "Id")]
|
||||
public readonly Dictionary<int, ItemPieceRecord> PieceItems = [];
|
||||
[LoadRecord("GachaGradeProbTable.json", "Id")]
|
||||
public readonly Dictionary<int, GachaGradeProbRecord> GachaGradeProb = [];
|
||||
[LoadRecord("GachaListProbTable.json", "Id")]
|
||||
public readonly Dictionary<int, GachaListProbRecord> GachaListProb = [];
|
||||
[LoadRecord("RecycleResearchStatTable.json", "Id")]
|
||||
public readonly Dictionary<int, RecycleResearchStatRecord> RecycleResearchStats = [];
|
||||
[LoadRecord("RecycleResearchLevelTable.json", "Id")]
|
||||
public readonly Dictionary<int, RecycleResearchLevelRecord> RecycleResearchLevels = [];
|
||||
|
||||
// Harmony Cube Data Tables
|
||||
[LoadRecord("ItemHarmonyCubeTable.json", "Id")]
|
||||
public readonly Dictionary<int, ItemHarmonyCubeRecord> ItemHarmonyCubeTable = [];
|
||||
|
||||
[LoadRecord("ItemHarmonyCubeLevelTable.json", "Id")]
|
||||
public readonly Dictionary<int, ItemHarmonyCubeLevelRecord> ItemHarmonyCubeLevelTable = [];
|
||||
|
||||
// Favorite Item Data Tables
|
||||
[LoadRecord("FavoriteItemTable.json", "Id")]
|
||||
public readonly Dictionary<int, FavoriteItemRecord> FavoriteItemTable = [];
|
||||
|
||||
[LoadRecord("FavoriteItemExpTable.json", "Id")]
|
||||
public readonly Dictionary<int, FavoriteItemExpRecord> FavoriteItemExpTable = [];
|
||||
|
||||
[LoadRecord("FavoriteItemLevelTable.json", "Id")]
|
||||
public readonly Dictionary<int, FavoriteItemLevelRecord> FavoriteItemLevelTable = [];
|
||||
|
||||
[LoadRecord("FavoriteItemProbabilityTable.json", "Id")]
|
||||
public readonly Dictionary<int, FavoriteItemProbabilityRecord> FavoriteItemProbabilityTable = [];
|
||||
|
||||
[LoadRecord("FavoriteItemQuestTable.json", "Id")]
|
||||
public readonly Dictionary<int, FavoriteItemQuestRecord> FavoriteItemQuestTable = [];
|
||||
|
||||
[LoadRecord("FavoriteItemQuestStageTable.json", "Id")]
|
||||
public readonly Dictionary<int, FavoriteItemQuestStageRecord> FavoriteItemQuestStageTable = [];
|
||||
|
||||
// Tables related to PlaySoda Arcade's event.
|
||||
|
||||
[LoadRecord("EventPlaySodaManagerTable.json", "Id")]
|
||||
public readonly Dictionary<int, EventPlaySodaManagerRecord> EventPlaySodaManagerTable = [];
|
||||
|
||||
[LoadRecord("EventPlaySodaStoryModeTable.json", "Id")]
|
||||
public readonly Dictionary<int, EventPlaySodaStoryModeRecord> EventPlaySodaStoryModeTable = [];
|
||||
|
||||
[LoadRecord("EventPlaySodaChallengeModeTable.json", "Id")]
|
||||
public readonly Dictionary<int, EventPlaySodaChallengeModeRecord> EventPlaySodaChallengeModeTable = [];
|
||||
|
||||
[LoadRecord("EventPlaySodaPointRewardTable.json", "Id")]
|
||||
public readonly Dictionary<int, EventPlaySodaPointRewardRecord> EventPlaySodaPointRewardTable = [];
|
||||
|
||||
// Tables related to InTheMirror Arcade's event.
|
||||
|
||||
[LoadRecord("EventMvgQuestTable.json", "Id")]
|
||||
public readonly Dictionary<int, EventMVGQuestRecord_Raw> EventMvgQuestTable = [];
|
||||
|
||||
[LoadRecord("EventMvgShopTable.json", "Id")]
|
||||
public readonly Dictionary<int, EventMVGShopRecord_Raw> EventMvgShopTable = [];
|
||||
|
||||
[LoadRecord("EventMVGMissionTable.json", "Id")]
|
||||
public readonly Dictionary<int, EventMVGMissionRecord_Raw> EventMvgMissionTable = [];
|
||||
|
||||
static async Task<GameData> BuildAsync()
|
||||
{
|
||||
await Load();
|
||||
|
||||
Logging.WriteLine("Preparing");
|
||||
Stopwatch stopWatch = new();
|
||||
stopWatch.Start();
|
||||
await Instance.Parse();
|
||||
|
||||
stopWatch.Stop();
|
||||
Logging.WriteLine("Preparing took " + stopWatch.Elapsed);
|
||||
return Instance;
|
||||
}
|
||||
|
||||
public GameData(string filePath, string mpkFilePath)
|
||||
{
|
||||
if (!File.Exists(filePath)) throw new ArgumentException("Static data file must exist", nameof(filePath));
|
||||
|
||||
// disable warnings
|
||||
ZipStream = new();
|
||||
|
||||
// process json data
|
||||
byte[] rawBytes = File.ReadAllBytes(filePath);
|
||||
Sha256Hash = SHA256.HashData(rawBytes);
|
||||
Size = rawBytes.Length;
|
||||
|
||||
// process mpk data
|
||||
if (!string.IsNullOrEmpty(mpkFilePath))
|
||||
{
|
||||
byte[] rawBytes2 = File.ReadAllBytes(mpkFilePath);
|
||||
MpkHash = SHA256.HashData(rawBytes2);
|
||||
MpkSize = rawBytes2.Length;
|
||||
}
|
||||
|
||||
if (UseMemoryPack)
|
||||
LoadGameData(mpkFilePath, GameConfig.Root.StaticDataMpk);
|
||||
else
|
||||
LoadGameData(filePath, GameConfig.Root.StaticData);
|
||||
if (MainZip == null) throw new Exception("failed to read zip file");
|
||||
}
|
||||
|
||||
#region Data loading
|
||||
private static byte[] PresharedValue = [0xCB, 0xC2, 0x1C, 0x6F, 0xF3, 0xF5, 0x07, 0xF5, 0x05, 0xBA, 0xCA, 0xD4, 0x98, 0x28, 0x84, 0x1F, 0xF0, 0xD1, 0x38, 0xC7, 0x61, 0xDF, 0xD6, 0xE6, 0x64, 0x9A, 0x85, 0x13, 0x3E, 0x1A, 0x6A, 0x0C, 0x68, 0x0E, 0x2B, 0xC4, 0xDF, 0x72, 0xF8, 0xC6, 0x55, 0xE4, 0x7B, 0x14, 0x36, 0x18, 0x3B, 0xA7, 0xD1, 0x20, 0x81, 0x22, 0xD1, 0xA9, 0x18, 0x84, 0x65, 0x13, 0x0B, 0xED, 0xA3, 0x00, 0xE5, 0xD9];
|
||||
private static RSAParameters LoadParameters = new()
|
||||
{
|
||||
Exponent = [0x01, 0x00, 0x01],
|
||||
Modulus = [0x89, 0xD6, 0x66, 0x00, 0x7D, 0xFC, 0x7D, 0xCE, 0x83, 0xA6, 0x62, 0xE3, 0x1A, 0x5E, 0x9A, 0x53, 0xC7, 0x8A, 0x27, 0xF3, 0x67, 0xC1, 0xF3, 0xD4, 0x37, 0xFE, 0x50, 0x6D, 0x38, 0x45, 0xDF, 0x7E, 0x73, 0x5C, 0xF4, 0x9D, 0x40, 0x4C, 0x8C, 0x63, 0x21, 0x97, 0xDF, 0x46, 0xFF, 0xB2, 0x0D, 0x0E, 0xDB, 0xB2, 0x72, 0xB4, 0xA8, 0x42, 0xCD, 0xEE, 0x48, 0x06, 0x74, 0x4F, 0xE9, 0x56, 0x6E, 0x9A, 0xB1, 0x60, 0x18, 0xBC, 0x86, 0x0B, 0xB6, 0x32, 0xA7, 0x51, 0x00, 0x85, 0x7B, 0xC8, 0x72, 0xCE, 0x53, 0x71, 0x3F, 0x64, 0xC2, 0x25, 0x58, 0xEF, 0xB0, 0xC9, 0x1D, 0xE3, 0xB3, 0x8E, 0xFC, 0x55, 0xCF, 0x8B, 0x02, 0xA5, 0xC8, 0x1E, 0xA7, 0x0E, 0x26, 0x59, 0xA8, 0x33, 0xA5, 0xF1, 0x11, 0xDB, 0xCB, 0xD3, 0xA7, 0x1F, 0xB1, 0xC6, 0x10, 0x39, 0xC8, 0x31, 0x1D, 0x60, 0xDB, 0x0D, 0xA4, 0x13, 0x4B, 0x2B, 0x0E, 0xF3, 0x6F, 0x69, 0xCB, 0xA8, 0x62, 0x03, 0x69, 0xE6, 0x95, 0x6B, 0x8D, 0x11, 0xF6, 0xAF, 0xD9, 0xC2, 0x27, 0x3A, 0x32, 0x12, 0x05, 0xC3, 0xB1, 0xE2, 0x81, 0x4B, 0x40, 0xF8, 0x8B, 0x8D, 0xBA, 0x1F, 0x55, 0x60, 0x2C, 0x09, 0xC6, 0xED, 0x73, 0x96, 0x32, 0xAF, 0x5F, 0xEE, 0x8F, 0xEB, 0x5B, 0x93, 0xCF, 0x73, 0x13, 0x15, 0x6B, 0x92, 0x7B, 0x27, 0x0A, 0x13, 0xF0, 0x03, 0x4D, 0x6F, 0x5E, 0x40, 0x7B, 0x9B, 0xD5, 0xCE, 0xFC, 0x04, 0x97, 0x7E, 0xAA, 0xA3, 0x53, 0x2A, 0xCF, 0xD2, 0xD5, 0xCF, 0x52, 0xB2, 0x40, 0x61, 0x28, 0xB1, 0xA6, 0xF6, 0x78, 0xFB, 0x69, 0x9A, 0x85, 0xD6, 0xB9, 0x13, 0x14, 0x6D, 0xC4, 0x25, 0x36, 0x17, 0xDB, 0x54, 0x0C, 0xD8, 0x77, 0x80, 0x9A, 0x00, 0x62, 0x83, 0xDD, 0xB0, 0x06, 0x64, 0xD0, 0x81, 0x5B, 0x0D, 0x23, 0x9E, 0x88, 0xBD],
|
||||
DP = null
|
||||
};
|
||||
private void LoadGameData(string file, StaticData data)
|
||||
{
|
||||
using FileStream fileStream = File.Open(file, FileMode.Open, FileAccess.Read);
|
||||
|
||||
Rfc2898DeriveBytes a = new(PresharedValue, data.GetSalt2Bytes(), 10000, HashAlgorithmName.SHA256);
|
||||
byte[] key2 = a.GetBytes(32);
|
||||
|
||||
byte[] decryptionKey = key2[0..16];
|
||||
byte[] iv = key2[16..32];
|
||||
Aes aes = Aes.Create();
|
||||
aes.KeySize = 128;
|
||||
aes.BlockSize = 128;
|
||||
aes.Mode = CipherMode.CBC;
|
||||
aes.Key = decryptionKey;
|
||||
aes.IV = iv;
|
||||
ICryptoTransform transform = aes.CreateDecryptor();
|
||||
using CryptoStream stream = new(fileStream, transform, CryptoStreamMode.Read);
|
||||
|
||||
using MemoryStream ms = new();
|
||||
stream.CopyTo(ms);
|
||||
|
||||
byte[] bytes = ms.ToArray();
|
||||
ZipFile zip = new(ms, false);
|
||||
|
||||
ZipEntry signEntry = zip.GetEntry("sign") ?? throw new Exception("error 1");
|
||||
ZipEntry dataEntry = zip.GetEntry("data") ?? throw new Exception("error 2");
|
||||
Stream signStream = zip.GetInputStream(signEntry);
|
||||
Stream dataStream = zip.GetInputStream(dataEntry);
|
||||
|
||||
using MemoryStream signMs = new();
|
||||
signStream.CopyTo(signMs);
|
||||
|
||||
using MemoryStream dataMs = new();
|
||||
dataStream.CopyTo(dataMs);
|
||||
dataMs.Position = 0;
|
||||
|
||||
RSA rsa = RSA.Create(LoadParameters);
|
||||
if (!rsa.VerifyData(dataMs, signMs.ToArray(), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1))
|
||||
throw new Exception("error 3");
|
||||
|
||||
dataMs.Position = 0;
|
||||
Rfc2898DeriveBytes keyDec2 = new(PresharedValue, data.GetSalt1Bytes(), 10000, HashAlgorithmName.SHA256);
|
||||
byte[] key3 = keyDec2.GetBytes(32);
|
||||
|
||||
byte[] val2 = key3[0..16];
|
||||
byte[] iv2 = key3[16..32];
|
||||
|
||||
ZipStream = new MemoryStream();
|
||||
DoTransformation(val2, iv2, dataMs, ZipStream);
|
||||
|
||||
ZipStream.Position = 0;
|
||||
|
||||
MainZip = new ZipFile(ZipStream, false);
|
||||
}
|
||||
|
||||
public static void DoTransformation(byte[] key, byte[] salt, Stream inputStream, Stream outputStream)
|
||||
{
|
||||
SymmetricAlgorithm aes = Aes.Create();
|
||||
aes.Mode = CipherMode.ECB;
|
||||
aes.Padding = PaddingMode.None;
|
||||
|
||||
int blockSize = aes.BlockSize / 8;
|
||||
|
||||
if (salt.Length != blockSize)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"Salt size must be same as block size " +
|
||||
$"(actual: {salt.Length}, expected: {blockSize})");
|
||||
}
|
||||
|
||||
byte[] counter = (byte[])salt.Clone();
|
||||
|
||||
Queue<byte> xorMask = new();
|
||||
|
||||
byte[] zeroIv = new byte[blockSize];
|
||||
ICryptoTransform counterEncryptor = aes.CreateEncryptor(key, zeroIv);
|
||||
|
||||
int b;
|
||||
while ((b = inputStream.ReadByte()) != -1)
|
||||
{
|
||||
if (xorMask.Count == 0)
|
||||
{
|
||||
byte[] counterModeBlock = new byte[blockSize];
|
||||
|
||||
counterEncryptor.TransformBlock(
|
||||
counter, 0, counter.Length, counterModeBlock, 0);
|
||||
|
||||
for (int i2 = counter.Length - 1; i2 >= 0; i2--)
|
||||
{
|
||||
if (++counter[i2] != 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (byte b2 in counterModeBlock)
|
||||
{
|
||||
xorMask.Enqueue(b2);
|
||||
}
|
||||
}
|
||||
|
||||
byte mask = xorMask.Dequeue();
|
||||
outputStream.WriteByte((byte)(((byte)b) ^ mask));
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task Load()
|
||||
{
|
||||
string? targetFile = await AssetDownloadUtil.DownloadOrGetFileAsync(GameConfig.Root.StaticData.Url, CancellationToken.None) ?? throw new Exception("static data download fail");
|
||||
if (string.IsNullOrEmpty(GameConfig.Root.StaticDataMpk.Url))
|
||||
{
|
||||
_instance = new(targetFile, "");
|
||||
return;
|
||||
}
|
||||
|
||||
string? targetFile2 = await AssetDownloadUtil.DownloadOrGetFileAsync(GameConfig.Root.StaticDataMpk.Url, CancellationToken.None) ?? throw new Exception("static data download fail");
|
||||
_instance = new(targetFile, targetFile2);
|
||||
}
|
||||
#endregion
|
||||
|
||||
public async Task<X[]> LoadZip<X>(string entry, IProgress<double> bar) where X : new()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (UseMemoryPack) entry = entry.Replace(".json", ".mpk");
|
||||
|
||||
ZipEntry fileEntry = MainZip.GetEntry(entry);
|
||||
if (fileEntry == null)
|
||||
{
|
||||
Logging.WriteLine(entry + " does not exist in static data", LogType.Error);
|
||||
return [];
|
||||
}
|
||||
|
||||
X[]? deserializedObject;
|
||||
|
||||
if (UseMemoryPack)
|
||||
{
|
||||
Stream stream = MainZip.GetInputStream(fileEntry);
|
||||
deserializedObject = await MemoryPackSerializer.DeserializeAsync<X[]>(stream);
|
||||
}
|
||||
else
|
||||
{
|
||||
using var streamReader = new System.IO.StreamReader(MainZip.GetInputStream(fileEntry));
|
||||
var json = await streamReader.ReadToEndAsync();
|
||||
DataTable<X> obj = JsonConvert.DeserializeObject<DataTable<X>>(json) ?? throw new Exception("deserializeobject failed");
|
||||
deserializedObject = [.. obj.records];
|
||||
}
|
||||
|
||||
if (deserializedObject == null) throw new Exception("failed to parse " + entry);
|
||||
|
||||
currentFile++;
|
||||
bar.Report((double)currentFile / totalFiles);
|
||||
|
||||
return deserializedObject;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
Logging.WriteLine($"Failed to parse {entry}:\n{ex}\n", LogType.Error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Parse()
|
||||
{
|
||||
using ProgressBar progress = new();
|
||||
|
||||
totalFiles = GameDataInitializer.TotalFiles;
|
||||
if (totalFiles == 0) throw new Exception("Source generator failed.");
|
||||
|
||||
await GameDataInitializer.InitializeGameData(progress);
|
||||
|
||||
foreach (ZipEntry item in MainZip)
|
||||
{
|
||||
if (item.Name.StartsWith("FieldMapData_") && item.Name != "FieldMapData_EventMap.mpk")
|
||||
{
|
||||
FieldMapRecord[] x = await LoadZip<FieldMapRecord>(item.Name, progress);
|
||||
|
||||
foreach (FieldMapRecord map in x)
|
||||
{
|
||||
MapData.Add(map.Id, map);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// sanity checks
|
||||
if (QuestDataRecords.Count == 0) throw new Exception("QuestDataRecords should not be empty");
|
||||
}
|
||||
|
||||
public MainQuestRecord? GetMainQuestForStageClearCondition(int stage)
|
||||
{
|
||||
if (QuestDataRecords.Count == 0) throw new Exception("QuestDataRecords should not be empty");
|
||||
foreach (KeyValuePair<int, MainQuestRecord> item in QuestDataRecords)
|
||||
{
|
||||
if (item.Value.ConditionId == stage)
|
||||
{
|
||||
return item.Value;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
public MainQuestRecord? GetMainQuestByTableId(int tId)
|
||||
{
|
||||
return QuestDataRecords[tId];
|
||||
}
|
||||
public CampaignStageRecord? GetStageData(int stage)
|
||||
{
|
||||
return StageDataRecords[stage];
|
||||
}
|
||||
public RewardRecord? GetRewardTableEntry(int rewardId)
|
||||
{
|
||||
return RewardDataRecords[rewardId];
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns the level and its minimum value for XP value
|
||||
/// </summary>
|
||||
/// <param name="targetExp"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public (int, int) GetUserLevelFromUserExp(int targetExp)
|
||||
{
|
||||
int prevLevel = 0;
|
||||
int prevValue = 0;
|
||||
for (int i = 1; i < UserExpDataRecords.Count + 1; i++)
|
||||
{
|
||||
UserExpRecord item = UserExpDataRecords[i];
|
||||
|
||||
if (prevValue < targetExp)
|
||||
{
|
||||
prevLevel = item.Level;
|
||||
prevValue = item.Exp;
|
||||
}
|
||||
else
|
||||
{
|
||||
return (prevLevel, prevValue);
|
||||
}
|
||||
}
|
||||
return (-1, -1);
|
||||
}
|
||||
public int GetUserMinXpForLevel(int targetLevel)
|
||||
{
|
||||
for (int i = 1; i < UserExpDataRecords.Count + 1; i++)
|
||||
{
|
||||
UserExpRecord item = UserExpDataRecords[i];
|
||||
|
||||
if (targetLevel == item.Level)
|
||||
{
|
||||
return item.Exp;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
public string? GetMapIdFromDBFieldName(string field)
|
||||
{
|
||||
// Get game map ID from DB Field Name (ex: 1_Normal for chapter 1 normal)
|
||||
string[] keys = field.Split("_");
|
||||
if (int.TryParse(keys[0], out int chapterNum))
|
||||
{
|
||||
string difficulty = keys[1];
|
||||
|
||||
foreach (KeyValuePair<int, CampaignChapterRecord> item in ChapterCampaignData)
|
||||
{
|
||||
if (difficulty == "Normal" && item.Value.Chapter == chapterNum)
|
||||
{
|
||||
return item.Value.FieldId;
|
||||
}
|
||||
else if (difficulty == "Hard" && item.Value.Chapter == chapterNum)
|
||||
{
|
||||
return item.Value.HardFieldId;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return keys[0]; // Already a Map ID
|
||||
}
|
||||
}
|
||||
public int GetNormalChapterNumberFromFieldName(string field)
|
||||
{
|
||||
foreach (KeyValuePair<int, CampaignChapterRecord> item in ChapterCampaignData)
|
||||
{
|
||||
if (item.Value.FieldId == field)
|
||||
{
|
||||
return item.Value.Chapter;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
public IEnumerable<int> GetAllCostumes()
|
||||
{
|
||||
foreach (KeyValuePair<int, CharacterCostumeRecord> item in CharacterCostumeTable)
|
||||
{
|
||||
yield return item.Value.Id;
|
||||
}
|
||||
}
|
||||
|
||||
internal ContentsTutorialRecord GetTutorialDataById(int TableId)
|
||||
{
|
||||
return TutorialTable[TableId];
|
||||
}
|
||||
|
||||
public ItemSubType GetItemSubType(int itemType)
|
||||
{
|
||||
// Check if it's an equipment item
|
||||
if (ItemEquipTable.TryGetValue(itemType, out ItemEquipRecord? equipRecord))
|
||||
{
|
||||
return equipRecord.ItemSubType;
|
||||
}
|
||||
|
||||
// Check if it's a harmony cube item
|
||||
if (ItemHarmonyCubeTable.TryGetValue(itemType, out ItemHarmonyCubeRecord? harmonyCubeRecord))
|
||||
{
|
||||
return harmonyCubeRecord.ItemSubType;
|
||||
}
|
||||
|
||||
// Return null if item type not found
|
||||
return ItemSubType.None;
|
||||
}
|
||||
|
||||
internal IEnumerable<int> GetStageIdsForChapter(int chapterNumber, bool normal)
|
||||
{
|
||||
ChapterMod mod = normal ? ChapterMod.Normal : ChapterMod.Hard;
|
||||
foreach (KeyValuePair<int, CampaignStageRecord> item in StageDataRecords)
|
||||
{
|
||||
CampaignStageRecord data = item.Value;
|
||||
|
||||
int chVal = data.ChapterId - 1;
|
||||
|
||||
if (chapterNumber == chVal && data.ChapterMod == mod && data.StageType == StageType.Main)
|
||||
{
|
||||
yield return data.Id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<int, CharacterLevelRecord> GetCharacterLevelUpData()
|
||||
{
|
||||
return LevelData;
|
||||
}
|
||||
|
||||
public TacticAcademyFunctionRecord GetTacticAcademyLesson(int lessonId)
|
||||
{
|
||||
return TacticAcademyLessons[lessonId];
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetScenarioStageIdsForChapter(int chapterNumber)
|
||||
{
|
||||
return albumResourceRecords.Values.Where(record => record.TargetChapter == chapterNumber && !string.IsNullOrEmpty(record.ScenarioGroupId)).Select(record => record.ScenarioGroupId);
|
||||
}
|
||||
public bool IsValIdScenarioStage(string scenarioGroupId, int targetChapter, int targetStage)
|
||||
{
|
||||
// Only process stages that belong to the main quest
|
||||
if (!scenarioGroupId.StartsWith("d_main_"))
|
||||
{
|
||||
return false; // Exclude stages that don't belong to the main quest
|
||||
}
|
||||
|
||||
// Example regular stage format: "d_main_26_08"
|
||||
// Example bonus stage format: "d_main_18af_06"
|
||||
// Example stage with suffix format: "d_main_01_01_s" or "d_main_01_01_e"
|
||||
|
||||
string[] parts = scenarioGroupId.Split('_');
|
||||
|
||||
if (parts.Length < 4)
|
||||
{
|
||||
return false; // If it doesn't have at least 4 parts, it's not a valId stage
|
||||
}
|
||||
|
||||
string chapterPart = parts[2]; // This could be "26", "18af", "01"
|
||||
string stagePart = parts[3]; // This is the stage part, e.g., "08", "01_s", or "01_e"
|
||||
|
||||
// Remove any suffixes like "_s", "_e" from the stage part for comparison
|
||||
string cleanedStagePart = stagePart.Split('_')[0]; // Removes "_s", "_e", etc.
|
||||
|
||||
// Handle bonus stages (ending in "af" or having "_s", "_e" suffix)
|
||||
bool isBonusStage = chapterPart.EndsWith("af") || stagePart.Contains("_s") || stagePart.Contains("_e");
|
||||
|
||||
// Extract chapter number (remove "af" if present)
|
||||
string chapterNumberStr = isBonusStage && chapterPart.EndsWith("af")
|
||||
? chapterPart[..^2] // Remove "af"
|
||||
: chapterPart;
|
||||
|
||||
// Parse chapter and stage numbers
|
||||
if (int.TryParse(chapterNumberStr, out int chapter) && int.TryParse(cleanedStagePart, out int stage))
|
||||
{
|
||||
// Check if it's a bonus stage with a suffix
|
||||
bool isSpecialStage = stagePart.Contains("_s") || stagePart.Contains("_e");
|
||||
|
||||
// Only accept stages if they are:
|
||||
// 1. In a chapter less than the target chapter
|
||||
// 2. OR in the target chapter but with a stage number less than or equal to the target stage
|
||||
// 3. OR it's a special stage (with "_s" or "_e") in the target chapter and target stage
|
||||
if (chapter < targetChapter ||
|
||||
(chapter == targetChapter && (stage < targetStage || (stage == targetStage && isSpecialStage))))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal string GetMapIdFromChapter(int chapter, ChapterMod mod)
|
||||
{
|
||||
CampaignChapterRecord data = ChapterCampaignData[chapter - 1];
|
||||
if (mod == ChapterMod.Hard)
|
||||
return data.HardFieldId;
|
||||
else return data.FieldId;
|
||||
}
|
||||
internal string GetMapIdFromChapter(int chapter, string mod)
|
||||
{
|
||||
CampaignChapterRecord data = ChapterCampaignData[chapter - 1];
|
||||
if (mod == "Hard")
|
||||
return data.HardFieldId;
|
||||
else return data.FieldId;
|
||||
}
|
||||
|
||||
internal int GetConditionReward(int groupId, long damage)
|
||||
{
|
||||
IEnumerable<KeyValuePair<int, ConditionRewardRecord>> results = ConditionRewards.Where(x => x.Value.Group == groupId && x.Value.ValueMin <= damage && x.Value.ValueMax >= damage);
|
||||
if (results.Any())
|
||||
return results.FirstOrDefault().Value.RewardId;
|
||||
else return 0;
|
||||
}
|
||||
|
||||
public FavoriteItemQuestRecord? GetFavoriteItemQuestTableData(int questId)
|
||||
{
|
||||
FavoriteItemQuestTable.TryGetValue(questId, out FavoriteItemQuestRecord?data);
|
||||
return data;
|
||||
}
|
||||
|
||||
public FavoriteItemQuestStageRecord? GetFavoriteItemQuestStageData(int stageId)
|
||||
{
|
||||
FavoriteItemQuestStageTable.TryGetValue(stageId, out FavoriteItemQuestStageRecord? data);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
public class DataTable<T>
|
||||
{
|
||||
public string version { get; set; } = "";
|
||||
public List<T> records { get; set; } = [];
|
||||
}
|
||||
}
|
||||
16819
EpinelPS/Data/JsonStaticData.cs
Normal file
16819
EpinelPS/Data/JsonStaticData.cs
Normal file
File diff suppressed because it is too large
Load Diff
10
EpinelPS/Data/LoadRecordAttribute.cs
Normal file
10
EpinelPS/Data/LoadRecordAttribute.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace EpinelPS.Data
|
||||
{
|
||||
[System.AttributeUsage(System.AttributeTargets.Field)]
|
||||
|
||||
public class LoadRecordAttribute(string file, string primaryKey) : Attribute
|
||||
{
|
||||
public string File { get; set; } = file;
|
||||
public string PrimaryKey { get; set; } = primaryKey;
|
||||
}
|
||||
}
|
||||
6
EpinelPS/Database/DatabaseConnection.cs
Normal file
6
EpinelPS/Database/DatabaseConnection.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace EpinelPS.Database;
|
||||
|
||||
public class DatabaseConnection
|
||||
{
|
||||
|
||||
}
|
||||
154
EpinelPS/Database/JsonDb.cs
Normal file
154
EpinelPS/Database/JsonDb.cs
Normal file
@@ -0,0 +1,154 @@
|
||||
using System.Globalization;
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Utils;
|
||||
using Google.Protobuf;
|
||||
using Newtonsoft.Json;
|
||||
using Paseto;
|
||||
using Paseto.Builder;
|
||||
|
||||
namespace EpinelPS.Database
|
||||
{
|
||||
internal class JsonDb
|
||||
{
|
||||
public static CoreInfo Instance { get; internal set; }
|
||||
|
||||
// Note: change this in sodium
|
||||
public static byte[] ServerPrivateKey = Convert.FromBase64String("FSUY8Ohd942n5LWAfxn6slK3YGwc8OqmyJoJup9nNos=");
|
||||
public static byte[] ServerPublicKey = Convert.FromBase64String("04hFDd1e/BOEF2h4b0MdkX2h6W5REeqyW+0r9+eSeh0=");
|
||||
|
||||
static JsonDb()
|
||||
{
|
||||
if (!File.Exists(AppDomain.CurrentDomain.BaseDirectory + "/db.json"))
|
||||
{
|
||||
Console.WriteLine("users: warning: configuration not found, writing default data");
|
||||
Instance = new CoreInfo();
|
||||
Save();
|
||||
}
|
||||
|
||||
var j = JsonConvert.DeserializeObject<CoreInfo>(File.ReadAllText(AppDomain.CurrentDomain.BaseDirectory + "/db.json"));
|
||||
if (j != null)
|
||||
{
|
||||
Instance = j;
|
||||
|
||||
if (Instance.DbVersion != 5)
|
||||
{
|
||||
Logging.Warn("!!!WARNING!!!");
|
||||
Logging.Warn("Database version is extremely out of date.");
|
||||
Logging.Warn("It is recommended to delete db.json to avoid issues.");
|
||||
}
|
||||
|
||||
if (Instance.LauncherTokenKey.Length == 0)
|
||||
{
|
||||
Console.WriteLine("Launcher token key is null, generating new key");
|
||||
|
||||
var pasetoKey = new PasetoBuilder().Use(ProtocolVersion.V4, Purpose.Local)
|
||||
.GenerateSymmetricKey();
|
||||
Instance.LauncherTokenKey = pasetoKey.Key.ToArray();
|
||||
}
|
||||
if (Instance.EncryptionTokenKey.Length == 0)
|
||||
{
|
||||
Console.WriteLine("EncryptionTokenKey is null, generating new key");
|
||||
|
||||
var pasetoKey = new PasetoBuilder().Use(ProtocolVersion.V4, Purpose.Local)
|
||||
.GenerateSymmetricKey();
|
||||
Instance.EncryptionTokenKey = pasetoKey.Key.ToArray();
|
||||
}
|
||||
|
||||
Save();
|
||||
|
||||
Logging.SetOutputLevel(Instance.LogLevel);
|
||||
|
||||
ValidateDb();
|
||||
Console.WriteLine("JsonDb: Loaded");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Failed to read configuration json file");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void Reload()
|
||||
{
|
||||
if (!File.Exists(AppDomain.CurrentDomain.BaseDirectory + "/db.json"))
|
||||
{
|
||||
Console.WriteLine("users: warning: configuration not found, writing default data");
|
||||
Instance = new CoreInfo();
|
||||
Save();
|
||||
}
|
||||
|
||||
var j = JsonConvert.DeserializeObject<CoreInfo>(File.ReadAllText(AppDomain.CurrentDomain.BaseDirectory + "/db.json"));
|
||||
if (j != null)
|
||||
{
|
||||
Instance = j;
|
||||
Console.WriteLine("Database reload complete.");
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateDb()
|
||||
{
|
||||
// check if character level is valid
|
||||
foreach (var user in Instance.Users)
|
||||
{
|
||||
foreach (var c in user.Characters)
|
||||
{
|
||||
if (c.Level > 1000)
|
||||
{
|
||||
Console.WriteLine($"Warning: Character level for character {c.Tid} cannot be above 1000, setting to 1000");
|
||||
c.Level = 1000;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static User? GetUser(ulong id)
|
||||
{
|
||||
return Instance.Users.Where(x => x.ID == id).FirstOrDefault();
|
||||
}
|
||||
public static void Save()
|
||||
{
|
||||
if (Instance != null)
|
||||
{
|
||||
File.WriteAllText(AppDomain.CurrentDomain.BaseDirectory + "/db.json", JsonConvert.SerializeObject(Instance, Formatting.Indented));
|
||||
}
|
||||
}
|
||||
public static int CurrentJukeboxBgm(int position)
|
||||
{
|
||||
var activeJukeboxBgm = new List<int>();
|
||||
//important first position holds lobby bgm id and second commanders room bgm id
|
||||
foreach (var user in Instance.Users)
|
||||
{
|
||||
if (user.JukeboxBgm == null || user.JukeboxBgm.Count == 0)
|
||||
{
|
||||
// this if statemet only exists becaus some weird black magic copies default value over and over
|
||||
//in the file when its set in public List<int> JukeboxBgm = new List<int>();
|
||||
//delete when or if it gets fixed
|
||||
|
||||
user.JukeboxBgm = [2, 5];
|
||||
}
|
||||
|
||||
activeJukeboxBgm.AddRange(user.JukeboxBgm);
|
||||
}
|
||||
|
||||
if (activeJukeboxBgm.Count == 0)
|
||||
{
|
||||
return 8995001;
|
||||
}
|
||||
|
||||
position = (position == 2 && activeJukeboxBgm.Count > 1) ? 2 : 1;
|
||||
return activeJukeboxBgm[position - 1];
|
||||
}
|
||||
|
||||
public static bool IsSickPulls(User selectedUser)
|
||||
{
|
||||
if (selectedUser != null)
|
||||
{
|
||||
return selectedUser.sickpulls;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"User not found");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
83
EpinelPS/EpinelPS.csproj
Normal file
83
EpinelPS/EpinelPS.csproj
Normal file
@@ -0,0 +1,83 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IncludeHttpRuleProtos>true</IncludeHttpRuleProtos>
|
||||
<PublishSingleFile>true</PublishSingleFile>
|
||||
<SelfContained>true</SelfContained>
|
||||
<IncludeNativeLibrariesForSelfExtract>True</IncludeNativeLibrariesForSelfExtract>
|
||||
<NoWarn>$(NoWarn);SYSLIB0057</NoWarn>
|
||||
<Version>0.135.4.3</Version>
|
||||
<CETCompat>false</CETCompat>
|
||||
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<DebugSymbols>false</DebugSymbols>
|
||||
<DebugType>none</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ASodium" Version="0.6.2" />
|
||||
<PackageReference Include="DnsClient" Version="1.8.0" />
|
||||
<PackageReference Include="Google.Api.CommonProtos" Version="2.17.0" />
|
||||
<PackageReference Include="Google.Protobuf.Tools" Version="3.31.1" />
|
||||
<PackageReference Include="Grpc.Tools" Version="2.72.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="MemoryPack" Version="1.21.4" />
|
||||
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="6.0.0" />
|
||||
<PackageReference Include="Paseto.Core" Version="1.4.1" />
|
||||
<PackageReference Include="PeterO.Cbor" Version="4.5.5" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.4.2" />
|
||||
<PackageReference Include="Sodium.Core" Version="1.4.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Protobuf Include="Protos\*.*" GrpcServices="Server" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Remove="Views\Shared\error.cshtml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="wwwroot\admin\**" />
|
||||
<None Include="wwwroot\admin\index.html" />
|
||||
<None Include="wwwroot\nikke_launcher\index.html" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="gameconfig.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="gameversion.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="site.pfx">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="wwwroot\**\*">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\EpinelPS.Analyzers\EpinelPS.Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<_ContentIncludedByDefault Remove="wwwroot\admin\assets\js\loginpage.js" />
|
||||
<_ContentIncludedByDefault Remove="wwwroot\admin\assets\login.css" />
|
||||
<_ContentIncludedByDefault Remove="wwwroot\admin\assets\login.jpg" />
|
||||
<_ContentIncludedByDefault Remove="wwwroot\admin\assets\style.css" />
|
||||
<_ContentIncludedByDefault Remove="wwwroot\admin\css\site.css" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
1
EpinelPS/Global.cs
Normal file
1
EpinelPS/Global.cs
Normal file
@@ -0,0 +1 @@
|
||||
global using EpinelPS.Models;
|
||||
@@ -1,19 +1,14 @@
|
||||
using nksrv.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace nksrv.LobbyServer.Msgs.Antibot
|
||||
namespace EpinelPS.LobbyServer.Antibot
|
||||
{
|
||||
[PacketPath("/antibot/battlereportdata")]
|
||||
public class BattleReportData : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
var req = await ReadData<ReqBattleReportData>();
|
||||
var response = new ResBattleReportData();
|
||||
ReqBattleReportData req = await ReadData<ReqBattleReportData>();
|
||||
ResBattleReportData response = new();
|
||||
|
||||
// this is responsible for server side anticheat
|
||||
|
||||
@@ -1,22 +1,17 @@
|
||||
using nksrv.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace nksrv.LobbyServer.Msgs.Antibot
|
||||
namespace EpinelPS.LobbyServer.Antibot
|
||||
{
|
||||
[PacketPath("/antibot/recvdata")]
|
||||
public class RecieveAntibotData : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
var req = await ReadData<ReqAntibotRecvData>();
|
||||
ReqAntibotRecvData req = await ReadData<ReqAntibotRecvData>();
|
||||
|
||||
// I don't really care about reimplementing the server side anticheat, so return
|
||||
|
||||
var response = new ResAntibotRecvData();
|
||||
ResAntibotRecvData response = new();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
37
EpinelPS/LobbyServer/Archive/ArchiveClearStage.cs
Normal file
37
EpinelPS/LobbyServer/Archive/ArchiveClearStage.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Database;
|
||||
namespace EpinelPS.LobbyServer.Archive
|
||||
{
|
||||
[PacketPath("/archive/storydungeon/clearstage")]
|
||||
public class ClearArchiveStage : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqClearArchiveStage req = await ReadData<ReqClearArchiveStage>(); // has fields EventId, StageId, BattleResult
|
||||
int evid = req.EventId;
|
||||
int stgid = req.StageId;
|
||||
int result = req.BattleResult; // if 2 add to event info as last stage
|
||||
User user = GetUser() ?? throw new Exception("User not found.");
|
||||
|
||||
// Check if the EventInfo exists for the given EventId
|
||||
if (!user.EventInfo.TryGetValue(evid, out EventData? eventData))
|
||||
{
|
||||
throw new Exception($"Event with ID {evid} not found.");
|
||||
}
|
||||
|
||||
// Update the EventData if BattleResult is 2
|
||||
if (result == 1)
|
||||
{
|
||||
|
||||
// Update the LastStage in EventData
|
||||
eventData.LastStage = stgid;
|
||||
|
||||
}
|
||||
JsonDb.Save();
|
||||
ResClearArchiveStage response = new();
|
||||
|
||||
// Send the response back to the client
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
18
EpinelPS/LobbyServer/Archive/ArchiveEnterStage.cs
Normal file
18
EpinelPS/LobbyServer/Archive/ArchiveEnterStage.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Archive
|
||||
{
|
||||
[PacketPath("/archive/storydungeon/enterstage")]
|
||||
public class EnterArchiveStage : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqEnterArchiveStage req = await ReadData<ReqEnterArchiveStage>();// has fields EventId StageId TeamNumber
|
||||
int evid = req.EventId;
|
||||
|
||||
ResEnterArchiveStage response = new();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
17
EpinelPS/LobbyServer/Archive/CheckBookmarkScenarioExists.cs
Normal file
17
EpinelPS/LobbyServer/Archive/CheckBookmarkScenarioExists.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Archive
|
||||
{
|
||||
[PacketPath("/bookmark/scenario/exist")]
|
||||
public class CheckBookmarkScenarioExists : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqExistScenarioBookmark req = await ReadData<ReqExistScenarioBookmark>();
|
||||
|
||||
ResExistScenarioBookmark response = new();
|
||||
// TODO
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
37
EpinelPS/LobbyServer/Archive/CompleteArchiveScenario.cs
Normal file
37
EpinelPS/LobbyServer/Archive/CompleteArchiveScenario.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Database;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Archive
|
||||
{
|
||||
[PacketPath("/archive/scenario/complete")]
|
||||
public class CompleteScenario : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqCompleteArchiveScenario req = await ReadData<ReqCompleteArchiveScenario>(); // req has EventId, ScenarioId, DialogType fields
|
||||
int evid = req.EventId;
|
||||
string scenid = req.ScenarioId;
|
||||
int dialtyp = req.DialogType;
|
||||
|
||||
User user = GetUser();
|
||||
|
||||
// Ensure we are working with the user's EventInfo and not CompletedScenarios
|
||||
if (!user.EventInfo.TryGetValue(evid, out EventData? evt))
|
||||
{
|
||||
// Create a new EventData if the event doesn't exist
|
||||
evt = new EventData();
|
||||
user.EventInfo[evid] = evt;
|
||||
}
|
||||
|
||||
// Ensure the CompletedScenarios list is initialized and add the ScenarioId
|
||||
if (!evt.CompletedScenarios.Contains(scenid))
|
||||
{
|
||||
evt.CompletedScenarios.Add(scenid);
|
||||
}
|
||||
JsonDb.Save();
|
||||
// Prepare and send the response
|
||||
ResCompleteArchiveScenario response = new();
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
31
EpinelPS/LobbyServer/Archive/FastClearArchiveStage.cs
Normal file
31
EpinelPS/LobbyServer/Archive/FastClearArchiveStage.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Database;
|
||||
namespace EpinelPS.LobbyServer.Archive
|
||||
{
|
||||
[PacketPath("/archive/storydungeon/fastclearstage")]
|
||||
public class FastClearArchiveStage : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqFastClearArchiveStage req = await ReadData<ReqFastClearArchiveStage>();
|
||||
int evid = req.EventId;
|
||||
int stgid = req.StageId;
|
||||
|
||||
User user = GetUser() ?? throw new Exception("User not found.");
|
||||
|
||||
// Check if the EventInfo exists for the given EventId
|
||||
if (!user.EventInfo.TryGetValue(evid, out EventData? eventData))
|
||||
{
|
||||
throw new Exception($"Event with ID {evid} not found.");
|
||||
}
|
||||
// Update the LastStage in EventData
|
||||
eventData.LastStage = stgid;
|
||||
|
||||
JsonDb.Save();
|
||||
ResFastClearArchiveStage response = new();
|
||||
|
||||
// Send the response back to the client
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
51
EpinelPS/LobbyServer/Archive/GetArchiveStoryDungeon.cs
Normal file
51
EpinelPS/LobbyServer/Archive/GetArchiveStoryDungeon.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Database;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Archive
|
||||
{
|
||||
[PacketPath("/archive/storydungeon/get")]
|
||||
public class GetArchiveStoryDungeon : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetArchiveStoryDungeon req = await ReadData<ReqGetArchiveStoryDungeon>(); // has EventId field
|
||||
int evid = req.EventId;
|
||||
User user = GetUser();
|
||||
|
||||
// Ensure the EventInfo dictionary contains the requested EventId
|
||||
if (!user.EventInfo.TryGetValue(evid, out EventData? eventData))
|
||||
{
|
||||
eventData = new EventData
|
||||
{
|
||||
CompletedScenarios = [], // Initialize empty list
|
||||
Diff = 0, // Default difficulty
|
||||
LastStage = 0 // Default last cleared stage
|
||||
};
|
||||
// Create a new default entry for the missing EventId
|
||||
user.EventInfo[evid] = eventData;
|
||||
JsonDb.Save();
|
||||
}
|
||||
|
||||
// Prepare the response
|
||||
ResGetArchiveStoryDungeon response = new()
|
||||
{
|
||||
// Populate team data
|
||||
TeamData = new NetUserTeamData
|
||||
{
|
||||
LastContentsTeamNumber = 1,
|
||||
Type = 1
|
||||
}
|
||||
};
|
||||
|
||||
// Populate the last cleared stage
|
||||
response.LastClearedArchiveStageList.Add(new NetLastClearedArchiveStage
|
||||
{
|
||||
DifficultyId = eventData.Diff,
|
||||
StageId = eventData.LastStage
|
||||
});
|
||||
|
||||
// Send the response
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
34
EpinelPS/LobbyServer/Archive/GetArchives.cs
Normal file
34
EpinelPS/LobbyServer/Archive/GetArchives.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Data;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Archive
|
||||
{
|
||||
[PacketPath("/archive/get")]
|
||||
public class GetArchives : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetArchiveRecord req = await ReadData<ReqGetArchiveRecord>();
|
||||
|
||||
ResGetArchiveRecord response = new();
|
||||
|
||||
// Explicitly select IDs from the records
|
||||
List<int> allIds = [.. GameData.Instance.archiveRecordManagerTable.Values.Select(record => record.Id)];
|
||||
|
||||
// Add the IDs to the response lists
|
||||
response.ArchiveRecordManagerList.AddRange(allIds);
|
||||
response.UnlockedArchiveRecordList.AddRange(allIds);
|
||||
|
||||
// Get entries with record_type "EventQuest"
|
||||
List<ArchiveRecordManagerRecord> eventQuestRecords = [.. GameData.Instance.archiveRecordManagerTable.Values.Where(record => record.RecordType == ArchiveRecordType.EventQuest)];
|
||||
|
||||
response.ArchiveEventQuest = new();
|
||||
response.ArchiveEventQuest.UnlockedArchiveRecordManagerEventQuestIdList.AddRange(eventQuestRecords.Select(record => record.Id));
|
||||
// TODO more fields
|
||||
|
||||
|
||||
// TODO: allow unlocking
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
21
EpinelPS/LobbyServer/Archive/GetMinigameData.cs
Normal file
21
EpinelPS/LobbyServer/Archive/GetMinigameData.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Archive
|
||||
{
|
||||
[PacketPath("/archive/minigame/getdata")]
|
||||
public class GetMinigameData : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetArchiveMiniGameData req = await ReadData<ReqGetArchiveMiniGameData>();
|
||||
|
||||
ResGetArchiveMiniGameData response = new()
|
||||
{
|
||||
Json = "{}"
|
||||
};
|
||||
// TODO
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
53
EpinelPS/LobbyServer/Archive/GetNonResettable.cs
Normal file
53
EpinelPS/LobbyServer/Archive/GetNonResettable.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Data; // Ensure this namespace is included
|
||||
|
||||
|
||||
namespace EpinelPS.LobbyServer.Archive
|
||||
{
|
||||
[PacketPath("/archive/scenario/getnonresettable")]
|
||||
public class GetNonResettable : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetNonResettableArchiveScenario req = await ReadData<ReqGetNonResettableArchiveScenario>(); // req has EventId field
|
||||
int evId = req.EventId;
|
||||
ResGetNonResettableArchiveScenario response = new();
|
||||
|
||||
// Access the GameData instance
|
||||
GameData gameData = GameData.Instance;
|
||||
|
||||
if (evId == 130002)
|
||||
{
|
||||
// Directly use the archiveEventQuestRecords dictionary
|
||||
foreach (ArchiveEventQuestRecord_Raw record in gameData.archiveEventQuestRecords.Values)
|
||||
{
|
||||
if (record.EventQuestManagerId == evId)
|
||||
{
|
||||
// Add the end_scenario_Id to the ScenarioIdList
|
||||
if (!string.IsNullOrEmpty(record.EndScenarioId))
|
||||
{
|
||||
response.ScenarioIdList.Add(record.EndScenarioId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Directly use the archiveEventStoryRecords dictionary
|
||||
foreach (ArchiveEventStoryRecord record in gameData.archiveEventStoryRecords.Values)
|
||||
{
|
||||
if (record.EventId == evId)
|
||||
{
|
||||
// Add the PrologueScenario to the ScenarioIdList
|
||||
if (!string.IsNullOrEmpty(record.PrologueScenario))
|
||||
{
|
||||
response.ScenarioIdList.Add(record.PrologueScenario);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
22
EpinelPS/LobbyServer/Archive/GetResettable.cs
Normal file
22
EpinelPS/LobbyServer/Archive/GetResettable.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Data;
|
||||
namespace EpinelPS.LobbyServer.Archive
|
||||
{
|
||||
[PacketPath("/archive/scenario/getresettable")]
|
||||
public class GetResettable : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetResettableArchiveScenario req = await ReadData<ReqGetResettableArchiveScenario>();
|
||||
ResGetResettableArchiveScenario response = new(); // has ScenarioIdList field that takes in strings
|
||||
|
||||
// Retrieve stage IDs from GameData
|
||||
List<string> stageIds = [.. GameData.Instance.archiveEventDungeonStageRecords.Values.Select(record => record.StageId.ToString())];
|
||||
|
||||
// Add them to the response
|
||||
response.ScenarioIdList.Add(stageIds);
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
38
EpinelPS/LobbyServer/Archive/MessengerGet.cs
Normal file
38
EpinelPS/LobbyServer/Archive/MessengerGet.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Data;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Archive
|
||||
{
|
||||
[PacketPath("/archive/messenger/get")]
|
||||
public class GetArchiveMessenger : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
// Read the request containing ArchiveMessengerGroupId
|
||||
ReqGetArchiveMessenger req = await ReadData<ReqGetArchiveMessenger>();
|
||||
int groupId = req.ArchiveMessengerGroupId;
|
||||
|
||||
// Initialize the response object
|
||||
ResGetArchiveMessenger response = new();
|
||||
|
||||
// Get the relevant data from ArchiveMessengerConditionTable
|
||||
GameData gameData = GameData.Instance;
|
||||
|
||||
if (gameData.archiveMessengerConditionRecords.TryGetValue(groupId, out ArchiveMessengerConditionRecord? conditionRecord))
|
||||
{
|
||||
foreach (var condition in conditionRecord.ArchiveMessengerConditionList)
|
||||
{
|
||||
// Add each condition as a NetArchiveMessage in the response
|
||||
response.ArchiveMessageList.Add(new NetArchiveMessage
|
||||
{
|
||||
ConditionId = condition.ConditionId,
|
||||
MessageId = conditionRecord.Tid // Correctly using tId as MessageId
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Write the response back
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
24
EpinelPS/LobbyServer/Arena/ChampionBadgeData.cs
Normal file
24
EpinelPS/LobbyServer/Arena/ChampionBadgeData.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using EpinelPS.Utils;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Arena
|
||||
{
|
||||
[PacketPath("/arena/champion/getbadgedata")]
|
||||
public class ChampionBadgeData : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetChampionArenaDataByBadge req = await ReadData<ReqGetChampionArenaDataByBadge>();
|
||||
ResGetChampionArenaDataByBadge response = new()
|
||||
{
|
||||
// TODO
|
||||
Schedule = new NetChampionArenaSchedule(),
|
||||
NextSchedule = new NetChampionArenaSchedule(),
|
||||
ChampionArenaContentsState = ChampionArenaContentsState.SeasonClosed,
|
||||
CurrentOrLastSeasonStartAt = Timestamp.FromDateTime(DateTime.UtcNow.AddDays(5))
|
||||
};
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
24
EpinelPS/LobbyServer/Arena/GetArena.cs
Normal file
24
EpinelPS/LobbyServer/Arena/GetArena.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using EpinelPS.Utils;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Arena
|
||||
{
|
||||
[PacketPath("/arena/get")]
|
||||
public class GetArena : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetArena req = await ReadData<ReqGetArena>();
|
||||
User user = GetUser();
|
||||
|
||||
ResGetArena response = new()
|
||||
{
|
||||
BanInfo = new NetArenaBanInfo() { Description = "Not Implemented", StartAt = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow), EndAt = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow.AddYears(10)) },
|
||||
User = new NetArenaData() { User = LobbyHandler.CreateWholeUserDataFromDbUser(user) }
|
||||
};
|
||||
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
22
EpinelPS/LobbyServer/Arena/GetArenaBanInfo.cs
Normal file
22
EpinelPS/LobbyServer/Arena/GetArenaBanInfo.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using EpinelPS.Utils;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Arena
|
||||
{
|
||||
[PacketPath("/arena/getbaninfo")]
|
||||
public class GetArenaBanInfo : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetArenaBanInfo req = await ReadData<ReqGetArenaBanInfo>();
|
||||
|
||||
ResGetArenaBanInfo response = new()
|
||||
{
|
||||
RookieArenaBanInfo = new NetArenaBanInfo() { Description = "Not Implemented", StartAt = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow), EndAt = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow.AddYears(10)) },
|
||||
SpecialArenaBanInfo = new NetArenaBanInfo() { Description = "Not Implemented", StartAt = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow), EndAt = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow.AddYears(10)) }
|
||||
};
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
23
EpinelPS/LobbyServer/Arena/GetChampion.cs
Normal file
23
EpinelPS/LobbyServer/Arena/GetChampion.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using EpinelPS.Utils;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Arena
|
||||
{
|
||||
[PacketPath("/arena/champion/get")]
|
||||
public class GetChampion : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetChampionArena req = await ReadData<ReqGetChampionArena>();
|
||||
|
||||
ResGetChampionArena response = new()
|
||||
{
|
||||
Schedule = new NetChampionArenaSchedule()
|
||||
};
|
||||
|
||||
// TODO
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
23
EpinelPS/LobbyServer/Arena/GetSpecialArena.cs
Normal file
23
EpinelPS/LobbyServer/Arena/GetSpecialArena.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using EpinelPS.Utils;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Arena
|
||||
{
|
||||
[PacketPath("/arena/special/get")]
|
||||
public class GetSpecialArena : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetSpecialArena req = await ReadData<ReqGetSpecialArena>();
|
||||
User user = GetUser();
|
||||
|
||||
ResGetSpecialArena response = new()
|
||||
{
|
||||
BanInfo = new NetArenaBanInfo() { Description = "Not Implemented", StartAt = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow), EndAt = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow.AddYears(10)) },
|
||||
User = new NetArenaData() { User = LobbyHandler.CreateWholeUserDataFromDbUser(user) }
|
||||
};
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
21
EpinelPS/LobbyServer/Arena/ShowSpecialArenaReward.cs
Normal file
21
EpinelPS/LobbyServer/Arena/ShowSpecialArenaReward.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using EpinelPS.Utils;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Arena
|
||||
{
|
||||
[PacketPath("/arena/special/showreward")]
|
||||
public class ShowSpecialArenaReward : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqShowSpecialArenaReward req = await ReadData<ReqShowSpecialArenaReward>();
|
||||
|
||||
ResShowSpecialArenaReward response = new()
|
||||
{
|
||||
IsBan = true,
|
||||
BanInfo = new NetArenaBanInfo() { Description = "Not Implemented", StartAt = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow), EndAt = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow.AddYears(10)) }
|
||||
};
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
18
EpinelPS/LobbyServer/Auth/AuthLogout.cs
Normal file
18
EpinelPS/LobbyServer/Auth/AuthLogout.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Auth
|
||||
{
|
||||
[PacketPath("/auth/logout")]
|
||||
public class AuthLogout : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqLogout req = await ReadData<ReqLogout>();
|
||||
|
||||
// TODO remove UsedAuthToken
|
||||
|
||||
await WriteDataAsync(new ResLogout());
|
||||
}
|
||||
}
|
||||
}
|
||||
63
EpinelPS/LobbyServer/Auth/DoEnterServer.cs
Normal file
63
EpinelPS/LobbyServer/Auth/DoEnterServer.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
using Google.Protobuf;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using Microsoft.AspNetCore.DataProtection.KeyManagement;
|
||||
using Paseto.Builder;
|
||||
using Paseto;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Auth
|
||||
{
|
||||
[PacketPath("/auth/enterserver")]
|
||||
public class GetUserOnlineStateLog : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqEnterServer req = await ReadData<ReqEnterServer>();
|
||||
|
||||
// request has auth token
|
||||
UsedAuthToken = req.AuthToken;
|
||||
foreach (AccessToken item in JsonDb.Instance.LauncherAccessTokens)
|
||||
{
|
||||
if (item.Token == UsedAuthToken)
|
||||
{
|
||||
UserId = item.UserID;
|
||||
}
|
||||
}
|
||||
if (UserId == 0) throw new BadHttpRequestException("unknown auth token", 403);
|
||||
User user = GetUser();
|
||||
|
||||
GameClientInfo rsp = LobbyHandler.GenGameClientTok(req.ClientPublicKey, UserId);
|
||||
|
||||
string token = new PasetoBuilder().Use(ProtocolVersion.V4, Purpose.Local)
|
||||
.WithKey(JsonDb.Instance.LauncherTokenKey, Encryption.SymmetricKey)
|
||||
.AddClaim("userId", UserId)
|
||||
.IssuedAt(DateTime.UtcNow)
|
||||
.Expiration(DateTime.UtcNow.AddDays(2))
|
||||
.Encode();
|
||||
|
||||
string encryptionToken = new PasetoBuilder().Use(ProtocolVersion.V4, Purpose.Local)
|
||||
.WithKey(JsonDb.Instance.LauncherTokenKey, Encryption.SymmetricKey)
|
||||
.AddClaim("data", JsonSerializer.Serialize(rsp))
|
||||
.IssuedAt(DateTime.UtcNow)
|
||||
.Expiration(DateTime.UtcNow.AddDays(2))
|
||||
.Encode();
|
||||
|
||||
|
||||
ResEnterServer response = new()
|
||||
{
|
||||
GameClientToken = token,
|
||||
FeatureDataInfo = new NetFeatureDataInfo() { }, // TODO
|
||||
Identifier = new NetLegacyUserIdentifier() { Server = 1000, Usn = (long)user.ID },
|
||||
ShouldRestartAfter = Duration.FromTimeSpan(TimeSpan.FromSeconds(86400)),
|
||||
|
||||
EncryptionToken = ByteString.CopyFromUtf8(encryptionToken)
|
||||
};
|
||||
|
||||
user.ResetDataIfNeeded();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
53
EpinelPS/LobbyServer/Auth/DoIntlAuth.cs
Normal file
53
EpinelPS/LobbyServer/Auth/DoIntlAuth.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Auth
|
||||
{
|
||||
[PacketPath("/auth/intl")]
|
||||
public class DoIntlAuth : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqAuthIntl req = await ReadData<ReqAuthIntl>();
|
||||
ResAuth response = new();
|
||||
|
||||
UsedAuthToken = req.Token;
|
||||
foreach (AccessToken item in JsonDb.Instance.LauncherAccessTokens)
|
||||
{
|
||||
if (item.Token == UsedAuthToken)
|
||||
{
|
||||
UserId = item.UserID;
|
||||
}
|
||||
}
|
||||
if (UserId == 0)
|
||||
{
|
||||
response.AuthError = new NetAuthError() { ErrorCode = AuthErrorCode.Error };
|
||||
}
|
||||
else
|
||||
{
|
||||
User user = GetUser();
|
||||
|
||||
if (user.IsBanned && user.BanEnd < DateTime.UtcNow)
|
||||
{
|
||||
user.IsBanned = false;
|
||||
user.BanId = 0;
|
||||
user.BanStart = DateTime.MinValue;
|
||||
user.BanEnd = DateTime.MinValue;
|
||||
JsonDb.Save();
|
||||
}
|
||||
|
||||
if (user.IsBanned)
|
||||
{
|
||||
response.BanInfo = new NetBanInfo() { BanId = user.BanId, Description = "The server admin is sad today because the hinge on his HP laptop broke which happened to be an HP Elitebook 8470p, and the RAM controller exploded and then fixed itself, please contact him", StartAt = Timestamp.FromDateTime(DateTime.SpecifyKind(user.BanStart, DateTimeKind.Utc)), EndAt = Timestamp.FromDateTime(DateTime.SpecifyKind(user.BanEnd, DateTimeKind.Utc)) };
|
||||
}
|
||||
else
|
||||
{
|
||||
response.AuthSuccess = new NetAuthSuccess() { AuthToken = req.Token, CentauriZoneId = "84", FirstAuth = false, PurchaseRestriction = new NetUserPurchaseRestriction() { PurchaseRestriction = PurchaseRestriction.Child, UpdatedAt = 638546758794611090 } };
|
||||
}
|
||||
}
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
26
EpinelPS/LobbyServer/Badge/DeleteBadge.cs
Normal file
26
EpinelPS/LobbyServer/Badge/DeleteBadge.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Badge
|
||||
{
|
||||
[PacketPath("/badge/delete")]
|
||||
public class DeleteBadge : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqDeleteBadge req = await ReadData<ReqDeleteBadge>();
|
||||
User user = GetUser();
|
||||
|
||||
ResDeleteBadge response = new();
|
||||
|
||||
foreach (long badgeId in req.BadgeSeqList)
|
||||
{
|
||||
user.Badges.RemoveAll(x => x.Seq == badgeId);
|
||||
}
|
||||
|
||||
JsonDb.Save();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
24
EpinelPS/LobbyServer/Badge/SyncBadge.cs
Normal file
24
EpinelPS/LobbyServer/Badge/SyncBadge.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using EpinelPS.Utils;
|
||||
using Google.Protobuf;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Badge
|
||||
{
|
||||
[PacketPath("/badge/sync")]
|
||||
public class SyncBadge : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqSyncBadge req = await ReadData<ReqSyncBadge>();
|
||||
User user = GetUser();
|
||||
|
||||
ResSyncBadge response = new();
|
||||
|
||||
foreach (BadgeModel item in user.Badges)
|
||||
{
|
||||
response.BadgeList.Add(item.ToNet());
|
||||
}
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
18
EpinelPS/LobbyServer/Campaign/CampaignPackageGetAll.cs
Normal file
18
EpinelPS/LobbyServer/Campaign/CampaignPackageGetAll.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Campaign
|
||||
{
|
||||
[PacketPath("/shutdownflags/campaignpackage/getall")]
|
||||
public class CampaignPackageGetAll : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqCampaignPackageGetAllShutdownFlags req = await ReadData<ReqCampaignPackageGetAllShutdownFlags>();
|
||||
|
||||
ResCampaignPackageGetAllShutdownFlags response = new();
|
||||
// TODO
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +1,29 @@
|
||||
using nksrv.LobbyServer.Msgs.Stage;
|
||||
using nksrv.StaticInfo;
|
||||
using nksrv.Utils;
|
||||
using Swan.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using EpinelPS.LobbyServer.Stage;
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace nksrv.LobbyServer.Msgs.Campaign
|
||||
namespace EpinelPS.LobbyServer.Campaign
|
||||
{
|
||||
[PacketPath("/campaign/getfield")]
|
||||
public class GetCampaignField : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
var req = await ReadData<ReqGetCampaignFieldData>();
|
||||
var user = GetUser();
|
||||
|
||||
ReqGetCampaignFieldData req = await ReadData<ReqGetCampaignFieldData>();
|
||||
User user = GetUser();
|
||||
|
||||
Console.WriteLine("Map ID: " + req.MapId);
|
||||
|
||||
var response = new ResGetCampaignFieldData();
|
||||
response.Field = GetStage.CreateFieldInfo(user, StaticDataParser.Instance.GetNormalChapterNumberFromFieldName(req.MapId), req.MapId.Contains("hard") ? "Hard" : "Normal");
|
||||
ResGetCampaignFieldData response = new()
|
||||
{
|
||||
Field = GetStage.CreateFieldInfo(user, req.MapId, out bool bossEntered),
|
||||
|
||||
// todo save this data
|
||||
response.Team = new NetUserTeamData() { LastContentsTeamNumber = 1, Type = 1 };
|
||||
// todo save this data
|
||||
Team = new NetUserTeamData() { LastContentsTeamNumber = 1, Type = 1 }
|
||||
};
|
||||
if (user.LastNormalStageCleared >= 6000003)
|
||||
{
|
||||
var team = new NetTeamData() { TeamNumber = 1 };
|
||||
NetTeamData team = new() { TeamNumber = 1 };
|
||||
team.Slots.Add(new NetTeamSlot() { Slot = 1, Value = 47263455 });
|
||||
team.Slots.Add(new NetTeamSlot() { Slot = 2, Value = 47263456 });
|
||||
team.Slots.Add(new NetTeamSlot() { Slot = 3, Value = 47263457 });
|
||||
@@ -39,14 +35,14 @@ namespace nksrv.LobbyServer.Msgs.Campaign
|
||||
}
|
||||
|
||||
string resultingJson;
|
||||
if (!user.MapJson.ContainsKey(req.MapId))
|
||||
if (!user.MapJson.TryGetValue(req.MapId, out string? value))
|
||||
{
|
||||
resultingJson = "";
|
||||
user.MapJson.Add(req.MapId, resultingJson);
|
||||
}
|
||||
else
|
||||
{
|
||||
resultingJson = user.MapJson[req.MapId];
|
||||
resultingJson = value;
|
||||
}
|
||||
|
||||
response.Json = resultingJson;
|
||||
30
EpinelPS/LobbyServer/Campaign/GetFieldObjectsCount.cs
Normal file
30
EpinelPS/LobbyServer/Campaign/GetFieldObjectsCount.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.LobbyServer.Stage;
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Campaign
|
||||
{
|
||||
[PacketPath("/campaign/getfieldobjectitemsnum")]
|
||||
public class GetFieldObjectsCount : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetCampaignFieldObjectItemsNum req = await ReadData<ReqGetCampaignFieldObjectItemsNum>();
|
||||
User user = GetUser();
|
||||
|
||||
ResGetCampaignFieldObjectItemsNum response = new();
|
||||
|
||||
foreach (KeyValuePair<string, FieldInfoNew> map in user.FieldInfoNew)
|
||||
{
|
||||
response.FieldObjectItemsNum.Add(new NetCampaignFieldObjectItemsNum()
|
||||
{
|
||||
MapId = map.Key,
|
||||
Count = map.Value.CompletedObjects.Count
|
||||
});
|
||||
}
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
52
EpinelPS/LobbyServer/Campaign/ObtainItem.cs
Normal file
52
EpinelPS/LobbyServer/Campaign/ObtainItem.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.LobbyServer.Stage;
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Campaign
|
||||
{
|
||||
[PacketPath("/campaign/obtain/item")]
|
||||
public class ObtainItem : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqObtainCampaignItem req = await ReadData<ReqObtainCampaignItem>();
|
||||
User user = GetUser();
|
||||
|
||||
ResObtainCampaignItem response = new();
|
||||
|
||||
if (!user.FieldInfoNew.TryGetValue(req.MapId, out FieldInfoNew? field))
|
||||
{
|
||||
field = new FieldInfoNew();
|
||||
user.FieldInfoNew.Add(req.MapId, field);
|
||||
}
|
||||
|
||||
|
||||
foreach (NetFieldObject item in field.CompletedObjects)
|
||||
{
|
||||
if (item.PositionId == req.FieldObject.PositionId)
|
||||
{
|
||||
Logging.WriteLine("attempted to collect campaign field object twice!", LogType.WarningAntiCheat);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Register and return reward
|
||||
|
||||
var map = GameData.Instance.MapData[req.MapId];
|
||||
|
||||
var position = map.ItemSpawner.Where(x => x.PositionId == req.FieldObject.PositionId).FirstOrDefault() ?? throw new Exception("bad position Id");
|
||||
|
||||
FieldItemRecord positionReward = GameData.Instance.FieldItems[position.ItemId];
|
||||
RewardRecord reward = GameData.Instance.GetRewardTableEntry(positionReward.TypeValue) ?? throw new Exception("failed to get reward");
|
||||
response.Reward = RewardUtils.RegisterRewardsForUser(user, reward);
|
||||
|
||||
// HIde it from the field
|
||||
field.CompletedObjects.Add(new NetFieldObject() { PositionId = req.FieldObject.PositionId, Type = req.FieldObject.Type});
|
||||
|
||||
JsonDb.Save();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,16 @@
|
||||
using nksrv.Utils;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace nksrv.LobbyServer.Msgs.Campaign
|
||||
namespace EpinelPS.LobbyServer.Campaign
|
||||
{
|
||||
[PacketPath("/campaign/savefield")]
|
||||
public class SaveField : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
var req = await ReadData<ReqSaveCampaignField>();
|
||||
var user = GetUser();
|
||||
ReqSaveCampaignField req = await ReadData<ReqSaveCampaignField>();
|
||||
User user = GetUser();
|
||||
|
||||
var response = new ResGetFieldTalkList();
|
||||
|
||||
Console.WriteLine($"save {req.MapId} with {req.Json}");
|
||||
ResSaveCampaignField response = new();
|
||||
|
||||
if (!user.MapJson.ContainsKey(req.MapId))
|
||||
{
|
||||
@@ -20,7 +18,7 @@ namespace nksrv.LobbyServer.Msgs.Campaign
|
||||
}
|
||||
else
|
||||
{
|
||||
user.MapJson[req.MapId] = req.Json;
|
||||
user.MapJson[req.MapId] = req.Json;
|
||||
}
|
||||
|
||||
await WriteDataAsync(response);
|
||||
27
EpinelPS/LobbyServer/Campaign/SaveFieldObject.cs
Normal file
27
EpinelPS/LobbyServer/Campaign/SaveFieldObject.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Campaign
|
||||
{
|
||||
[PacketPath("/campaign/savefieldobject")]
|
||||
public class SaveFieldObject : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqSaveCampaignFieldObject req = await ReadData<ReqSaveCampaignFieldObject>();
|
||||
User user = GetUser();
|
||||
|
||||
ResSaveCampaignFieldObject response = new();
|
||||
|
||||
Logging.WriteLine($"save {req.MapId} with {req.FieldObject.PositionId}", LogType.Debug);
|
||||
|
||||
FieldInfoNew field = user.FieldInfoNew[req.MapId];
|
||||
|
||||
field.CompletedObjects.Add(new NetFieldObject() { PositionId = req.FieldObject.PositionId, Json = req.FieldObject.Json, Type = req.FieldObject.Type });
|
||||
JsonDb.Save();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
58
EpinelPS/LobbyServer/Character/ChangeSynchroDevice.cs
Normal file
58
EpinelPS/LobbyServer/Character/ChangeSynchroDevice.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Character
|
||||
{
|
||||
[PacketPath("/character/SynchroDevice/Change")]
|
||||
public class ChangeSynchroDevice : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqSynchroChange req = await ReadData<ReqSynchroChange>();
|
||||
User user = GetUser();
|
||||
|
||||
ResSynchroChange response = new();
|
||||
|
||||
List<CharacterModel> highestLevelCharacters = [.. user.Characters.OrderByDescending(x => x.Level).Take(5)];
|
||||
|
||||
int slot = 1;
|
||||
foreach (CharacterModel? item in highestLevelCharacters)
|
||||
{
|
||||
if (item.Level != 200)
|
||||
{
|
||||
throw new Exception("expected level to be 200");
|
||||
}
|
||||
|
||||
response.Characters.Add(new NetUserCharacterData() { Default = new() { Csn = item.Csn, Skill1Lv = item.Skill1Lvl, Skill2Lv = item.Skill2Lvl, CostumeId = item.CostumeId, Lv = item.Level, Grade = item.Grade, Tid = item.Tid, UltiSkillLv = item.UltimateLevel }, IsSynchro = user.GetSynchro(item.Csn) });
|
||||
|
||||
|
||||
|
||||
foreach (SynchroSlot s in user.SynchroSlots)
|
||||
{
|
||||
if (s.Slot == slot)
|
||||
{
|
||||
s.CharacterSerialNumber = item.Csn;
|
||||
break;
|
||||
}
|
||||
}
|
||||
slot++;
|
||||
}
|
||||
|
||||
user.SynchroDeviceUpgraded = true;
|
||||
|
||||
foreach (SynchroSlot item in user.SynchroSlots)
|
||||
{
|
||||
response.Slots.Add(new NetSynchroSlot() { Slot = item.Slot, AvailableRegisterAt = item.AvailableAt, Csn = item.CharacterSerialNumber });
|
||||
}
|
||||
|
||||
JsonDb.Save();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
18
EpinelPS/LobbyServer/Character/Counsel/Check.cs
Normal file
18
EpinelPS/LobbyServer/Character/Counsel/Check.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Character.Counsel;
|
||||
|
||||
[PacketPath("/character/attractive/check")]
|
||||
public class CheckCharacterCounsel : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqCounseledBefore req = await ReadData<ReqCounseledBefore>();
|
||||
User user = GetUser();
|
||||
|
||||
ResCounseledBefore response = new();
|
||||
|
||||
// TODO: Validate response from real server and pull info from user info
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
18
EpinelPS/LobbyServer/Character/Counsel/CheckCounsel.cs
Normal file
18
EpinelPS/LobbyServer/Character/Counsel/CheckCounsel.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Character.Counsel;
|
||||
|
||||
[PacketPath("/character/counsel/check")]
|
||||
public class CheckCounsel : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqCounseledBefore req = await ReadData<ReqCounseledBefore>();
|
||||
|
||||
ResCounseledBefore response = new();
|
||||
|
||||
response.IsCounseledBefore = false;
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
99
EpinelPS/LobbyServer/Character/Counsel/DoCounsel.cs
Normal file
99
EpinelPS/LobbyServer/Character/Counsel/DoCounsel.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Character.Counsel
|
||||
{
|
||||
[PacketPath("/character/attractive/counsel")]
|
||||
public class DoCounsel : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqCharacterCounsel req = await ReadData<ReqCharacterCounsel>();
|
||||
User user = GetUser();
|
||||
|
||||
ResCharacterCounsel response = new();
|
||||
|
||||
foreach (KeyValuePair<CurrencyType, long> currency in user.Currency)
|
||||
{
|
||||
response.Currencies.Add(new NetUserCurrencyData() { Type = (int)currency.Key, Value = currency.Value });
|
||||
}
|
||||
|
||||
NetUserAttractiveData? currentBondInfo = user.BondInfo.FirstOrDefault(x => x.NameCode == req.NameCode);
|
||||
|
||||
if (currentBondInfo != null)
|
||||
{
|
||||
int beforeLv = currentBondInfo.Lv;
|
||||
int beforeExp = currentBondInfo.Exp;
|
||||
|
||||
currentBondInfo.Exp += 100;
|
||||
currentBondInfo.CounseledCount++;
|
||||
currentBondInfo.CanCounselToday = true; // Always allow counseling
|
||||
UpdateAttractiveLevel(currentBondInfo);
|
||||
|
||||
response.Attractive = currentBondInfo;
|
||||
response.Exp = new NetIncreaseExpData
|
||||
{
|
||||
NameCode = currentBondInfo.NameCode,
|
||||
BeforeLv = beforeLv,
|
||||
BeforeExp = beforeExp,
|
||||
CurrentLv = currentBondInfo.Lv,
|
||||
CurrentExp = currentBondInfo.Exp,
|
||||
GainExp = 100
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
NetUserAttractiveData data = new NetUserAttractiveData()
|
||||
{
|
||||
NameCode = req.NameCode,
|
||||
Exp = 100,
|
||||
CounseledCount = 1,
|
||||
IsFavorites = false,
|
||||
CanCounselToday = true,
|
||||
Lv = 1
|
||||
};
|
||||
UpdateAttractiveLevel(data);
|
||||
user.BondInfo.Add(data);
|
||||
response.Attractive = data;
|
||||
response.Exp = new NetIncreaseExpData
|
||||
{
|
||||
NameCode = data.NameCode,
|
||||
BeforeLv = 1,
|
||||
BeforeExp = 0,
|
||||
CurrentLv = 1,
|
||||
CurrentExp = 100,
|
||||
GainExp = 100
|
||||
};
|
||||
}
|
||||
|
||||
JsonDb.Save();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
|
||||
private void UpdateAttractiveLevel(NetUserAttractiveData attractiveData)
|
||||
{
|
||||
while (attractiveData.Lv < 40)
|
||||
{
|
||||
AttractiveLevelRecord? levelInfo = GameData.Instance.AttractiveLevelTable.FirstOrDefault(x => x.Value.AttractiveLevel == attractiveData.Lv).Value;
|
||||
|
||||
if (levelInfo == null)
|
||||
{
|
||||
// No more level data
|
||||
break;
|
||||
}
|
||||
|
||||
if (attractiveData.Exp >= levelInfo.AttractivePoint)
|
||||
{
|
||||
attractiveData.Exp -= levelInfo.AttractivePoint;
|
||||
attractiveData.Lv++;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
114
EpinelPS/LobbyServer/Character/Counsel/Present.cs
Normal file
114
EpinelPS/LobbyServer/Character/Counsel/Present.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Character.Counsel
|
||||
{
|
||||
[PacketPath("/character/attractive/present")]
|
||||
public class Present : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqCharacterPresent req = await ReadData<ReqCharacterPresent>();
|
||||
User user = GetUser();
|
||||
|
||||
ResCharacterPresent response = new ResCharacterPresent();
|
||||
|
||||
NetUserAttractiveData? bondInfo = user.BondInfo.FirstOrDefault(x => x.NameCode == req.NameCode);
|
||||
if (bondInfo == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int totalExpGained = 0;
|
||||
CharacterRecord? characterRecord = GameData.Instance.CharacterTable.Values.FirstOrDefault(x => x.NameCode == req.NameCode);
|
||||
|
||||
foreach (NetItemData item in req.Items)
|
||||
{
|
||||
ItemMaterialRecord? materialInfo = GameData.Instance.itemMaterialTable.GetValueOrDefault(item.Tid);
|
||||
if (materialInfo != null && materialInfo.ItemSubType == ItemSubType.AttractiveMaterial)
|
||||
{
|
||||
int expGained = materialInfo.ItemValue * (int)item.Count;
|
||||
|
||||
if (characterRecord != null)
|
||||
{
|
||||
if (materialInfo.MaterialType == MaterialType.Corporation)
|
||||
{
|
||||
string corporation = materialInfo.NameLocalkey.Split('_')[2];
|
||||
if (corporation.Equals(characterRecord.Corporation.ToString(), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
expGained *= 5;
|
||||
}
|
||||
}
|
||||
else if (materialInfo.MaterialType == MaterialType.Squad)
|
||||
{
|
||||
string squad = materialInfo.NameLocalkey.Split('_')[2];
|
||||
if (squad.Equals(characterRecord.Squad.ToString(), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
expGained *= 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
totalExpGained += expGained;
|
||||
|
||||
ItemData? userItem = user.Items.FirstOrDefault(x => x.ItemType == item.Tid);
|
||||
if (userItem != null)
|
||||
{
|
||||
userItem.Count -= (int)item.Count;
|
||||
if (userItem.Count <= 0)
|
||||
{
|
||||
user.Items.Remove(userItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int beforeLv = bondInfo.Lv;
|
||||
int beforeExp = bondInfo.Exp;
|
||||
|
||||
bondInfo.Exp += totalExpGained;
|
||||
UpdateAttractiveLevel(bondInfo);
|
||||
|
||||
response.Attractive = bondInfo;
|
||||
response.Exp = new NetIncreaseExpData
|
||||
{
|
||||
NameCode = bondInfo.NameCode,
|
||||
BeforeLv = beforeLv,
|
||||
BeforeExp = beforeExp,
|
||||
CurrentLv = bondInfo.Lv,
|
||||
CurrentExp = bondInfo.Exp,
|
||||
GainExp = totalExpGained
|
||||
};
|
||||
|
||||
response.Items.AddRange(NetUtils.GetUserItems(user));
|
||||
|
||||
JsonDb.Save();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
|
||||
private void UpdateAttractiveLevel(NetUserAttractiveData attractiveData)
|
||||
{
|
||||
while (attractiveData.Lv < 40)
|
||||
{
|
||||
AttractiveLevelRecord? levelInfo = GameData.Instance.AttractiveLevelTable.Values.FirstOrDefault(x => x.AttractiveLevel == attractiveData.Lv);
|
||||
|
||||
if (levelInfo == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (attractiveData.Exp >= levelInfo.AttractivePoint)
|
||||
{
|
||||
attractiveData.Exp -= levelInfo.AttractivePoint;
|
||||
attractiveData.Lv++;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
99
EpinelPS/LobbyServer/Character/Counsel/QuickCounsel.cs
Normal file
99
EpinelPS/LobbyServer/Character/Counsel/QuickCounsel.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Character.Counsel
|
||||
{
|
||||
[PacketPath("/character/counsel/quick")]
|
||||
public class QuickCounsel : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqCharacterQuickCounsel req = await ReadData<ReqCharacterQuickCounsel>();
|
||||
User user = GetUser();
|
||||
|
||||
ResCharacterQuickCounsel response = new ResCharacterQuickCounsel();
|
||||
|
||||
foreach (KeyValuePair<CurrencyType, long> currency in user.Currency)
|
||||
{
|
||||
response.Currencies.Add(new NetUserCurrencyData() { Type = (int)currency.Key, Value = currency.Value });
|
||||
}
|
||||
|
||||
NetUserAttractiveData? bondInfo = user.BondInfo.FirstOrDefault(x => x.NameCode == req.NameCode);
|
||||
|
||||
if (bondInfo != null)
|
||||
{
|
||||
int beforeLv = bondInfo.Lv;
|
||||
int beforeExp = bondInfo.Exp;
|
||||
|
||||
bondInfo.Exp += 100;
|
||||
bondInfo.CounseledCount++;
|
||||
bondInfo.CanCounselToday = true; // Always allow counseling
|
||||
UpdateAttractiveLevel(bondInfo);
|
||||
|
||||
response.Attractive = bondInfo;
|
||||
response.Exp = new NetIncreaseExpData
|
||||
{
|
||||
NameCode = bondInfo.NameCode,
|
||||
BeforeLv = beforeLv,
|
||||
BeforeExp = beforeExp,
|
||||
CurrentLv = bondInfo.Lv,
|
||||
CurrentExp = bondInfo.Exp,
|
||||
GainExp = 100
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
NetUserAttractiveData data = new NetUserAttractiveData()
|
||||
{
|
||||
NameCode = req.NameCode,
|
||||
Exp = 100,
|
||||
CounseledCount = 1,
|
||||
IsFavorites = false,
|
||||
CanCounselToday = true,
|
||||
Lv = 1
|
||||
};
|
||||
UpdateAttractiveLevel(data);
|
||||
user.BondInfo.Add(data);
|
||||
response.Attractive = data;
|
||||
response.Exp = new NetIncreaseExpData
|
||||
{
|
||||
NameCode = data.NameCode,
|
||||
BeforeLv = 1,
|
||||
BeforeExp = 0,
|
||||
CurrentLv = 1,
|
||||
CurrentExp = 100,
|
||||
GainExp = 100
|
||||
};
|
||||
}
|
||||
|
||||
JsonDb.Save();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
|
||||
private void UpdateAttractiveLevel(NetUserAttractiveData attractiveData)
|
||||
{
|
||||
while (attractiveData.Lv < 40)
|
||||
{
|
||||
AttractiveLevelRecord? levelInfo = GameData.Instance.AttractiveLevelTable.Values.FirstOrDefault(x => x.AttractiveLevel == attractiveData.Lv);
|
||||
|
||||
if (levelInfo == null)
|
||||
{
|
||||
// No more level data
|
||||
break;
|
||||
}
|
||||
|
||||
if (attractiveData.Exp >= levelInfo.AttractivePoint)
|
||||
{
|
||||
attractiveData.Exp -= levelInfo.AttractivePoint;
|
||||
attractiveData.Lv++;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
70
EpinelPS/LobbyServer/Character/DoLimitBreak.cs
Normal file
70
EpinelPS/LobbyServer/Character/DoLimitBreak.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Character
|
||||
{
|
||||
[PacketPath("/character/upgrade")]
|
||||
public class DoLimitBreak : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
// Read the incoming request that contains the current CSN and ISN
|
||||
ReqCharacterUpgrade req = await ReadData<ReqCharacterUpgrade>(); // Contains csn and isn (read-only)
|
||||
ResCharacterUpgrade response = new();
|
||||
User user = GetUser();
|
||||
|
||||
// Get all character data from the game's character table
|
||||
List<CharacterRecord> fullchardata = [.. GameData.Instance.CharacterTable.Values];
|
||||
|
||||
CharacterModel targetCharacter = user.GetCharacterBySerialNumber(req.Csn) ?? throw new NullReferenceException();
|
||||
|
||||
// Find the element with the current csn from the request
|
||||
CharacterRecord currentCharacter = fullchardata.FirstOrDefault(c => c.Id == targetCharacter.Tid) ?? throw new NullReferenceException();
|
||||
|
||||
if (currentCharacter != null && targetCharacter != null)
|
||||
{
|
||||
if (currentCharacter.GradeCoreId == 103 || currentCharacter.GradeCoreId == 11 || currentCharacter.GradeCoreId == 201)
|
||||
{
|
||||
Console.WriteLine("cannot limit break any further!");
|
||||
await WriteDataAsync(response);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find a new CSN based on the `NameCode` of the current character and `GradeCoreId + 1`
|
||||
// For some reason, there is a seperate character for each limit/core break value.
|
||||
CharacterRecord? newCharacter = fullchardata.FirstOrDefault(c => c.NameCode == currentCharacter.NameCode && c.GradeCoreId == currentCharacter.GradeCoreId + 1);
|
||||
|
||||
|
||||
if (newCharacter != null)
|
||||
{
|
||||
// replace character in DB with new character
|
||||
targetCharacter.Grade++;
|
||||
targetCharacter.Tid = newCharacter.Id;
|
||||
|
||||
response.Character = new NetUserCharacterDefaultData()
|
||||
{
|
||||
Csn = req.Csn,
|
||||
CostumeId = targetCharacter.CostumeId,
|
||||
Grade = targetCharacter.Grade,
|
||||
Lv = user.GetSynchroLevel(),
|
||||
Skill1Lv = targetCharacter.Skill1Lvl,
|
||||
Skill2Lv = targetCharacter.Skill2Lvl,
|
||||
Tid = targetCharacter.Tid,
|
||||
UltiSkillLv = targetCharacter.UltimateLevel
|
||||
};
|
||||
|
||||
// remove spare body item
|
||||
ItemData bodyItem = user.Items.FirstOrDefault(i => i.Isn == req.Isn) ?? throw new NullReferenceException();
|
||||
user.RemoveItemBySerialNumber(req.Isn, 1);
|
||||
response.Items.Add(NetUtils.ToNet(bodyItem));
|
||||
|
||||
JsonDb.Save();
|
||||
}
|
||||
}
|
||||
|
||||
// Send the response back to the client
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
29
EpinelPS/LobbyServer/Character/GetCharacterAttractiveList.cs
Normal file
29
EpinelPS/LobbyServer/Character/GetCharacterAttractiveList.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Character
|
||||
{
|
||||
[PacketPath("/character/attractive/get")]
|
||||
public class GetCharacterAttractiveList : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetAttractiveList req = await ReadData<ReqGetAttractiveList>();
|
||||
User user = GetUser();
|
||||
|
||||
ResGetAttractiveList response = new()
|
||||
{
|
||||
CounselAvailableCount = 3 // TODO
|
||||
};
|
||||
|
||||
foreach (NetUserAttractiveData item in user.BondInfo)
|
||||
{
|
||||
response.Attractives.Add(item);
|
||||
item.CanCounselToday = true;
|
||||
|
||||
}
|
||||
|
||||
// TODO: ValIdate response from real server and pull info from user info
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
21
EpinelPS/LobbyServer/Character/GetCharacterCostume.cs
Normal file
21
EpinelPS/LobbyServer/Character/GetCharacterCostume.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Character
|
||||
{
|
||||
[PacketPath("/character/costume/get")]
|
||||
public class GetCharacterCostume : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetCharacterCostumeData req = await ReadData<ReqGetCharacterCostumeData>();
|
||||
|
||||
ResGetCharacterCostumeData response = new();
|
||||
|
||||
// return all
|
||||
response.CostumeIds.AddRange(GameData.Instance.GetAllCostumes());
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
29
EpinelPS/LobbyServer/Character/GetCharacterData.cs
Normal file
29
EpinelPS/LobbyServer/Character/GetCharacterData.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Character
|
||||
{
|
||||
[PacketPath("/character/get")]
|
||||
public class GetCharacterData : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetCharacterData req = await ReadData<ReqGetCharacterData>();
|
||||
User user = GetUser();
|
||||
|
||||
ResGetCharacterData response = new();
|
||||
foreach (CharacterModel item in user.Characters)
|
||||
{
|
||||
response.Character.Add(new NetUserCharacterData() { Default = new() { Csn = item.Csn, Skill1Lv = item.Skill1Lvl, Skill2Lv = item.Skill2Lvl, CostumeId = item.CostumeId, Lv = user.GetCharacterLevel(item.Csn, item.Level), Grade = item.Grade, Tid = item.Tid, UltiSkillLv = item.UltimateLevel }, IsSynchro = user.GetSynchro(item.Csn) });
|
||||
}
|
||||
|
||||
List<CharacterModel> highestLevelCharacters = [.. user.Characters.OrderByDescending(x => x.Level).Take(5)];
|
||||
|
||||
foreach (CharacterModel? c in highestLevelCharacters)
|
||||
{
|
||||
response.SynchroStandardCharacters.Add(c.Csn);
|
||||
}
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
50
EpinelPS/LobbyServer/Character/GetSynchrodevice.cs
Normal file
50
EpinelPS/LobbyServer/Character/GetSynchrodevice.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Character
|
||||
{
|
||||
[PacketPath("/character/synchrodevice/get")]
|
||||
public class GetSynchrodevice : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetSynchroData req = await ReadData<ReqGetSynchroData>();
|
||||
User user = GetUser();
|
||||
|
||||
if (user.SynchroSlots.Count == 0)
|
||||
{
|
||||
|
||||
user.SynchroSlots = [
|
||||
new SynchroSlot() { Slot = 1 },
|
||||
new SynchroSlot() { Slot = 2},
|
||||
new SynchroSlot() { Slot = 3 },
|
||||
new SynchroSlot() { Slot = 4 },
|
||||
new SynchroSlot() { Slot = 5 },
|
||||
];
|
||||
}
|
||||
|
||||
List<CharacterModel> highestLevelCharacters = [.. user.Characters.OrderByDescending(x => x.Level).Take(5)];
|
||||
|
||||
ResGetSynchroData response = new()
|
||||
{
|
||||
Synchro = new NetUserSynchroData()
|
||||
};
|
||||
|
||||
foreach (CharacterModel? item in highestLevelCharacters)
|
||||
{
|
||||
response.Synchro.StandardCharacters.Add(new NetUserCharacterData() { Default = new() { Csn = item.Csn, Skill1Lv = item.Skill1Lvl, Skill2Lv = item.Skill2Lvl, CostumeId = item.CostumeId, Lv = item.Level, Grade = item.Grade, Tid = item.Tid, UltiSkillLv = item.UltimateLevel }, IsSynchro = user.GetSynchro(item.Csn) });
|
||||
}
|
||||
|
||||
foreach (SynchroSlot item in user.SynchroSlots)
|
||||
{
|
||||
response.Synchro.Slots.Add(new NetSynchroSlot() { Slot = item.Slot, AvailableRegisterAt = 1, Csn = item.CharacterSerialNumber });
|
||||
}
|
||||
|
||||
response.Synchro.SynchroMaxLv = 1000;
|
||||
response.Synchro.SynchroLv = user.GetSynchroLevel();
|
||||
response.Synchro.IsChanged = user.SynchroDeviceUpgraded;
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
82
EpinelPS/LobbyServer/Character/LevelUp.cs
Normal file
82
EpinelPS/LobbyServer/Character/LevelUp.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Character
|
||||
{
|
||||
[PacketPath("/character/levelup")]
|
||||
public class LevelUp : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqCharacterLevelUp req = await ReadData<ReqCharacterLevelUp>();
|
||||
User user = GetUser();
|
||||
ResCharacterLevelUp response = new();
|
||||
Dictionary<int, CharacterLevelRecord> data = GameData.Instance.GetCharacterLevelUpData();
|
||||
|
||||
foreach (CharacterModel item in user.Characters.ToArray())
|
||||
{
|
||||
if (item.Csn == req.Csn)
|
||||
{
|
||||
int requiredCredit = 0;
|
||||
int requiredBattleData = 0;
|
||||
int requiredCoreDust = 0;
|
||||
for (int i = item.Level; i < req.Lv; i++)
|
||||
{
|
||||
CharacterLevelRecord levelUpData = data[i];
|
||||
requiredCredit += levelUpData.Gold;
|
||||
requiredBattleData += levelUpData.CharacterExp;
|
||||
requiredCoreDust += levelUpData.CharacterExp2;
|
||||
}
|
||||
|
||||
if (user.CanSubtractCurrency(CurrencyType.Gold, requiredCredit) &&
|
||||
user.CanSubtractCurrency(CurrencyType.CharacterExp, requiredBattleData) &&
|
||||
user.CanSubtractCurrency(CurrencyType.CharacterExp2, requiredCoreDust))
|
||||
{
|
||||
user.SubtractCurrency(CurrencyType.Gold, requiredCredit);
|
||||
user.SubtractCurrency(CurrencyType.CharacterExp, requiredBattleData);
|
||||
user.SubtractCurrency(CurrencyType.CharacterExp2, requiredCoreDust);
|
||||
item.Level = req.Lv;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logging.WriteLine("ERROR: Not enough currency for upgrade", LogType.WarningAntiCheat);
|
||||
return;
|
||||
}
|
||||
|
||||
response.Character = new()
|
||||
{
|
||||
CostumeId = item.CostumeId,
|
||||
Csn = item.Csn,
|
||||
Lv = item.Level,
|
||||
Skill1Lv = item.Skill1Lvl,
|
||||
Skill2Lv = item.Skill2Lvl,
|
||||
UltiSkillLv = item.UltimateLevel,
|
||||
Grade = item.Grade,
|
||||
Tid = item.Tid
|
||||
};
|
||||
List<CharacterModel> highestLevelCharacters = [.. user.Characters.OrderByDescending(x => x.Level).Take(5)];
|
||||
|
||||
response.SynchroLv = user.GetSynchroLevel();
|
||||
|
||||
foreach (CharacterModel? c in highestLevelCharacters)
|
||||
{
|
||||
response.SynchroStandardCharacters.Add(c.Csn);
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<CurrencyType, long> currency in user.Currency)
|
||||
{
|
||||
response.Currencies.Add(new NetUserCurrencyData() { Type = (int)currency.Key, Value = currency.Value });
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
user.AddTrigger(Trigger.CharacterLevelUpCount, 1);
|
||||
JsonDb.Save();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
39
EpinelPS/LobbyServer/Character/ObtainEpReward.cs
Normal file
39
EpinelPS/LobbyServer/Character/ObtainEpReward.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Database;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Character
|
||||
{
|
||||
[PacketPath("/character/attractive/obtainreward")]
|
||||
public class ObtainEpReward : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqObtainAttractiveReward req = await ReadData<ReqObtainAttractiveReward>();
|
||||
ResObtainAttractiveReward response = new();
|
||||
User user = GetUser();
|
||||
|
||||
// look up ID from name code and level
|
||||
KeyValuePair<int, AttractiveLevelRewardRecord> levelUpRecord = GameData.Instance.AttractiveLevelReward.Where(x => x.Value.AttractiveLevel == req.Lv && x.Value.NameCode == req.NameCode).FirstOrDefault();
|
||||
|
||||
foreach (NetUserAttractiveData item in user.BondInfo)
|
||||
{
|
||||
if (item.NameCode == req.NameCode)
|
||||
{
|
||||
if (!item.ObtainedRewardLevels.Contains(levelUpRecord.Value.Id))
|
||||
{
|
||||
item.ObtainedRewardLevels.Add(levelUpRecord.Value.Id);
|
||||
|
||||
RewardRecord reward = GameData.Instance.GetRewardTableEntry(levelUpRecord.Value.RewardId) ?? throw new Exception("failed to get reward");
|
||||
response.Reward = RewardUtils.RegisterRewardsForUser(user, reward);
|
||||
|
||||
JsonDb.Save();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
48
EpinelPS/LobbyServer/Character/RegisterSynchroDevice.cs
Normal file
48
EpinelPS/LobbyServer/Character/RegisterSynchroDevice.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Character
|
||||
{
|
||||
[PacketPath("/character/SynchroDevice/Regist")]
|
||||
public class RegisterSynchroDevice : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqSynchroRegister req = await ReadData<ReqSynchroRegister>();
|
||||
User user = GetUser();
|
||||
CharacterModel? targetCharacter = user.GetCharacterBySerialNumber(req.Csn) ?? throw new Exception("target character does not exist");
|
||||
ResSynchroRegister response = new();
|
||||
foreach (SynchroSlot item in user.SynchroSlots)
|
||||
{
|
||||
if (item.Slot == req.Slot)
|
||||
{
|
||||
if (item.CharacterSerialNumber != 0)
|
||||
{
|
||||
Console.WriteLine("must remove character from synchrodevice first");
|
||||
}
|
||||
else
|
||||
{
|
||||
item.CharacterSerialNumber = req.Csn;
|
||||
response.IsSynchro = true;
|
||||
response.Character = new NetUserCharacterDefaultData()
|
||||
{
|
||||
Csn = item.CharacterSerialNumber,
|
||||
CostumeId = targetCharacter.CostumeId,
|
||||
Grade = targetCharacter.Grade,
|
||||
Lv = user.GetSynchroLevel(),
|
||||
Skill1Lv = targetCharacter.Skill1Lvl,
|
||||
Skill2Lv = targetCharacter.Skill2Lvl,
|
||||
Tid = targetCharacter.Tid,
|
||||
UltiSkillLv = targetCharacter.UltimateLevel
|
||||
};
|
||||
response.Slot = new NetSynchroSlot() { AvailableRegisterAt = item.AvailableAt, Csn = item.CharacterSerialNumber, Slot = item.Slot };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JsonDb.Save();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
81
EpinelPS/LobbyServer/Character/ResetLevel.cs
Normal file
81
EpinelPS/LobbyServer/Character/ResetLevel.cs
Normal file
@@ -0,0 +1,81 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Character
|
||||
{
|
||||
[PacketPath("/character/growreset")]
|
||||
public class ResetLevel : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqCharacterGrowReset req = await ReadData<ReqCharacterGrowReset>();
|
||||
User user = GetUser();
|
||||
ResCharacterGrowReset response = new();
|
||||
Dictionary<int, CharacterLevelRecord> data = GameData.Instance.GetCharacterLevelUpData();
|
||||
|
||||
foreach (CharacterModel item in user.Characters.ToArray())
|
||||
{
|
||||
if (item.Csn == req.Csn)
|
||||
{
|
||||
if (item.Level == 1)
|
||||
{
|
||||
Logging.WriteLine("Character level is already 1 - cannot reset", LogType.WarningAntiCheat);
|
||||
return;
|
||||
}
|
||||
if (item.Level == 200)
|
||||
{
|
||||
Logging.WriteLine("Character level is 200 - cannot reset", LogType.WarningAntiCheat);
|
||||
return;
|
||||
}
|
||||
|
||||
int requiredCredit = 0;
|
||||
int requiredBattleData = 0;
|
||||
int requiredCoreDust = 0;
|
||||
for (int i = 1; i < item.Level; i++)
|
||||
{
|
||||
CharacterLevelRecord levelUpData = data[i];
|
||||
requiredCredit += levelUpData.Gold;
|
||||
requiredBattleData += levelUpData.CharacterExp;
|
||||
requiredCoreDust += levelUpData.CharacterExp2;
|
||||
}
|
||||
|
||||
user.AddCurrency(CurrencyType.Gold, requiredCredit);
|
||||
user.AddCurrency(CurrencyType.CharacterExp, requiredBattleData);
|
||||
user.AddCurrency(CurrencyType.CharacterExp2, requiredCoreDust);
|
||||
item.Level = 1;
|
||||
|
||||
response.Character = new()
|
||||
{
|
||||
CostumeId = item.CostumeId,
|
||||
Csn = item.Csn,
|
||||
Lv = item.Level,
|
||||
Skill1Lv = item.Skill1Lvl,
|
||||
Skill2Lv = item.Skill2Lvl,
|
||||
UltiSkillLv = item.UltimateLevel,
|
||||
Grade = item.Grade,
|
||||
Tid = item.Tid
|
||||
};
|
||||
List<CharacterModel> highestLevelCharacters = [.. user.Characters.OrderByDescending(x => x.Level).Take(5)];
|
||||
|
||||
response.SynchroLv = highestLevelCharacters.Last().Level;
|
||||
|
||||
foreach (CharacterModel? c in highestLevelCharacters)
|
||||
{
|
||||
response.SynchroStandardCharacters.Add(c.Csn);
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<CurrencyType, long> currency in user.Currency)
|
||||
{
|
||||
response.Currencies.Add(new NetUserCurrencyData() { Type = (int)currency.Key, Value = currency.Value });
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
JsonDb.Save();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
29
EpinelPS/LobbyServer/Character/SetCharacterCostume.cs
Normal file
29
EpinelPS/LobbyServer/Character/SetCharacterCostume.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Character
|
||||
{
|
||||
[PacketPath("/character/costume/set")]
|
||||
public class SetCharacterCostume : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqSetCharacterCostume req = await ReadData<ReqSetCharacterCostume>();
|
||||
User user = GetUser();
|
||||
|
||||
foreach (CharacterModel item in user.Characters)
|
||||
{
|
||||
if (item.Csn == req.Csn)
|
||||
{
|
||||
item.CostumeId = req.CostumeId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
JsonDb.Save();
|
||||
|
||||
ResSetCharacterCostume response = new();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
94
EpinelPS/LobbyServer/Character/SkillLevelUp.cs
Normal file
94
EpinelPS/LobbyServer/Character/SkillLevelUp.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Character
|
||||
{
|
||||
[PacketPath("/character/skill/levelup")]
|
||||
public class SkillLevelUp : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqCharacterSkillLevelUp req = await ReadData<ReqCharacterSkillLevelUp>();
|
||||
User user = GetUser();
|
||||
ResCharacterSkillLevelUp response = new();
|
||||
|
||||
CharacterModel character = user.Characters.FirstOrDefault(c => c.Csn == req.Csn) ?? throw new Exception("cannot find character");
|
||||
|
||||
CharacterRecord charRecord = GameData.Instance.CharacterTable.Values.FirstOrDefault(c => c.Id == character.Tid) ?? throw new Exception("cannot find character record");
|
||||
Dictionary<int, int> skillIdMap = new()
|
||||
{
|
||||
{ 1, charRecord.UltiSkillId },
|
||||
{ 2, charRecord.Skill1Id },
|
||||
{ 3, charRecord.Skill2Id }
|
||||
};
|
||||
Dictionary<int, int> skillLevelMap = new()
|
||||
{
|
||||
{ 1, character.UltimateLevel },
|
||||
{ 2, character.Skill1Lvl },
|
||||
{ 3, character.Skill2Lvl }
|
||||
};
|
||||
SkillInfoRecord skillRecord = GameData.Instance.skillInfoTable.Values.FirstOrDefault(s => s.Id == skillIdMap[req.Category] + (skillLevelMap[req.Category] - 1)) ?? throw new Exception("cannot find character skill record");
|
||||
CostRecord costRecord = GameData.Instance.costTable.Values.FirstOrDefault(c => c.Id == skillRecord.LevelUpCostId) ?? throw new Exception("cannot find character cost record");
|
||||
|
||||
foreach (CostData? cost in costRecord.Costs.Where(i => i.ItemType != RewardType.None))
|
||||
{
|
||||
ItemData item = user.Items.FirstOrDefault(i => i.ItemType == cost.ItemId) ?? throw new NullReferenceException();
|
||||
|
||||
item.Count -= cost.ItemValue;
|
||||
|
||||
response.Items.Add(new NetUserItemData
|
||||
{
|
||||
Isn = item.Isn,
|
||||
Tid = cost.ItemId,
|
||||
Count = item.Count,
|
||||
Csn = item.Csn,
|
||||
Corporation = item.Corp,
|
||||
Lv = item.Level,
|
||||
Exp = item.Exp,
|
||||
Position = item.Position
|
||||
});
|
||||
}
|
||||
|
||||
NetUserCharacterDefaultData newChar = new()
|
||||
{
|
||||
CostumeId = character.CostumeId,
|
||||
Csn = character.Csn,
|
||||
Lv = character.Level,
|
||||
Grade = character.Grade,
|
||||
Tid = character.Tid,
|
||||
DispatchTid = character.Tid,
|
||||
Skill1Lv = character.Skill1Lvl,
|
||||
Skill2Lv = character.Skill2Lvl,
|
||||
UltiSkillLv = character.UltimateLevel,
|
||||
};
|
||||
|
||||
if (req.Category == 1)
|
||||
{
|
||||
character.UltimateLevel++;
|
||||
newChar.UltiSkillLv++;
|
||||
}
|
||||
else if (req.Category == 2)
|
||||
{
|
||||
character.Skill1Lvl++;
|
||||
newChar.Skill1Lv++;
|
||||
}
|
||||
else if (req.Category == 3)
|
||||
{
|
||||
character.Skill2Lvl++;
|
||||
newChar.Skill2Lv++;
|
||||
}
|
||||
|
||||
if (character.UltimateLevel == 10 && character.Skill1Lvl == 10 && character.Skill2Lvl == 10)
|
||||
{
|
||||
user.AddTrigger(Trigger.CharacterSkillLevelMax, 1);
|
||||
}
|
||||
|
||||
response.Character = newChar;
|
||||
|
||||
JsonDb.Save();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
49
EpinelPS/LobbyServer/Character/SynchroAddSlot.cs
Normal file
49
EpinelPS/LobbyServer/Character/SynchroAddSlot.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Character
|
||||
{
|
||||
[PacketPath("/character/synchrodevice/addslot")]
|
||||
public class SynchroAddSlot : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqSynchroAddSlot req = await ReadData<ReqSynchroAddSlot>();
|
||||
User user = GetUser();
|
||||
ResSynchroAddSlot response = new();
|
||||
|
||||
SynchroSlot? slot = user.SynchroSlots.FirstOrDefault(x => x.Slot == req.Slot);
|
||||
if (slot != null)
|
||||
{
|
||||
response.Slot = new NetSynchroSlot
|
||||
{
|
||||
Csn = slot.CharacterSerialNumber,
|
||||
Slot = slot.Slot,
|
||||
AvailableRegisterAt = slot.AvailableAt
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
NetSynchroSlot newSlot = new()
|
||||
{
|
||||
Csn = 0,
|
||||
Slot = req.Slot,
|
||||
AvailableRegisterAt = 0
|
||||
};
|
||||
|
||||
user.SynchroSlots.Add(new SynchroSlot()
|
||||
{
|
||||
Slot = newSlot.Slot,
|
||||
CharacterSerialNumber = newSlot.Csn,
|
||||
AvailableAt = newSlot.AvailableRegisterAt
|
||||
});
|
||||
|
||||
response.Slot = newSlot;
|
||||
}
|
||||
|
||||
JsonDb.Save();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
39
EpinelPS/LobbyServer/Character/SynchroAddSlotByItem.cs
Normal file
39
EpinelPS/LobbyServer/Character/SynchroAddSlotByItem.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Character
|
||||
{
|
||||
[PacketPath("/character/synchrodevice/addslotbyitem")]
|
||||
public class SynchroAddSlotByItem : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
// Broken protocol so we dIdn't valIdate request data.
|
||||
// May fix later.
|
||||
ReqSynchroAddSlot req = await ReadData<ReqSynchroAddSlot>();
|
||||
|
||||
User user = GetUser();
|
||||
ResSynchroAddSlot response = new();
|
||||
|
||||
NetSynchroSlot newSlot = new()
|
||||
{
|
||||
Csn = 0,
|
||||
Slot = user.SynchroSlots.Last().Slot + 1, // any upper bound?
|
||||
AvailableRegisterAt = 0
|
||||
};
|
||||
|
||||
user.SynchroSlots.Add(new SynchroSlot()
|
||||
{
|
||||
Slot = newSlot.Slot,
|
||||
CharacterSerialNumber = newSlot.Csn,
|
||||
AvailableAt = newSlot.AvailableRegisterAt
|
||||
});
|
||||
|
||||
response.Slot = newSlot;
|
||||
|
||||
JsonDb.Save();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
56
EpinelPS/LobbyServer/Character/SynchroLevelUp.cs
Normal file
56
EpinelPS/LobbyServer/Character/SynchroLevelUp.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Character
|
||||
{
|
||||
[PacketPath("/character/SynchroDevice/LevelUp")]
|
||||
public class SynchroLevelUp : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqSynchroLevelUp req = await ReadData<ReqSynchroLevelUp>();
|
||||
User user = GetUser();
|
||||
|
||||
ResSynchroLevelUp response = new();
|
||||
Dictionary<int, CharacterLevelRecord> data = GameData.Instance.GetCharacterLevelUpData();
|
||||
|
||||
|
||||
int requiredCredit = 0;
|
||||
int requiredBattleData = 0;
|
||||
int requiredCoreDust = 0;
|
||||
CharacterLevelRecord levelUpData = data[user.SynchroDeviceLevel + 1];
|
||||
requiredCredit += levelUpData.Gold;
|
||||
requiredBattleData += levelUpData.CharacterExp;
|
||||
requiredCoreDust += levelUpData.CharacterExp2;
|
||||
|
||||
if (user.CanSubtractCurrency(CurrencyType.Gold, requiredCredit) &&
|
||||
user.CanSubtractCurrency(CurrencyType.CharacterExp, requiredBattleData) &&
|
||||
user.CanSubtractCurrency(CurrencyType.CharacterExp2, requiredCoreDust))
|
||||
{
|
||||
user.SubtractCurrency(CurrencyType.Gold, requiredCredit);
|
||||
user.SubtractCurrency(CurrencyType.CharacterExp, requiredBattleData);
|
||||
user.SubtractCurrency(CurrencyType.CharacterExp2, requiredCoreDust);
|
||||
user.SynchroDeviceLevel++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// TOOD: log this
|
||||
Console.WriteLine("ERROR: Not enough currency for upgrade");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
foreach (KeyValuePair<CurrencyType, long> currency in user.Currency)
|
||||
{
|
||||
response.Currencies.Add(new NetUserCurrencyData() { Type = (int)currency.Key, Value = currency.Value });
|
||||
}
|
||||
response.SynchroLv = user.SynchroDeviceLevel;
|
||||
|
||||
user.AddTrigger(Trigger.CharacterLevelUpCount, 1);
|
||||
|
||||
JsonDb.Save();
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
63
EpinelPS/LobbyServer/Character/UnregisterSynchroDevice.cs
Normal file
63
EpinelPS/LobbyServer/Character/UnregisterSynchroDevice.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Character
|
||||
{
|
||||
[PacketPath("/character/SynchroDevice/Unregist")]
|
||||
public class UnregisterSynchroDevice : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqSynchroUnregist req = await ReadData<ReqSynchroUnregist>();
|
||||
User user = GetUser();
|
||||
|
||||
ResSynchroUnregist response = new();
|
||||
|
||||
foreach (SynchroSlot item in user.SynchroSlots)
|
||||
{
|
||||
if (item.Slot == req.Slot)
|
||||
{
|
||||
if (item.CharacterSerialNumber == 0)
|
||||
{
|
||||
Logging.WriteLine("must add character from synchrodevice first", LogType.Warning);
|
||||
}
|
||||
else
|
||||
{
|
||||
long oldCSN = item.CharacterSerialNumber;
|
||||
item.CharacterSerialNumber = 0;
|
||||
CharacterModel data = user.GetCharacterBySerialNumber(oldCSN) ?? throw new Exception("failed to lookup character");
|
||||
|
||||
response.Character = new NetUserCharacterDefaultData()
|
||||
{
|
||||
Csn = data.Csn,
|
||||
CostumeId = data.CostumeId,
|
||||
Grade = data.Grade,
|
||||
Lv = data.Level,
|
||||
Skill1Lv = data.Skill1Lvl,
|
||||
Skill2Lv = data.Skill2Lvl,
|
||||
Tid = data.Tid,
|
||||
UltiSkillLv = data.UltimateLevel
|
||||
};
|
||||
response.Slot = new NetSynchroSlot() { AvailableRegisterAt = item.AvailableAt, Csn = item.CharacterSerialNumber, Slot = item.Slot };
|
||||
|
||||
response.IsSynchro = false;
|
||||
List<CharacterModel> highestLevelCharacters = [.. user.Characters.OrderByDescending(x => x.Level).Take(5)];
|
||||
|
||||
|
||||
foreach (CharacterModel? item2 in highestLevelCharacters)
|
||||
{
|
||||
response.SynchroStandardCharacters.Add(item2.Csn);
|
||||
}
|
||||
|
||||
response.SynchroLv = user.GetSynchroLevel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JsonDb.Save();
|
||||
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
76
EpinelPS/LobbyServer/Character/UpgradeCore.cs
Normal file
76
EpinelPS/LobbyServer/Character/UpgradeCore.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Data;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Character
|
||||
{
|
||||
[PacketPath("/character/coreupgrade")]
|
||||
public class CoreUpgrade : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
// Read the incoming request that contains the current CSN and ISN
|
||||
ReqCharacterCoreUpgrade req = await ReadData<ReqCharacterCoreUpgrade>(); // Contains csn and isn (read-only)
|
||||
ResCharacterCoreUpgrade response = new();
|
||||
|
||||
User user = GetUser();
|
||||
|
||||
// Get all character data from the game's character table
|
||||
List<CharacterRecord> fullchardata = [.. GameData.Instance.CharacterTable.Values];
|
||||
|
||||
CharacterModel targetCharacter = user.GetCharacterBySerialNumber(req.Csn) ?? throw new NullReferenceException();
|
||||
|
||||
// Find the element with the current csn from the request
|
||||
CharacterRecord? currentCharacter = fullchardata.FirstOrDefault(c => c.Id == targetCharacter.Tid);
|
||||
|
||||
if (currentCharacter != null && targetCharacter != null)
|
||||
{
|
||||
if (currentCharacter.GradeCoreId == 103 || currentCharacter.GradeCoreId == 11 || currentCharacter.GradeCoreId == 201)
|
||||
{
|
||||
Console.WriteLine("warning: cannot upgrade code any further!");
|
||||
await WriteDataAsync(response);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find a new CSN based on the `NameCode` of the current character and `GradeCoreId + 1`
|
||||
// For some reason, there is a seperate character for each limit/core break value.
|
||||
CharacterRecord? newCharacter = fullchardata.FirstOrDefault(c => c.NameCode == currentCharacter.NameCode && c.GradeCoreId == currentCharacter.GradeCoreId + 1);
|
||||
|
||||
|
||||
if (newCharacter != null)
|
||||
{
|
||||
// replace character in DB with new character
|
||||
targetCharacter.Grade++;
|
||||
targetCharacter.Tid = newCharacter.Id;
|
||||
|
||||
response.Character = new NetUserCharacterDefaultData()
|
||||
{
|
||||
Csn = req.Csn,
|
||||
CostumeId = targetCharacter.CostumeId,
|
||||
Grade = targetCharacter.Grade,
|
||||
Lv = user.GetSynchroLevel(),
|
||||
Skill1Lv = targetCharacter.Skill1Lvl,
|
||||
Skill2Lv = targetCharacter.Skill2Lvl,
|
||||
Tid = targetCharacter.Tid,
|
||||
UltiSkillLv = targetCharacter.UltimateLevel
|
||||
};
|
||||
|
||||
// remove spare body item
|
||||
ItemData bodyItem = user.Items.FirstOrDefault(i => i.Isn == req.Isn) ?? throw new NullReferenceException();
|
||||
user.RemoveItemBySerialNumber(req.Isn, 1);
|
||||
response.Items.Add(NetUtils.ToNet(bodyItem));
|
||||
|
||||
if (newCharacter.GradeCoreId == 103 || newCharacter.GradeCoreId == 11 || newCharacter.GradeCoreId == 201)
|
||||
{
|
||||
user.AddTrigger(Trigger.CharacterGradeMax, 1);
|
||||
}
|
||||
|
||||
JsonDb.Save();
|
||||
}
|
||||
}
|
||||
|
||||
// Send the response back to the client
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
19
EpinelPS/LobbyServer/Client/CheckClientVersion.cs
Normal file
19
EpinelPS/LobbyServer/Client/CheckClientVersion.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Client
|
||||
{
|
||||
[PacketPath("/system/checkversion")]
|
||||
public class CheckClientVersion : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqCheckClientVersion req = await ReadData<ReqCheckClientVersion>();
|
||||
ResCheckClientVersion response = new()
|
||||
{
|
||||
Availability = ResCheckClientVersion.Types.Availability.None
|
||||
};
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
47
EpinelPS/LobbyServer/ContentsOpen/GetUnlocked.cs
Normal file
47
EpinelPS/LobbyServer/ContentsOpen/GetUnlocked.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.ContentsOpen
|
||||
{
|
||||
[PacketPath("/contentsopen/get/unlock")]
|
||||
public class GetUnlocked : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetContentsOpenUnlockInfo req = await ReadData<ReqGetContentsOpenUnlockInfo>();
|
||||
User user = GetUser();
|
||||
|
||||
// This request is used for showing the "Collection Item Unlocked" Popup and button unlock animation
|
||||
|
||||
ResGetContentsOpenUnlockInfo response = new();
|
||||
|
||||
if (user.ContentsOpenUnlocked.Count == 0)
|
||||
{
|
||||
// These Always returned as true by official server
|
||||
// Fixes "Recruitment unlocked" during chapter 0
|
||||
// TODO: Don't hardcode this, maybe its in GameData
|
||||
|
||||
user.ContentsOpenUnlocked.Add(3, new(true, true));
|
||||
user.ContentsOpenUnlocked.Add(4, new(true, true));
|
||||
user.ContentsOpenUnlocked.Add(6, new(true, true));
|
||||
user.ContentsOpenUnlocked.Add(15, new(true, true));
|
||||
user.ContentsOpenUnlocked.Add(16, new(true, true));
|
||||
user.ContentsOpenUnlocked.Add(18, new(true, true));
|
||||
user.ContentsOpenUnlocked.Add(19, new(true, true));
|
||||
JsonDb.Save();
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<int, UnlockData> item in user.ContentsOpenUnlocked)
|
||||
{
|
||||
response.ContentsOpenUnlockInfoList.Add(new NetContentsOpenUnlockInfo()
|
||||
{
|
||||
ContentsOpenTableId = item.Key,
|
||||
IsUnlockButtonPlayed = item.Value.ButtonAnimationPlayed,
|
||||
IsUnlockPopupPlayed = item.Value.PopupAnimationPlayed,
|
||||
});
|
||||
}
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
38
EpinelPS/LobbyServer/ContentsOpen/SetUnlockButton.cs
Normal file
38
EpinelPS/LobbyServer/ContentsOpen/SetUnlockButton.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.ContentsOpen
|
||||
{
|
||||
[PacketPath("/contentsopen/set/unlock/button")]
|
||||
public class SetUnlockButton : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqSetContentsOpenUnlockButtonPlay req = await ReadData<ReqSetContentsOpenUnlockButtonPlay>();
|
||||
User user = GetUser();
|
||||
|
||||
ResSetContentsOpenUnlockButtonPlay response = new();
|
||||
|
||||
// Unlock button animation completed
|
||||
|
||||
foreach (int item in req.ContentsOpenTableIds)
|
||||
{
|
||||
if (user.ContentsOpenUnlocked.TryGetValue(item, out UnlockData? data))
|
||||
{
|
||||
data.ButtonAnimationPlayed = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
user.ContentsOpenUnlocked.Add(item, new UnlockData()
|
||||
{
|
||||
ButtonAnimationPlayed = true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
JsonDb.Save();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
36
EpinelPS/LobbyServer/ContentsOpen/SetUnlockPopup.cs
Normal file
36
EpinelPS/LobbyServer/ContentsOpen/SetUnlockPopup.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.ContentsOpen
|
||||
{
|
||||
[PacketPath("/contentsopen/set/unlock/popup")]
|
||||
public class SetUnlockPopup : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqSetContentsOpenUnlockPopupPlay req = await ReadData<ReqSetContentsOpenUnlockPopupPlay>();
|
||||
User user = GetUser();
|
||||
|
||||
ResSetContentsOpenUnlockPopupPlay response = new();
|
||||
|
||||
foreach (int item in req.ContentsOpenTableIds)
|
||||
{
|
||||
if (user.ContentsOpenUnlocked.TryGetValue(item, out UnlockData? data))
|
||||
{
|
||||
data.PopupAnimationPlayed = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
user.ContentsOpenUnlocked.Add(item, new UnlockData()
|
||||
{
|
||||
PopupAnimationPlayed = true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
JsonDb.Save();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
16
EpinelPS/LobbyServer/EmptyHandler.cs
Normal file
16
EpinelPS/LobbyServer/EmptyHandler.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer
|
||||
{
|
||||
public class EmptyHandler : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetNow req = await ReadData<ReqGetNow>();
|
||||
ResGetNow response = new();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
19
EpinelPS/LobbyServer/Episode/ListMission.cs
Normal file
19
EpinelPS/LobbyServer/Episode/ListMission.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Episode
|
||||
{
|
||||
[PacketPath("/episode/mission/enter")]
|
||||
public class ListMission : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqListValidEpMission req = await ReadData<ReqListValidEpMission>();
|
||||
|
||||
ResListValidEpMission response = new();
|
||||
|
||||
// TOOD
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
19
EpinelPS/LobbyServer/Event/CheckBookmarkScenarioExists.cs
Normal file
19
EpinelPS/LobbyServer/Event/CheckBookmarkScenarioExists.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event
|
||||
{
|
||||
[PacketPath("/bookmark/event/scenario/exist")]
|
||||
public class CheckBookmarkScenarioExists : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqExistScenarioBookmark req = await ReadData<ReqExistScenarioBookmark>();
|
||||
|
||||
ResExistScenarioBookmark response = new();
|
||||
|
||||
// TODO
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
21
EpinelPS/LobbyServer/Event/CheckScenarioExists.cs
Normal file
21
EpinelPS/LobbyServer/Event/CheckScenarioExists.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event
|
||||
{
|
||||
[PacketPath("/event/scenario/exist")]
|
||||
public class CheckScenarioExists : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqExistEventScenario req = await ReadData<ReqExistEventScenario>();
|
||||
|
||||
ResExistEventScenario response = new();
|
||||
|
||||
foreach (string? item in req.ScenarioGroupIds)
|
||||
response.ExistGroupIds.Add(item);
|
||||
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
20
EpinelPS/LobbyServer/Event/CollectSystem/ListField.cs
Normal file
20
EpinelPS/LobbyServer/Event/CollectSystem/ListField.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event.CollectSystem
|
||||
{
|
||||
[PacketPath("/event/collect-system/list-field")]
|
||||
public class ListField : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqListFieldEventCollectData req = await ReadData<ReqListFieldEventCollectData>();
|
||||
User user = GetUser();
|
||||
|
||||
ResListFieldEventCollectData response = new();
|
||||
|
||||
// TODO
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
32
EpinelPS/LobbyServer/Event/CompleteEventScenario.cs
Normal file
32
EpinelPS/LobbyServer/Event/CompleteEventScenario.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event
|
||||
{
|
||||
[PacketPath("/event/scenario/complete")]
|
||||
public class CompleteEventScenario : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqSetEventScenarioComplete req = await ReadData<ReqSetEventScenarioComplete>();
|
||||
User user = GetUser();
|
||||
|
||||
if (user.EventInfo.TryGetValue(req.EventId, out EventData? evt))
|
||||
{
|
||||
evt.CompletedScenarios.Add(req.ScenarioId);
|
||||
}
|
||||
else
|
||||
{
|
||||
evt = new();
|
||||
evt.CompletedScenarios.Add(req.ScenarioId);
|
||||
user.EventInfo.Add(req.EventId, evt);
|
||||
}
|
||||
|
||||
ResSetEventScenarioComplete response = new();
|
||||
|
||||
// TODO reward
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
49
EpinelPS/LobbyServer/Event/EnterEventField.cs
Normal file
49
EpinelPS/LobbyServer/Event/EnterEventField.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event
|
||||
{
|
||||
[PacketPath("/eventfield/enter")]
|
||||
public class EnterEventField : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqEnterEventField req = await ReadData<ReqEnterEventField>();
|
||||
User user = GetUser();
|
||||
|
||||
ResEnterEventField response = new()
|
||||
{
|
||||
Field = new(),
|
||||
Json = "{}"
|
||||
};
|
||||
|
||||
|
||||
|
||||
// Retrieve collected objects
|
||||
|
||||
if (!user.FieldInfoNew.TryGetValue(req.MapId, out FieldInfoNew? field))
|
||||
{
|
||||
field = new FieldInfoNew();
|
||||
user.FieldInfoNew.Add(req.MapId, field);
|
||||
}
|
||||
|
||||
foreach (int stage in field.CompletedStages)
|
||||
{
|
||||
response.Field.Stages.Add(new NetFieldStageData() { StageId = stage });
|
||||
}
|
||||
foreach (NetFieldObject obj in field.CompletedObjects)
|
||||
{
|
||||
response.Field.Objects.Add(obj);
|
||||
}
|
||||
|
||||
|
||||
// Retrieve camera data
|
||||
if (user.MapJson.TryGetValue(req.MapId, out string? mapJson))
|
||||
{
|
||||
response.Json = mapJson;
|
||||
}
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
41
EpinelPS/LobbyServer/Event/EventLoginGet.cs
Normal file
41
EpinelPS/LobbyServer/Event/EventLoginGet.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event
|
||||
{
|
||||
[PacketPath("/event/login/get")]
|
||||
public class EventLoginGet : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqLoginEventData req = await ReadData<ReqLoginEventData>();
|
||||
int evId = req.EventId;
|
||||
ResLoginEventData response = new()
|
||||
{
|
||||
EndDate = DateTime.Now.AddDays(13).Ticks,
|
||||
DisableDate = DateTime.Now.AddDays(13).Ticks,
|
||||
LastAttendance = new LoginEventAttendance
|
||||
{
|
||||
Day = 14, // Example day value, adjust as needed
|
||||
AttendanceDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks // Assign Ticks here
|
||||
}
|
||||
}; // fields "EndDate", "DisableDate", "RewardHistories", "LastAttendance"
|
||||
response.RewardHistories.Add(new LoginEventRewardHistory() { IsReceived = true, Day = 1 } );
|
||||
response.RewardHistories.Add(new LoginEventRewardHistory() { IsReceived = true, Day = 2 } );
|
||||
response.RewardHistories.Add(new LoginEventRewardHistory() { IsReceived = true, Day = 3 } );
|
||||
response.RewardHistories.Add(new LoginEventRewardHistory() { IsReceived = true, Day = 4 } );
|
||||
response.RewardHistories.Add(new LoginEventRewardHistory() { IsReceived = true, Day = 5 } );
|
||||
response.RewardHistories.Add(new LoginEventRewardHistory() { IsReceived = true, Day = 6 } );
|
||||
response.RewardHistories.Add(new LoginEventRewardHistory() { IsReceived = true, Day = 7 } );
|
||||
response.RewardHistories.Add(new LoginEventRewardHistory() { IsReceived = true, Day = 8 } );
|
||||
response.RewardHistories.Add(new LoginEventRewardHistory() { IsReceived = true, Day = 9 } );
|
||||
response.RewardHistories.Add(new LoginEventRewardHistory() { IsReceived = true, Day = 10 } );
|
||||
response.RewardHistories.Add(new LoginEventRewardHistory() { IsReceived = true, Day = 11 } );
|
||||
response.RewardHistories.Add(new LoginEventRewardHistory() { IsReceived = true, Day = 12 } );
|
||||
response.RewardHistories.Add(new LoginEventRewardHistory() { IsReceived = true, Day = 13 } );
|
||||
response.RewardHistories.Add(new LoginEventRewardHistory() { IsReceived = true, Day = 14 } );
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
57
EpinelPS/LobbyServer/Event/EventTypes.cs
Normal file
57
EpinelPS/LobbyServer/Event/EventTypes.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
namespace EpinelPS.LobbyServer.Event
|
||||
{
|
||||
public enum EventType : int
|
||||
{
|
||||
None = 0,
|
||||
DailyMissionEvent = 1,
|
||||
LoginEvent = 2,
|
||||
ViewShortCut = 3,
|
||||
CooperationEvent = 4,
|
||||
StoryEvent = 5,
|
||||
PickupGachaEvent = 6,
|
||||
PollEvent = 7,
|
||||
ComeBackUserEvent = 8,
|
||||
EventPass = 9,
|
||||
FieldHubEvent = 10,
|
||||
ShopEvent = 11,
|
||||
MissionEvent = 12,
|
||||
ChargeGachaEvent = 13,
|
||||
MiniGameSortOut = 14,
|
||||
CharacterSkillResetEvent = 15,
|
||||
EventQuest = 16,
|
||||
RewardUpEvent = 17,
|
||||
SDBattleEvent = 18,
|
||||
TextAdventure = 19,
|
||||
ChallengeModeEvent = 20,
|
||||
DailyFreeGachaEvent = 21,
|
||||
BoxGachaEvent = 22,
|
||||
DiceEvent = 23,
|
||||
BBQTycoon = 24,
|
||||
CE002MiniGame = 25,
|
||||
TriggerMissionEventReward = 26,
|
||||
ArenaRookieGroupShuffle = 27,
|
||||
ArenaSpecialGroupShuffle = 28,
|
||||
NKSMiniGame = 29,
|
||||
DatingSimulator = 30,
|
||||
DessertRush = 31,
|
||||
CE003MiniGame = 32,
|
||||
TowerDefense = 33,
|
||||
EventPlaySoda = 34,
|
||||
IslandAdventure = 35,
|
||||
MiniGameDD = 36,
|
||||
CE004MiniGame = 37,
|
||||
MVGMiniGame = 38,
|
||||
DragonDungeonRunMiniGame = 39,
|
||||
NewPlayerLottery = 40,
|
||||
PirateCafe = 41,
|
||||
CEEvaMiniGame = 42,
|
||||
BubbleMarchMiniGame = 43,
|
||||
CE006BossChallengeMiniGame = 44,
|
||||
SupportCharacterEvent = 45,
|
||||
GachaBoard = 46,
|
||||
FreeRewardPass = 47,
|
||||
GachaPayback = 48,
|
||||
FieldCollectEvent = 49,
|
||||
MiniGameBTG = 50
|
||||
}
|
||||
}
|
||||
20
EpinelPS/LobbyServer/Event/Field/ListPasswordDoor.cs
Normal file
20
EpinelPS/LobbyServer/Event/Field/ListPasswordDoor.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event.Field
|
||||
{
|
||||
[PacketPath("/event/field/password-door/list")]
|
||||
public class ListPasswordDoor : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqListFieldPasswordDoorData req = await ReadData<ReqListFieldPasswordDoorData>();
|
||||
User user = GetUser();
|
||||
|
||||
ResListFieldPasswordDoorData response = new();
|
||||
|
||||
// TODO
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
24
EpinelPS/LobbyServer/Event/GetChallengeStage.cs
Normal file
24
EpinelPS/LobbyServer/Event/GetChallengeStage.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event
|
||||
{
|
||||
[PacketPath("/event/challengestage/get")]
|
||||
public class GetChallengeStage : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqChallengeEventStageData req = await ReadData<ReqChallengeEventStageData>();
|
||||
User user = GetUser();
|
||||
|
||||
ResChallengeEventStageData response = new()
|
||||
{
|
||||
RemainTicket = 3,
|
||||
TeamData = user.UserTeams[1]
|
||||
};
|
||||
|
||||
// TODO implement properly
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
20
EpinelPS/LobbyServer/Event/GetClearedMission.cs
Normal file
20
EpinelPS/LobbyServer/Event/GetClearedMission.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event
|
||||
{
|
||||
[PacketPath("/event/mission/getclear")]
|
||||
public class GetClearedMissions : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetEventMissionClear req = await ReadData<ReqGetEventMissionClear>(); //has EventIdList
|
||||
|
||||
|
||||
ResGetEventMissionClear response = new();
|
||||
// response.ResGetEventMissionClear.Add(new NetEventMissionClearData(EventId = 0, EventMissionId = 0 , CreatedAt = 0));
|
||||
|
||||
// TODO
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
31
EpinelPS/LobbyServer/Event/GetEventScenario.cs
Normal file
31
EpinelPS/LobbyServer/Event/GetEventScenario.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Data; // For GameData access
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event
|
||||
{
|
||||
[PacketPath("/event/scenario/get")]
|
||||
public class GetEventScenario : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetEventScenarioData req = await ReadData<ReqGetEventScenarioData>();
|
||||
User user = GetUser();
|
||||
|
||||
ResGetEventScenarioData response = new();
|
||||
|
||||
/*
|
||||
if (user.EventInfo.TryGetValue(req.EventId, out EventData? data))
|
||||
{
|
||||
response.ScenarioIdList.AddRange(data.CompletedScenarios);
|
||||
}
|
||||
*/
|
||||
|
||||
// Get all scenario_group_Id values from albumResourceRecords starting with "event_"
|
||||
response.ScenarioIdList.Add(GameData.Instance.albumResourceRecords.Values.Where(record => record.ScenarioGroupId.StartsWith("event_")).Select(record => record.ScenarioGroupId).ToList());
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
144
EpinelPS/LobbyServer/Event/GetJoinedEvent.cs
Normal file
144
EpinelPS/LobbyServer/Event/GetJoinedEvent.cs
Normal file
@@ -0,0 +1,144 @@
|
||||
using EpinelPS.Utils;
|
||||
namespace EpinelPS.LobbyServer.Event
|
||||
{
|
||||
[PacketPath("/event/getjoinedevent")]
|
||||
public class GetJoinedEvent : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetJoinedEvent req = await ReadData<ReqGetJoinedEvent>();
|
||||
//types are defined in EventTypes.cs
|
||||
ResGetJoinedEvent response = new();
|
||||
|
||||
response.EventWithJoinData.Add(new NetEventWithJoinData()
|
||||
{
|
||||
EventData = new NetEventData()
|
||||
{
|
||||
Id = 20001,
|
||||
EventSystemType = (int)EventType.PickupGachaEvent,
|
||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(7)).Ticks,
|
||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventEndDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks
|
||||
},
|
||||
JoinAt = 0
|
||||
});
|
||||
|
||||
// ssr rapi
|
||||
response.EventWithJoinData.Add(new NetEventWithJoinData()
|
||||
{
|
||||
EventData = new NetEventData()
|
||||
{
|
||||
Id = 70077,
|
||||
EventSystemType = (int)EventType.PickupGachaEvent,
|
||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(7)).Ticks,
|
||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventEndDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks
|
||||
},
|
||||
JoinAt = 0
|
||||
});
|
||||
response.EventWithJoinData.Add(new NetEventWithJoinData()
|
||||
{
|
||||
EventData = new NetEventData()
|
||||
{
|
||||
Id = 10046,
|
||||
EventSystemType = (int)EventType.LoginEvent,
|
||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventEndDate = DateTime.Now.AddDays(20).Ticks
|
||||
},
|
||||
JoinAt = 0
|
||||
});
|
||||
response.EventWithJoinData.Add(new NetEventWithJoinData()
|
||||
{
|
||||
EventData = new NetEventData()
|
||||
{
|
||||
Id = 40066,
|
||||
EventSystemType = (int)EventType.StoryEvent,
|
||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventEndDate = DateTime.Now.AddDays(20).Ticks
|
||||
},
|
||||
JoinAt = 0
|
||||
});
|
||||
response.EventWithJoinData.Add(new NetEventWithJoinData()
|
||||
{
|
||||
EventData = new NetEventData()
|
||||
{
|
||||
Id = 60066,
|
||||
EventSystemType = (int)EventType.ChallengeModeEvent,
|
||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventEndDate = DateTime.Now.AddDays(20).Ticks
|
||||
},
|
||||
JoinAt = 0
|
||||
});
|
||||
response.EventWithJoinData.Add(new NetEventWithJoinData()
|
||||
{
|
||||
EventData = new NetEventData()
|
||||
{
|
||||
Id = 70078,
|
||||
EventSystemType = (int)EventType.PickupGachaEvent,
|
||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventEndDate = DateTime.Now.AddDays(20).Ticks
|
||||
},
|
||||
JoinAt = 0
|
||||
});
|
||||
response.EventWithJoinData.Add(new NetEventWithJoinData()
|
||||
{
|
||||
EventData = new NetEventData()
|
||||
{
|
||||
Id = 70079,
|
||||
EventSystemType = (int)EventType.PickupGachaEvent,
|
||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventEndDate = DateTime.Now.AddDays(20).Ticks
|
||||
},
|
||||
JoinAt = 0
|
||||
});
|
||||
//full burst day
|
||||
|
||||
/*
|
||||
response.EventWithJoinData.Add(new NetEventWithJoinData()
|
||||
{
|
||||
EventData = new NetEventData()
|
||||
{
|
||||
Id = 140052,
|
||||
EventSystemType = RewardUpEvent,
|
||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventEndDate = DateTime.Now.AddDays(20).Ticks
|
||||
},
|
||||
JoinAt = 0
|
||||
});
|
||||
*/
|
||||
|
||||
//dailies reward up
|
||||
|
||||
/*
|
||||
response.EventWithJoinData.Add(new NetEventWithJoinData()
|
||||
{
|
||||
EventData = new NetEventData()
|
||||
{
|
||||
Id = 170017,
|
||||
EventSystemType = TriggerMissionEventReward,
|
||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventEndDate = DateTime.Now.AddDays(20).Ticks
|
||||
},
|
||||
JoinAt = 0
|
||||
});
|
||||
*/
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,108 +1,92 @@
|
||||
using nksrv.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace nksrv.LobbyServer.Msgs.Event
|
||||
namespace EpinelPS.LobbyServer.Event
|
||||
{
|
||||
[PacketPath("/event/list")]
|
||||
public class ListEvents : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
var req = await ReadData<ReqGetEventList>();
|
||||
ReqGetEventList req = await ReadData<ReqGetEventList>();
|
||||
|
||||
var response = new ResGetEventList();
|
||||
// types are defined in EventTypes.cs
|
||||
ResGetEventList response = new();
|
||||
|
||||
// Boom! The Ghost! summer event
|
||||
response.EventList.Add(new NetEventData()
|
||||
{
|
||||
Id = 81301,
|
||||
EventDisableDate = 1000000000000000,
|
||||
EventStartDate = 1,
|
||||
EventEndDate = 1000000000000000,
|
||||
EventVisibleDate = 0,
|
||||
EventSystemType = 34
|
||||
});
|
||||
|
||||
|
||||
// TODO: Support events
|
||||
|
||||
response.EventList.Add(new NetEventData()
|
||||
{
|
||||
Id = 20001,
|
||||
EventDisableDate = 1000000000000000,
|
||||
EventStartDate = 1,
|
||||
EventEndDate = 1000000000000000,
|
||||
EventVisibleDate = 0,
|
||||
EventSystemType = 1
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Island Adventure
|
||||
response.EventList.Add(new NetEventData()
|
||||
{
|
||||
Id = 81400,
|
||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventEndDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventSystemType = 10
|
||||
});
|
||||
response.EventList.Add(new NetEventData()
|
||||
{
|
||||
Id = 81401,
|
||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventEndDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventSystemType = 35
|
||||
});
|
||||
response.EventList.Add(new NetEventData()
|
||||
{
|
||||
Id = 81402,
|
||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventEndDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventSystemType = 11
|
||||
});
|
||||
|
||||
response.EventList.Add(new NetEventData()
|
||||
{
|
||||
Id = 81403,
|
||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventEndDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventSystemType = 2
|
||||
});
|
||||
|
||||
response.EventList.Add(new NetEventData()
|
||||
{
|
||||
Id = 160011,
|
||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventEndDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventSystemType = 22
|
||||
});
|
||||
|
||||
// Aegis the Diver event
|
||||
response.EventList.Add(new NetEventData()
|
||||
{
|
||||
Id = 800001,
|
||||
EventSystemType = 36,
|
||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
Id = 82400,
|
||||
EventSystemType = (int)EventType.FieldHubEvent,
|
||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(7)).Ticks,
|
||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventEndDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks
|
||||
});
|
||||
await WriteDataAsync(response);
|
||||
response.EventList.Add(new NetEventData()
|
||||
{
|
||||
Id = 82401,
|
||||
EventSystemType = (int)EventType.MiniGameBTG,
|
||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(7)).Ticks,
|
||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventEndDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks
|
||||
});
|
||||
response.EventList.Add(new NetEventData()
|
||||
{
|
||||
Id = 82402,
|
||||
EventSystemType = (int)EventType.ShopEvent,
|
||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(7)).Ticks,
|
||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventEndDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks
|
||||
});
|
||||
response.EventList.Add(new NetEventData()
|
||||
{
|
||||
Id = 40084,
|
||||
EventSystemType = (int)EventType.StoryEvent,
|
||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(7)).Ticks,
|
||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventEndDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks
|
||||
});
|
||||
response.EventList.Add(new NetEventData()
|
||||
{
|
||||
Id = 40085,
|
||||
EventSystemType = (int)EventType.StoryEvent,
|
||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(7)).Ticks,
|
||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventEndDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks
|
||||
});
|
||||
response.EventList.Add(new NetEventData()
|
||||
{
|
||||
Id = 60084,
|
||||
EventSystemType = (int)EventType.ChallengeModeEvent,
|
||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(7)).Ticks,
|
||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventEndDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks
|
||||
});
|
||||
response.EventList.Add(new NetEventData()
|
||||
{
|
||||
Id = 100032,
|
||||
EventSystemType = (int)EventType.EventPass,
|
||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(7)).Ticks,
|
||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventEndDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks
|
||||
});
|
||||
response.EventList.Add(new NetEventData()
|
||||
{
|
||||
Id = 82403,
|
||||
EventSystemType = (int)EventType.LoginEvent,
|
||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(7)).Ticks,
|
||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventEndDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks
|
||||
});
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
26
EpinelPS/LobbyServer/Event/Minigame/CE002/GetCe002Data.cs
Normal file
26
EpinelPS/LobbyServer/Event/Minigame/CE002/GetCe002Data.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event.Minigame.CE002
|
||||
{
|
||||
[PacketPath("/event/minigame/ce002/get")]
|
||||
public class GetCe002Data : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetMiniGameCe002Data req = await ReadData<ReqGetMiniGameCe002Data>();
|
||||
User user = GetUser();
|
||||
|
||||
ResGetMiniGameCe002Data response = new()
|
||||
{
|
||||
Data = new()
|
||||
{
|
||||
Ce002Id = req.Ce002Id
|
||||
}
|
||||
};
|
||||
|
||||
// TODO implement properly
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
20
EpinelPS/LobbyServer/Event/Minigame/CE006/GetSBStats.cs
Normal file
20
EpinelPS/LobbyServer/Event/Minigame/CE006/GetSBStats.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event.Minigame.CE006
|
||||
{
|
||||
[PacketPath("/event/minigame/stellar-blade/statistics/get")]
|
||||
public class GetSBStats : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetStellarBladeStatistics req = await ReadData<ReqGetStellarBladeStatistics>();
|
||||
User user = GetUser();
|
||||
|
||||
ResGetStellarBladeStatistics response = new();
|
||||
|
||||
// TODO implement
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
19
EpinelPS/LobbyServer/Event/Mission/GetClearList.cs
Normal file
19
EpinelPS/LobbyServer/Event/Mission/GetClearList.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event.Mission
|
||||
{
|
||||
[PacketPath("/event/mission/getclearlist")]
|
||||
public class GetClearList : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetEventMissionClearList req = await ReadData<ReqGetEventMissionClearList>();
|
||||
|
||||
ResGetEventMissionClearList response = new(); //field ResGetEventMissionClearMap data type NestEventMissionClear field NestEventMissionClear data type NetEventMissionClearData fields EventId EventMissionId CreatedAt
|
||||
|
||||
// TOOD
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
24
EpinelPS/LobbyServer/Event/Shop/ListProductList.cs
Normal file
24
EpinelPS/LobbyServer/Event/Shop/ListProductList.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event.Shop
|
||||
{
|
||||
[PacketPath("/event/shopproductlist")]
|
||||
public class ListProductList : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqShopProductList req = await ReadData<ReqShopProductList>();
|
||||
User user = GetUser();
|
||||
|
||||
ResShopProductList response = new();
|
||||
response.Shops.Add(new NetShopProductData()
|
||||
{
|
||||
|
||||
});
|
||||
|
||||
// TODO implement properly
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user