Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c21047024c | ||
|
|
252f1da79f | ||
|
|
77fccdbac5 | ||
|
|
8711be388e | ||
|
|
316fff969e | ||
|
|
c185497502 | ||
|
|
1ab148a50a | ||
|
|
1de4d8c406 | ||
|
|
16d8c25ea0 | ||
|
|
6b0095508d | ||
|
|
e5d2724f8c | ||
|
|
0d1c3e60cd | ||
|
|
a5fea98d59 | ||
|
|
80b69400d5 | ||
|
|
e5a22ece90 | ||
|
|
01710f1857 | ||
|
|
14261c8e29 | ||
|
|
4cd290aedc | ||
|
|
95d9d0be5f | ||
|
|
c2258a5fd9 | ||
|
|
ccdb5d3754 | ||
|
|
01a2f04289 | ||
|
|
df47afcf32 | ||
|
|
82f3ab6d23 | ||
|
|
f3edade533 | ||
|
|
28c5fdc461 | ||
|
|
c8b8a2816e | ||
|
|
d3265dbc02 | ||
|
|
4871c0bf6f | ||
|
|
003f655585 | ||
|
|
7afd41664c | ||
|
|
04be44cff1 | ||
|
|
39c5920dc5 | ||
|
|
b280438819 | ||
|
|
c142516b00 | ||
|
|
c7deb8f970 | ||
|
|
e7a71ea07d | ||
|
|
2f185890e5 | ||
|
|
47cdbb0315 | ||
|
|
4730846674 | ||
|
|
9fad46fb08 | ||
|
|
c3df11958d | ||
|
|
09716e4d4b | ||
|
|
aa0b7787a8 | ||
|
|
5c6cc5228f | ||
|
|
7397102dc0 | ||
|
|
d89065e58a | ||
|
|
8960040008 | ||
|
|
b486b5b36a | ||
|
|
01ba2ea90b | ||
|
|
d04a213e87 | ||
|
|
ebde41d2e7 | ||
|
|
219a90c640 | ||
|
|
7cfc3d7e11 | ||
|
|
1d211d5f15 | ||
|
|
3129170217 | ||
|
|
77f4cfcfbf | ||
|
|
b624ec53d8 | ||
|
|
3a59c639a5 |
@@ -0,0 +1,5 @@
|
||||
version = 1
|
||||
|
||||
[[analyzers]]
|
||||
name = "csharp"
|
||||
enabled = true
|
||||
+2
-1
@@ -439,4 +439,5 @@ FodyWeavers.xsd
|
||||
/db/card.db3
|
||||
|
||||
|
||||
.idea
|
||||
.idea
|
||||
identifier.sqlite
|
||||
@@ -5,6 +5,12 @@ VisualStudioVersion = 16.0.32126.315
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GC-local-server-rewrite", "GC-local-server-rewrite\GC-local-server-rewrite.csproj", "{D5FFF2E8-6591-4967-8883-A28F453F0524}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharedProject", "SharedProject\SharedProject.csproj", "{C01D0576-9D4E-4535-8055-C4905433A1EB}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MudAdmin", "MudAdmin\MudAdmin.csproj", "{DC8E30E9-F81E-4E28-A4D2-F4576C77FFBE}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GCRelayServer", "GCRelayServer\GCRelayServer.csproj", "{268178DF-6345-4D9E-A389-9E809CBF39C9}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -15,6 +21,18 @@ Global
|
||||
{D5FFF2E8-6591-4967-8883-A28F453F0524}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D5FFF2E8-6591-4967-8883-A28F453F0524}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D5FFF2E8-6591-4967-8883-A28F453F0524}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{C01D0576-9D4E-4535-8055-C4905433A1EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C01D0576-9D4E-4535-8055-C4905433A1EB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C01D0576-9D4E-4535-8055-C4905433A1EB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C01D0576-9D4E-4535-8055-C4905433A1EB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{DC8E30E9-F81E-4E28-A4D2-F4576C77FFBE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{DC8E30E9-F81E-4E28-A4D2-F4576C77FFBE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DC8E30E9-F81E-4E28-A4D2-F4576C77FFBE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DC8E30E9-F81E-4E28-A4D2-F4576C77FFBE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{268178DF-6345-4D9E-A389-9E809CBF39C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{268178DF-6345-4D9E-A389-9E809CBF39C9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{268178DF-6345-4D9E-A389-9E809CBF39C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{268178DF-6345-4D9E-A389-9E809CBF39C9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Adlibs/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Keynum/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Touhou/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=unlockable/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Vocaloid/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
@@ -1,6 +1,8 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=ADDR/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=BDATA/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Fcol/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Incom/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Pcol/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Respone/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Respone/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Tenpo/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
@@ -11,22 +11,31 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CertificateManager" Version="1.0.8" />
|
||||
<PackageReference Include="ChoETL" Version="1.2.1.44" />
|
||||
<PackageReference Include="EmbedIO" Version="3.4.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
||||
<PackageReference Include="sqlite-net2" Version="2.0.7" />
|
||||
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.0.7" />
|
||||
<PackageReference Include="ChoETL" Version="1.2.1.47" />
|
||||
<PackageReference Include="ConcurrentHashSet" Version="1.3.0" />
|
||||
<PackageReference Include="Config.Net" Version="4.19.0" />
|
||||
<PackageReference Include="Config.Net.Json" Version="4.19.0" />
|
||||
<PackageReference Include="EmbedIO" Version="3.5.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0-preview.5.22301.12" />
|
||||
<PackageReference Include="sqlite-net2" Version="2.1.0-preB" />
|
||||
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.0" />
|
||||
<PackageReference Include="System.Security.Cryptography.X509Certificates" Version="4.3.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="log" />
|
||||
<Folder Include="event\" />
|
||||
<None Remove="db\music.db3" />
|
||||
<None Remove="db\music4MAX465.db3" />
|
||||
<Content Include="db\music.db3">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
</Content>
|
||||
<None Remove="static\favicon.ico" />
|
||||
<Content Include="db\music4MAX465.db3">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
</Content>
|
||||
<Content Include="static\favicon.ico">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
@@ -42,15 +51,23 @@
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
</Content>
|
||||
<None Remove="GC-local-server-rewrite.exe.config.xml" />
|
||||
<None Include="GC-local-server-rewrite.exe.config">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
</None>
|
||||
<None Remove="db\music4MAX.db3" />
|
||||
<Content Include="db\music4MAX.db3">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
</Content>
|
||||
<None Update="config.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
</None>
|
||||
<None Update="db\music471.db3">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SharedProject\SharedProject.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<appSettings>
|
||||
<add key="AvatarCount" value="294"/>
|
||||
<add key="NavigatorCount" value="71"/>
|
||||
<add key="ItemCount" value="21"/>
|
||||
<add key="SkinCount" value="21"/>
|
||||
<add key="SeCount" value="25"/>
|
||||
<add key="TitleCount" value="4942"/>
|
||||
<add key="MusicDBName" value="music.db3"/>
|
||||
</appSettings>
|
||||
</configuration>
|
||||
@@ -13,10 +13,11 @@ internal class Program
|
||||
Batteries_V2.Init();
|
||||
|
||||
InitializeLogging();
|
||||
|
||||
|
||||
LogConfigValues();
|
||||
|
||||
var urlPrefixes = args.Length > 0 ? args : new[] { "http://*:80", "https://*:443" };
|
||||
var urlPrefixes =
|
||||
args.Length > 0 ? new List<string>(args) : new List<string> { "http://+:80", "https://+:443" };
|
||||
|
||||
using (var cts = new CancellationTokenSource())
|
||||
{
|
||||
@@ -31,7 +32,7 @@ internal class Program
|
||||
Terminal.Flush();
|
||||
|
||||
Console.WriteLine("Press any key to exit.");
|
||||
WaitForKeypress();
|
||||
Console.ReadKey(true);
|
||||
}
|
||||
|
||||
private static void InitializeLogging()
|
||||
@@ -50,7 +51,7 @@ internal class Program
|
||||
/// </summary>
|
||||
/// <param name="urlPrefixes"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
private static async Task RunWebServerAsync(string[] urlPrefixes, CancellationToken cancellationToken)
|
||||
private static async Task RunWebServerAsync(IEnumerable<string> urlPrefixes, CancellationToken cancellationToken)
|
||||
{
|
||||
using var server = Server.CreateWebServer(urlPrefixes);
|
||||
await server.RunAsync(cancellationToken).ConfigureAwait(false);
|
||||
@@ -66,7 +67,7 @@ internal class Program
|
||||
// Be sure to run in parallel.
|
||||
await Task.Yield();
|
||||
|
||||
"Press any key to stop the web server.".Info(nameof(Program));
|
||||
"Press x to stop the web server.".Info(nameof(Program));
|
||||
WaitForKeypress();
|
||||
"Stopping...".Info(nameof(Program));
|
||||
cancel();
|
||||
@@ -77,30 +78,30 @@ internal class Program
|
||||
/// </summary>
|
||||
private static void WaitForKeypress()
|
||||
{
|
||||
while (Console.KeyAvailable)
|
||||
{
|
||||
Console.ReadKey(true);
|
||||
}
|
||||
ConsoleKeyInfo consoleKeyInfo;
|
||||
|
||||
Console.ReadKey(true);
|
||||
do
|
||||
{
|
||||
while (Console.KeyAvailable == false)
|
||||
{
|
||||
Thread.Sleep(250);
|
||||
}
|
||||
|
||||
consoleKeyInfo = Console.ReadKey(true);
|
||||
} while (consoleKeyInfo.Key != ConsoleKey.X);
|
||||
}
|
||||
|
||||
private static void LogConfigValues()
|
||||
{
|
||||
var configs = $"Config values: {nameof(Configs.SE_COUNT)} : {Configs.SE_COUNT}\n" +
|
||||
$"{nameof(Configs.ITEM_COUNT)} : {Configs.ITEM_COUNT}\n" +
|
||||
$"{nameof(Configs.SKIN_COUNT)} : {Configs.SKIN_COUNT}\n" +
|
||||
$"{nameof(Configs.TITLE_COUNT)} : {Configs.TITLE_COUNT}\n" +
|
||||
$"{nameof(Configs.AVATAR_COUNT)} : {Configs.AVATAR_COUNT}\n" +
|
||||
$"{nameof(Configs.NAVIGATOR_COUNT)} : {Configs.NAVIGATOR_COUNT}\n" +
|
||||
$"{nameof(Configs.MUSIC_DB_NAME)} : {Configs.MUSIC_DB_NAME}\n";
|
||||
configs.Info();
|
||||
|
||||
var paths = $"Paths: {nameof(PathHelper.HtmlRootPath)}: {PathHelper.HtmlRootPath}\n" +
|
||||
$"{nameof(PathHelper.LogRootPath)}: {PathHelper.LogRootPath}\n" +
|
||||
$"{nameof(PathHelper.DataBaseRootPath)}: {PathHelper.DataBaseRootPath}\n" +
|
||||
$"{nameof(PathHelper.ConfigFilePath)}: {PathHelper.ConfigFilePath}\n";
|
||||
paths.Info();
|
||||
var paths = $"Paths: {nameof(PathHelper.HtmlRootPath)}: {PathHelper.HtmlRootPath}\n" +
|
||||
$"{nameof(PathHelper.LogRootPath)}: {PathHelper.LogRootPath}\n" +
|
||||
$"{nameof(PathHelper.DataBaseRootPath)}: {PathHelper.DataBaseRootPath}\n" +
|
||||
$"{nameof(PathHelper.ConfigFilePath)}: {PathHelper.ConfigFilePath}\n" +
|
||||
$"{nameof(PathHelper.CertRootPath)}: {PathHelper.CertRootPath}\n";
|
||||
paths.Info();
|
||||
|
||||
var configs = "Config values: \n" +
|
||||
$"{Configs.SETTINGS.Stringify()}";
|
||||
configs.Info();
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
using EmbedIO;
|
||||
|
||||
namespace GCLocalServerRewrite.backports;
|
||||
|
||||
/// <summary>
|
||||
/// Provides custom response serializer callbacks.
|
||||
/// </summary>
|
||||
public static class CustomResponseSerializer
|
||||
{
|
||||
private static readonly ResponseSerializerCallback CHUNKED_ENCODING_BASE_SERIALIZER = GetBaseSerializer(false);
|
||||
private static readonly ResponseSerializerCallback BUFFERING_BASE_SERIALIZER = GetBaseSerializer(true);
|
||||
|
||||
/// <summary>
|
||||
/// Sends data in a HTTP response without serialization.
|
||||
/// </summary>
|
||||
/// <param name="bufferResponse">
|
||||
/// <see langword="true" /> to write the response body to a memory buffer first,
|
||||
/// then send it all together with a <c>Content-Length</c> header; <see langword="false" /> to use chunked
|
||||
/// transfer encoding.
|
||||
/// </param>
|
||||
/// <returns>A <see cref="ResponseSerializerCallback" /> that can be used to serialize data to a HTTP response.</returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <see cref="string" />s and one-dimensional arrays of <see cref="byte" />s
|
||||
/// are sent to the client unchanged; every other type is converted to a string.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The <see cref="IHttpResponse.ContentType">ContentType</see> set on the response is used to negotiate
|
||||
/// a compression method, according to request headers.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Strings (and other types converted to strings) are sent with the encoding specified by
|
||||
/// <see cref="IHttpResponse.ContentEncoding" />.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static ResponseSerializerCallback None(bool bufferResponse)
|
||||
{
|
||||
return bufferResponse ? BUFFERING_BASE_SERIALIZER : CHUNKED_ENCODING_BASE_SERIALIZER;
|
||||
}
|
||||
|
||||
private static ResponseSerializerCallback GetBaseSerializer(bool bufferResponse)
|
||||
{
|
||||
return async (context, data) =>
|
||||
{
|
||||
if (data is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var isBinaryResponse = data is byte[];
|
||||
|
||||
if (!context.TryDetermineCompression(context.Response.ContentType, out var preferCompression))
|
||||
{
|
||||
preferCompression = true;
|
||||
}
|
||||
|
||||
preferCompression = false;
|
||||
|
||||
if (isBinaryResponse)
|
||||
{
|
||||
var responseBytes = (byte[])data;
|
||||
using var stream = context.OpenResponseStream(bufferResponse, preferCompression);
|
||||
await stream.WriteAsync(responseBytes).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
var responseString = data is string stringData ? stringData : data.ToString() ?? string.Empty;
|
||||
await using var text = context.OpenResponseText(context.Response.ContentEncoding, bufferResponse,
|
||||
preferCompression);
|
||||
await text.WriteAsync(responseString).ConfigureAwait(false);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,9 @@ public static class CertificateHelper
|
||||
X509KeyUsageFlags.KeyEncipherment |
|
||||
X509KeyUsageFlags.DigitalSignature;
|
||||
|
||||
private const X509KeyStorageFlags X509_KEY_STORAGE_FLAGS_MACHINE = X509KeyStorageFlags.PersistKeySet
|
||||
| X509KeyStorageFlags.MachineKeySet;
|
||||
private const X509KeyStorageFlags X509_KEY_STORAGE_FLAGS_MACHINE = X509KeyStorageFlags.PersistKeySet |
|
||||
X509KeyStorageFlags.MachineKeySet |
|
||||
X509KeyStorageFlags.Exportable;
|
||||
|
||||
private const X509KeyUsageFlags CERT_X509_KEY_USAGE_FLAGS = X509KeyUsageFlags.DataEncipherment |
|
||||
X509KeyUsageFlags.KeyEncipherment |
|
||||
@@ -50,10 +51,8 @@ public static class CertificateHelper
|
||||
|
||||
private static readonly SubjectAlternativeName SUBJECT_ALTERNATIVE_NAME = new()
|
||||
{
|
||||
DnsName = new List<string>
|
||||
{
|
||||
"localhost"
|
||||
}
|
||||
DnsName = Configs.DOMAINS,
|
||||
IpAddress = System.Net.IPAddress.Parse(Configs.SETTINGS.ServerIp)
|
||||
};
|
||||
|
||||
private static readonly ValidityPeriod VALIDITY_PERIOD = new()
|
||||
@@ -77,6 +76,7 @@ public static class CertificateHelper
|
||||
{
|
||||
return existingCert;
|
||||
}
|
||||
"Existing CN not found! Removing old certificates and genrate new ones...".Info();
|
||||
}
|
||||
|
||||
RemovePreviousCert(StoreName.My, StoreLocation.LocalMachine);
|
||||
@@ -129,10 +129,16 @@ public static class CertificateHelper
|
||||
var certWithPrivateKey = new X509Certificate2(certPfxBytes, (string)null!,
|
||||
X509_KEY_STORAGE_FLAGS_MACHINE);
|
||||
|
||||
|
||||
AddCertToStore(rootCaWithPrivateKey, StoreName.My, StoreLocation.LocalMachine);
|
||||
AddCertToStore(rootCaWithPrivateKey, StoreName.Root, StoreLocation.LocalMachine);
|
||||
AddCertToStore(certWithPrivateKey, StoreName.My, StoreLocation.LocalMachine);
|
||||
|
||||
Directory.CreateDirectory(PathHelper.CertRootPath);
|
||||
|
||||
File.WriteAllBytes(Path.Combine(PathHelper.CertRootPath, "root.pfx"), rootCaWithPrivateKey.Export(X509ContentType.Pfx));
|
||||
File.WriteAllBytes(Path.Combine(PathHelper.CertRootPath, "cert.pfx"), certWithPrivateKey.Export(X509ContentType.Pfx));
|
||||
|
||||
return certWithPrivateKey;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.Configuration;
|
||||
using Swan;
|
||||
using Swan.Logging;
|
||||
using Config.Net;
|
||||
|
||||
namespace GCLocalServerRewrite.common;
|
||||
|
||||
@@ -16,11 +14,19 @@ public static class Configs
|
||||
|
||||
public const string LOG_FOLDER = "log";
|
||||
|
||||
public const string CERT_FOLDER = "certs";
|
||||
|
||||
public const string LOG_BASE_NAME = "log";
|
||||
|
||||
public const string CONFIG_FILE_NAME = "config.json";
|
||||
|
||||
public const string STATIC_FOLDER = "static";
|
||||
|
||||
public const string CONFIG_FILE_NAME = "App.config";
|
||||
public const string WWWROOT = "wwwroot";
|
||||
|
||||
public const string API_BASE_ROUTE = "/api";
|
||||
|
||||
public const string OPTION_SERVICE_BASE_ROUTE = "/service/option";
|
||||
|
||||
public const string CARD_SERVICE_BASE_ROUTE = "/service/card";
|
||||
|
||||
@@ -30,6 +36,8 @@ public static class Configs
|
||||
|
||||
public const string INCOM_SERVICE_BASE_ROUTE = "/service/incom";
|
||||
|
||||
public const string UPDATE_SERVICE_BASE_ROUTE = "/update/cgi";
|
||||
|
||||
public const string RANK_BASE_ROUTE = "/ranking";
|
||||
|
||||
public const string ALIVE_BASE_ROUTE = "/alive";
|
||||
@@ -38,8 +46,6 @@ public static class Configs
|
||||
|
||||
public const string STATIC_BASE_ROUTE = "/static";
|
||||
|
||||
public const string CARD_DB_NAME = "card.db3";
|
||||
|
||||
public const string ROOT_XPATH = "/root";
|
||||
|
||||
public const string DATA_XPATH = $"{ROOT_XPATH}/data";
|
||||
@@ -68,7 +74,7 @@ public static class Configs
|
||||
|
||||
public const string MUSIC_AOU = "music_aou";
|
||||
|
||||
public const string MUSIC_AOU_XPATH = $"{ROOT_XPATH}/{MUSIC_AOU}";
|
||||
public const string MUSIC_AOU_XPATH = $"{ROOT_XPATH}/{MUSIC_AOU}/record";
|
||||
|
||||
public const string ITEM = "item";
|
||||
|
||||
@@ -119,64 +125,50 @@ public static class Configs
|
||||
public const string SESSION_XPATH = $"{ROOT_XPATH}/session";
|
||||
|
||||
public const string RANK_STATUS_XPATH = $"{ROOT_XPATH}/ranking_status";
|
||||
|
||||
public const string ONLINE_MATCHING_XPATH = $"{ROOT_XPATH}/online_matching/record";
|
||||
|
||||
public const string ONLINE_BATTLE_RESULT_XPATH = $"{ROOT_XPATH}/online_battle_result";
|
||||
|
||||
public const int FIRST_CONFIG_PCOL1 = 0;
|
||||
public const int SECOND_CONFIG_PCOL1 = 1;
|
||||
public const int CONFIG_PCOL2 = 0;
|
||||
public const int CONFIG_PCOL3 = 0;
|
||||
|
||||
public const int GC4_EX_GID = 303801;
|
||||
|
||||
public static readonly int AVATAR_COUNT;
|
||||
public const int FAVORITE_PCOL1 = 10;
|
||||
|
||||
public static readonly int NAVIGATOR_COUNT;
|
||||
public const int COUNT_PCOL1 = 20;
|
||||
|
||||
public static readonly int ITEM_COUNT;
|
||||
public const int SCORE_PCOL1 = 21;
|
||||
|
||||
public static readonly int TITLE_COUNT;
|
||||
|
||||
public static readonly int SKIN_COUNT;
|
||||
|
||||
public static readonly int SE_COUNT;
|
||||
|
||||
public static readonly string MUSIC_DB_NAME;
|
||||
|
||||
private static readonly Configuration CONFIG = ConfigurationManager.OpenExeConfiguration(PathHelper.ConfigFilePath);
|
||||
|
||||
private const string AVATAR_COUNT_CONFIG_NAME = "AvatarCount";
|
||||
private const string NAVIGATOR_COUNT_CONFIG_NAME = "NavigatorCount";
|
||||
private const string ITEM_COUNT_CONFIG_NAME = "ItemCount";
|
||||
private const string TITLE_COUNT_CONFIG_NAME = "TitleCount";
|
||||
private const string SKIN_COUNT_CONFIG_NAME = "SkinCount";
|
||||
private const string SE_COUNT_CONFIG_NAME = "SeCount";
|
||||
private const string MUSIC_DB_CONFIG_NAME = "MusicDBName";
|
||||
|
||||
private const int AVATAR_DEFAULT_COUNT = 294;
|
||||
private const int NAVIGATOR_DEFAULT_COUNT = 71;
|
||||
private const int ITEM_DEFAULT_COUNT = 21;
|
||||
private const int TITLE_DEFAULT_COUNT = 4942;
|
||||
private const int SKIN_DEFAULT_COUNT = 21;
|
||||
private const int SE_DEFAULT_COUNT = 25;
|
||||
private const string MUSIC_DB_DEFAULT_NAME = "music.db3";
|
||||
|
||||
|
||||
static Configs()
|
||||
public static readonly List<string> DOMAINS = new()
|
||||
{
|
||||
void GetIntValueFromConfig(string configFieldName, int defaultValue, out int field )
|
||||
{
|
||||
var success = int.TryParse(CONFIG.AppSettings.Settings[configFieldName].Value, out var count);
|
||||
"localhost",
|
||||
"cert.nesys.jp",
|
||||
"nesys.taito.co.jp",
|
||||
"fjm170920zero.nesica.net"
|
||||
};
|
||||
|
||||
field = success ? count:defaultValue;
|
||||
}
|
||||
|
||||
GetIntValueFromConfig(AVATAR_COUNT_CONFIG_NAME, AVATAR_DEFAULT_COUNT, out AVATAR_COUNT);
|
||||
GetIntValueFromConfig(NAVIGATOR_COUNT_CONFIG_NAME, NAVIGATOR_DEFAULT_COUNT, out NAVIGATOR_COUNT);
|
||||
GetIntValueFromConfig(ITEM_COUNT_CONFIG_NAME, ITEM_DEFAULT_COUNT, out ITEM_COUNT);
|
||||
GetIntValueFromConfig(TITLE_COUNT_CONFIG_NAME, TITLE_DEFAULT_COUNT, out TITLE_COUNT);
|
||||
GetIntValueFromConfig(SKIN_COUNT_CONFIG_NAME, SKIN_DEFAULT_COUNT, out SKIN_COUNT);
|
||||
GetIntValueFromConfig(SE_COUNT_CONFIG_NAME, SE_DEFAULT_COUNT, out SE_COUNT);
|
||||
public static readonly IAppSettings SETTINGS =
|
||||
new ConfigurationBuilder<IAppSettings>().UseJsonFile(PathHelper.ConfigFilePath).Build();
|
||||
|
||||
MUSIC_DB_NAME = CONFIG.AppSettings.Settings[MUSIC_DB_CONFIG_NAME].Value;
|
||||
public const int DEFAULT_AVATAR_COUNT = 323;
|
||||
public const int DEFAULT_NAVIGATOR_COUNT = 94;
|
||||
public const int DEFAULT_ITEM_COUNT = 21;
|
||||
public const int DEFAULT_TITLE_COUNT = 5273;
|
||||
public const int DEFAULT_SKIN_COUNT = 21;
|
||||
public const int DEFAULT_SE_COUNT = 26;
|
||||
public const string DEFAULT_CARD_DB_NAME = "card.db3";
|
||||
public const string DEFAULT_MUSIC_DB_NAME = "music4MAX465.db3";
|
||||
public const string DEFAULT_SERVER_IP = "127.0.0.1";
|
||||
public const string DEFAULT_RELAY_SERVER = "127.0.0.1";
|
||||
public const int DEFAULT_RELAY_PORT = 54321;
|
||||
public const string DEFAULT_EVENT_FOLDER = "event";
|
||||
|
||||
if (MUSIC_DB_NAME == string.Empty)
|
||||
{
|
||||
MUSIC_DB_NAME = MUSIC_DB_DEFAULT_NAME;
|
||||
}
|
||||
|
||||
}
|
||||
public static readonly IReadOnlyList<int> DEFAULT_UNLOCKABLE_SONGS = new[]
|
||||
{
|
||||
11, 13, 149, 273, 291, 320, 321, 371, 378, 384, 464, 471, 474, 475, 492, 494, 498, 520,
|
||||
548, 551, 558, 561, 565, 570, 577, 583, 612, 615, 622, 632, 659, 666, 668, 670, 672, 676,
|
||||
680, 682, 685, 686, 697, 700, 701, 711, 720, 749, 875, 876, 877
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using Config.Net;
|
||||
|
||||
namespace GCLocalServerRewrite.common;
|
||||
|
||||
public interface IAppSettings
|
||||
{
|
||||
[Option(DefaultValue = Configs.DEFAULT_AVATAR_COUNT)]
|
||||
int AvatarCount { get; }
|
||||
|
||||
[Option(DefaultValue = Configs.DEFAULT_NAVIGATOR_COUNT)]
|
||||
int NavigatorCount { get; }
|
||||
|
||||
[Option(DefaultValue = Configs.DEFAULT_ITEM_COUNT)]
|
||||
int ItemCount { get; }
|
||||
|
||||
[Option(DefaultValue = Configs.DEFAULT_TITLE_COUNT)]
|
||||
int TitleCount { get; }
|
||||
|
||||
[Option(DefaultValue = Configs.DEFAULT_SKIN_COUNT)]
|
||||
int SkinCount { get; }
|
||||
|
||||
[Option(DefaultValue = Configs.DEFAULT_SE_COUNT)]
|
||||
int SeCount { get; }
|
||||
|
||||
[Option(DefaultValue = Configs.DEFAULT_MUSIC_DB_NAME)]
|
||||
string MusicDbName { get; }
|
||||
|
||||
[Option(DefaultValue = Configs.DEFAULT_CARD_DB_NAME)]
|
||||
string CardDbName { get; }
|
||||
|
||||
[Option(DefaultValue = Configs.DEFAULT_SERVER_IP)]
|
||||
string ServerIp { get; }
|
||||
|
||||
[Option(DefaultValue = Configs.DEFAULT_EVENT_FOLDER)]
|
||||
string EventFolder { get; }
|
||||
|
||||
[Option(DefaultValue = Configs.DEFAULT_RELAY_SERVER)]
|
||||
string RelayServer { get; }
|
||||
|
||||
[Option(DefaultValue = Configs.DEFAULT_RELAY_PORT)]
|
||||
int RelayPort { get; }
|
||||
|
||||
[Option(DefaultValue = false)]
|
||||
bool DownloadEvents { get; }
|
||||
|
||||
[Option(DefaultValue = null)]
|
||||
IEnumerable<int>? UnlockableSongIds { get; }
|
||||
|
||||
IEnumerable<IOptionServiceResponse> ResponseData { get; }
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace GCLocalServerRewrite.common;
|
||||
|
||||
public interface IOptionServiceResponse
|
||||
{
|
||||
string FileName { get; }
|
||||
|
||||
long NotBeforeUnixTime { get; }
|
||||
|
||||
long NotAfterUnixTime { get; }
|
||||
|
||||
string Md5 { get; }
|
||||
|
||||
int Index { get; }
|
||||
}
|
||||
@@ -8,7 +8,7 @@ public static class PathHelper
|
||||
/// <summary>
|
||||
/// Gets the local path of html/static files.
|
||||
/// </summary>
|
||||
public static string HtmlRootPath => Path.Combine(BasePath, Configs.STATIC_FOLDER);
|
||||
public static string HtmlRootPath => Path.Combine(BasePath, Configs.STATIC_FOLDER, Configs.WWWROOT);
|
||||
|
||||
/// <summary>
|
||||
/// Root path for database, when debug, it's under source root, when release, it's the exe dir
|
||||
@@ -17,7 +17,9 @@ public static class PathHelper
|
||||
|
||||
public static string LogRootPath => Path.Combine(BasePath, Configs.LOG_FOLDER);
|
||||
|
||||
public static string ConfigFilePath => Environment.ProcessPath ?? string.Empty;
|
||||
public static string CertRootPath => Path.Combine(BasePath, Configs.CERT_FOLDER);
|
||||
|
||||
public static string ConfigFilePath => Path.Combine(BasePath, Configs.CONFIG_FILE_NAME);
|
||||
|
||||
private static string BasePath
|
||||
{
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
{
|
||||
"AvatarCount": 356,
|
||||
"NavigatorCount": 118,
|
||||
"ItemCount": 21,
|
||||
"SkinCount": 21,
|
||||
"SeCount": 26,
|
||||
"TitleCount": 5530,
|
||||
"CardDbName": "card.db3",
|
||||
"MusicDbName": "music471omni.db3",
|
||||
"ServerIp": "127.0.0.1",
|
||||
"EventFolder": "event",
|
||||
"DownloadEvents": false,
|
||||
"RelayServer": "127.0.0.1",
|
||||
"RelayPort": 54321,
|
||||
"UnlockableSongIds": [
|
||||
11,
|
||||
13,
|
||||
149,
|
||||
273,
|
||||
291,
|
||||
320,
|
||||
321,
|
||||
371,
|
||||
378,
|
||||
384,
|
||||
464,
|
||||
471,
|
||||
474,
|
||||
475,
|
||||
492,
|
||||
494,
|
||||
498,
|
||||
520,
|
||||
548,
|
||||
551,
|
||||
558,
|
||||
561,
|
||||
565,
|
||||
570,
|
||||
577,
|
||||
583,
|
||||
612,
|
||||
615,
|
||||
622,
|
||||
632,
|
||||
659,
|
||||
666,
|
||||
668,
|
||||
670,
|
||||
672,
|
||||
676,
|
||||
680,
|
||||
682,
|
||||
685,
|
||||
686,
|
||||
697,
|
||||
700,
|
||||
701,
|
||||
711,
|
||||
720,
|
||||
749,
|
||||
875,
|
||||
876,
|
||||
877
|
||||
],
|
||||
"ResponseData": [
|
||||
{
|
||||
"FileName": "/event_103_20201125.evt",
|
||||
"NotBeforeUnixTime": 1335677127,
|
||||
"NotAfterUnixTime": 1966397127,
|
||||
"Md5": "9ef6c4d5ca381583a2d99b626ce06b5e",
|
||||
"Index": 0
|
||||
},
|
||||
{
|
||||
"FileName": "/event_20201125_reg.jpg",
|
||||
"NotBeforeUnixTime": 1335677127,
|
||||
"NotAfterUnixTime": 1966397127,
|
||||
"Md5": "8e3fe25bf50dcbed13dbb54cc18b1efa",
|
||||
"Index": 1
|
||||
},
|
||||
{
|
||||
"FileName": "/event_20201125_sgreg.png",
|
||||
"NotBeforeUnixTime": 1335677127,
|
||||
"NotAfterUnixTime": 1966397127,
|
||||
"Md5": "e0abb0503fe0c530d8a68e36994264c6",
|
||||
"Index": 2
|
||||
},
|
||||
{
|
||||
"FileName": "/news_big_20201125_0.jpg",
|
||||
"NotBeforeUnixTime": 1335677127,
|
||||
"NotAfterUnixTime": 1966397127,
|
||||
"Md5": "4a0f66431f6449279dc046149d1dd882",
|
||||
"Index": 0
|
||||
},
|
||||
{
|
||||
"FileName": "/news_big_20201125_2.jpg",
|
||||
"NotBeforeUnixTime": 1335677127,
|
||||
"NotAfterUnixTime": 1966397127,
|
||||
"Md5": "8e3fe25bf50dcbed13dbb54cc18b1efa",
|
||||
"Index": 2
|
||||
},
|
||||
{
|
||||
"FileName": "/news_small_20201125_1.jpg",
|
||||
"NotBeforeUnixTime": 1335677127,
|
||||
"NotAfterUnixTime": 1966397127,
|
||||
"Md5": "e20135bcd41c98875aec2b52eb9fcd06",
|
||||
"Index": 1
|
||||
},
|
||||
{
|
||||
"FileName": "/telop_20201125.txt",
|
||||
"NotBeforeUnixTime": 1335677127,
|
||||
"NotAfterUnixTime": 1966397127,
|
||||
"Md5": "ee228de44d6656a9ec0bb7f1a0ca64e1",
|
||||
"Index": 0
|
||||
},
|
||||
{
|
||||
"FileName": "/event_unlock_20201125.cmp",
|
||||
"NotBeforeUnixTime": 1335677127,
|
||||
"NotAfterUnixTime": 1966397127,
|
||||
"Md5": "534a253e3de8360c2beff49a5f120105",
|
||||
"Index": 8
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -14,7 +14,7 @@ public class AliveController : WebApiController
|
||||
public string Check()
|
||||
{
|
||||
HttpContext.Response.ContentType = MediaTypeNames.Text.Html;
|
||||
HttpContext.Response.ContentEncoding = Encoding.Default;
|
||||
HttpContext.Response.ContentEncoding = new UTF8Encoding(false);
|
||||
HttpContext.Response.KeepAlive = true;
|
||||
|
||||
return "REMOTE ADDRESS: 127.0.0.1\n" +
|
||||
@@ -28,7 +28,7 @@ public class AliveController : WebApiController
|
||||
{
|
||||
HttpContext.Response.SetEmptyResponse((int)HttpStatusCode.OK);
|
||||
HttpContext.Response.ContentType = MediaTypeNames.Text.Plain;
|
||||
HttpContext.Response.ContentEncoding = Encoding.Default;
|
||||
HttpContext.Response.ContentEncoding = new UTF8Encoding(false);
|
||||
HttpContext.Response.KeepAlive = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,340 @@
|
||||
using EmbedIO;
|
||||
using EmbedIO.Routing;
|
||||
using EmbedIO.WebApi;
|
||||
using GCLocalServerRewrite.common;
|
||||
using GCLocalServerRewrite.models;
|
||||
using SharedProject.common;
|
||||
using SharedProject.enums;
|
||||
using SharedProject.models;
|
||||
using SQLite.Net2;
|
||||
using Swan.Logging;
|
||||
|
||||
namespace GCLocalServerRewrite.controllers;
|
||||
|
||||
public class ApiController : WebApiController
|
||||
{
|
||||
private readonly SQLiteConnection cardSqLiteConnection;
|
||||
|
||||
private readonly Dictionary<int, Music> musics;
|
||||
private readonly Dictionary<int, MusicExtra> musicExtras;
|
||||
|
||||
public ApiController()
|
||||
{
|
||||
cardSqLiteConnection = DatabaseHelper.ConnectDatabase(Configs.SETTINGS.CardDbName);
|
||||
var musicSqLiteConnection = DatabaseHelper.ConnectDatabase(Configs.SETTINGS.MusicDbName);
|
||||
musics = musicSqLiteConnection.Table<Music>().ToDictionary(music => music.MusicId);
|
||||
musicExtras = musicSqLiteConnection.Table<MusicExtra>().ToDictionary(music => music.MusicId);
|
||||
}
|
||||
|
||||
[Route(HttpVerbs.Get, "/Users")]
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public List<User> GetUsers()
|
||||
{
|
||||
var result = cardSqLiteConnection.Table<Card>().ToList().ConvertAll(card => new User
|
||||
{
|
||||
CardId = card.CardId,
|
||||
PlayerName = card.PlayerName
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[Route(HttpVerbs.Post, "/Users/SetPlayerName")]
|
||||
public bool SetPlayerName([JsonData] User data)
|
||||
{
|
||||
var existing = cardSqLiteConnection.Table<Card>().Where(card => card.CardId == data.CardId);
|
||||
if (!existing.Any())
|
||||
{
|
||||
$"Trying to update non existing user's name! Card id {data.CardId}".Warn();
|
||||
return false;
|
||||
}
|
||||
var user = existing.First();
|
||||
user.PlayerName = data.PlayerName;
|
||||
return cardSqLiteConnection.Update(user) == 1;
|
||||
}
|
||||
|
||||
[Route(HttpVerbs.Post, "/UserDetail/SetMusicFavorite")]
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public bool SetFavorite([JsonData] MusicFavoriteData data)
|
||||
{
|
||||
var existing = cardSqLiteConnection.Table<CardDetail>()
|
||||
.Where(detail => detail.CardId == data.CardId
|
||||
&& detail.Pcol1 == Configs.FAVORITE_PCOL1
|
||||
&& detail.Pcol2 == data.MusicId);
|
||||
|
||||
if (!existing.Any())
|
||||
{
|
||||
$"Trying to update non existing song's favorite! Card id {data.CardId}, music id {data.MusicId}".Warn();
|
||||
return false;
|
||||
}
|
||||
|
||||
var cardDetail = existing.First();
|
||||
cardDetail.Fcol1 = data.IsFavorite ? 1 : 0;
|
||||
var result = cardSqLiteConnection.Update(cardDetail);
|
||||
|
||||
return result == 1;
|
||||
}
|
||||
|
||||
[Route(HttpVerbs.Post, "/UserDetail/SetPlayOption")]
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public bool SetPlayOption([JsonData] PlayOption data)
|
||||
{
|
||||
var firstConfig = cardSqLiteConnection.Table<CardDetail>()
|
||||
.Where(detail => detail.CardId == data.CardId
|
||||
&& detail.Pcol1 == Configs.FIRST_CONFIG_PCOL1
|
||||
&& detail.Pcol2 == Configs.CONFIG_PCOL2
|
||||
&& detail.Pcol3 == Configs.CONFIG_PCOL3);
|
||||
|
||||
var secondConfig = cardSqLiteConnection.Table<CardDetail>()
|
||||
.Where(detail => detail.CardId == data.CardId
|
||||
&& detail.Pcol1 == Configs.SECOND_CONFIG_PCOL1
|
||||
&& detail.Pcol2 == Configs.CONFIG_PCOL2
|
||||
&& detail.Pcol3 == Configs.CONFIG_PCOL3);
|
||||
|
||||
if (!firstConfig.Any() || !secondConfig.Any())
|
||||
{
|
||||
$"Trying to update non existing card's config! Card id {data.CardId}".Warn();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
var firstDetail = firstConfig.First();
|
||||
firstDetail.ScoreUi1 = (long)data.FastSlowIndicator;
|
||||
firstDetail.ScoreUi2 = (long)data.FeverTrance;
|
||||
firstDetail.ScoreI1 = data.AvatarId;
|
||||
firstDetail.Fcol2 = (int)data.TitleId;
|
||||
|
||||
var secondDetail = secondConfig.First();
|
||||
secondDetail.ScoreI1 = data.NavigatorId;
|
||||
|
||||
var firstResult = cardSqLiteConnection.Update(firstDetail);
|
||||
var secondResult = cardSqLiteConnection.Update(secondDetail);
|
||||
|
||||
return firstResult == 1 && secondResult == 1;
|
||||
}
|
||||
|
||||
[Route(HttpVerbs.Get, "/UserDetail/{cardId}")]
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public UserDetail? GetUserDetail(long cardId)
|
||||
{
|
||||
var cardResult = cardSqLiteConnection.Table<Card>().Where(card => card.CardId == cardId);
|
||||
|
||||
if (!cardResult.Any())
|
||||
{
|
||||
$"Getting detail for non exisisting card! Card id is {cardId}".Warn();
|
||||
return null;
|
||||
}
|
||||
|
||||
var card = cardResult.First();
|
||||
|
||||
return ToUserDetail(card);
|
||||
}
|
||||
|
||||
private UserDetail? ToUserDetail(Card card)
|
||||
{
|
||||
if (!cardSqLiteConnection.Table<CardDetail>().Select(detail => detail.CardId == card.CardId).Any())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var userDetail = new UserDetail
|
||||
{
|
||||
CardId = card.CardId,
|
||||
PlayerName = card.PlayerName
|
||||
};
|
||||
var songPlayDataDict = new Dictionary<int, SongPlayData>();
|
||||
|
||||
ProcessCardDetail(userDetail, songPlayDataDict);
|
||||
|
||||
userDetail.SongPlayDataList = songPlayDataDict.Values.ToList();
|
||||
userDetail.TotalSongCount = musics.Count;
|
||||
userDetail.TotalStageCount = userDetail.TotalSongCount * 3 + musicExtras.Count;
|
||||
userDetail.AverageScore = (int)(userDetail.TotalScore / userDetail.PlayedStageCount);
|
||||
userDetail.PlayedSongCount = songPlayDataDict.Count;
|
||||
return userDetail;
|
||||
}
|
||||
|
||||
private void ProcessCardDetail(UserDetail userDetail, IDictionary<int, SongPlayData> songPlayDataDict)
|
||||
{
|
||||
var firstOption = cardSqLiteConnection.Table<CardDetail>()
|
||||
.FirstOrDefault(detail => detail.CardId == userDetail.CardId
|
||||
&& detail.Pcol1 == Configs.FIRST_CONFIG_PCOL1
|
||||
&& detail.Pcol2 == Configs.CONFIG_PCOL2
|
||||
&& detail.Pcol3 == Configs.CONFIG_PCOL3
|
||||
, new CardDetail
|
||||
{
|
||||
CardId = userDetail.CardId
|
||||
});
|
||||
var secondOption = cardSqLiteConnection.Table<CardDetail>()
|
||||
.FirstOrDefault(detail => detail.CardId == userDetail.CardId
|
||||
&& detail.Pcol1 == Configs.SECOND_CONFIG_PCOL1
|
||||
&& detail.Pcol2 == Configs.CONFIG_PCOL2
|
||||
&& detail.Pcol3 == Configs.CONFIG_PCOL3
|
||||
, new CardDetail
|
||||
{
|
||||
CardId = userDetail.CardId
|
||||
});
|
||||
|
||||
SetOptions(firstOption, secondOption, userDetail);
|
||||
|
||||
var songCounts = cardSqLiteConnection.Table<CardDetail>()
|
||||
.Where(detail => detail.CardId == userDetail.CardId && detail.Pcol1 == Configs.COUNT_PCOL1);
|
||||
|
||||
foreach (var detail in songCounts)
|
||||
{
|
||||
SetCounts(detail, songPlayDataDict, userDetail);
|
||||
}
|
||||
|
||||
var songScores = cardSqLiteConnection.Table<CardDetail>()
|
||||
.Where(detail => detail.CardId == userDetail.CardId && detail.Pcol1 == Configs.SCORE_PCOL1);
|
||||
|
||||
foreach (var detail in songScores)
|
||||
{
|
||||
SetDetails(detail, songPlayDataDict, userDetail);
|
||||
}
|
||||
|
||||
var favorites = cardSqLiteConnection.Table<CardDetail>()
|
||||
.Where(detail => detail.CardId == userDetail.CardId && detail.Pcol1 == Configs.FAVORITE_PCOL1)
|
||||
.ToDictionary(detail => detail.Pcol2);
|
||||
|
||||
foreach (var (musicId, songPlayData) in songPlayDataDict)
|
||||
{
|
||||
songPlayData.IsFavorite = favorites[musicId].Fcol1 != 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetOptions(CardDetail firstOptionCardDetail, CardDetail secondOptionCardDetail, UserDetail userDetail)
|
||||
{
|
||||
var fastSlow = (int)firstOptionCardDetail.ScoreUi1;
|
||||
var feverTrance = (int)firstOptionCardDetail.ScoreUi2;
|
||||
|
||||
if (!Enum.IsDefined(typeof(PlayOptions.FastSlowIndicator), fastSlow))
|
||||
{
|
||||
fastSlow = (int)PlayOptions.FastSlowIndicator.NotUsed;
|
||||
}
|
||||
|
||||
if (!Enum.IsDefined(typeof(PlayOptions.FeverTranceShow), feverTrance))
|
||||
{
|
||||
feverTrance = (int)PlayOptions.FeverTranceShow.Show;
|
||||
}
|
||||
|
||||
userDetail.PlayOption = new PlayOption
|
||||
{
|
||||
CardId = firstOptionCardDetail.CardId,
|
||||
FastSlowIndicator = (PlayOptions.FastSlowIndicator)fastSlow,
|
||||
FeverTrance = (PlayOptions.FeverTranceShow)feverTrance,
|
||||
AvatarId = firstOptionCardDetail.ScoreI1,
|
||||
TitleId = firstOptionCardDetail.Fcol2,
|
||||
NavigatorId = secondOptionCardDetail.ScoreI1
|
||||
};
|
||||
}
|
||||
private void SetDetails(CardDetail cardDetail, IDictionary<int, SongPlayData> songPlayDataDict,
|
||||
UserDetail userDetail)
|
||||
{
|
||||
var musicId = cardDetail.Pcol2;
|
||||
|
||||
AddSongPlayDataIfNotExist(songPlayDataDict, musicId);
|
||||
|
||||
for (var i = 0; i < SharedConstants.DIFFICULTY_COUNT; i++)
|
||||
{
|
||||
var songPlayDetailData = songPlayDataDict[musicId].SongPlaySubDataList[i];
|
||||
songPlayDetailData.Difficulty = (Difficulty)i;
|
||||
|
||||
if (i != cardDetail.Pcol3)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
songPlayDetailData.Score = (int)cardDetail.ScoreUi1;
|
||||
songPlayDetailData.MaxChain = (int)cardDetail.ScoreUi3;
|
||||
|
||||
userDetail.TotalScore += cardDetail.ScoreUi1;
|
||||
|
||||
if (cardDetail.ScoreUi1 >= SharedConstants.S_SCORE_THRESHOLD)
|
||||
{
|
||||
userDetail.SAboveStageCount++;
|
||||
}
|
||||
|
||||
if (cardDetail.ScoreUi1 >= SharedConstants.S_PLUS_SCORE_THRESHOLD)
|
||||
{
|
||||
userDetail.SPlusAboveStageCount++;
|
||||
}
|
||||
|
||||
if (cardDetail.ScoreUi1 >= SharedConstants.S_PLUS_PLUS_SCORE_THRESHOLD)
|
||||
{
|
||||
userDetail.SPlusPlusAboveStageCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetCounts(CardDetail cardDetail, IDictionary<int, SongPlayData> songPlayDataDict, UserDetail userDetail)
|
||||
{
|
||||
var musicId = cardDetail.Pcol2;
|
||||
|
||||
AddSongPlayDataIfNotExist(songPlayDataDict, musicId);
|
||||
|
||||
for (var i = 0; i < SharedConstants.DIFFICULTY_COUNT; i++)
|
||||
{
|
||||
var songPlayDetailData = songPlayDataDict[musicId].SongPlaySubDataList[i];
|
||||
|
||||
songPlayDetailData.Difficulty = (Difficulty)i;
|
||||
|
||||
if (i != cardDetail.Pcol3)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
songPlayDetailData.PlayCount = (int)cardDetail.ScoreUi1;
|
||||
songPlayDetailData.LastPlayTime = cardDetail.LastPlayTime;
|
||||
songPlayDetailData.ClearState = ClearState.Failed;
|
||||
userDetail.PlayedStageCount++;
|
||||
|
||||
if (cardDetail.ScoreUi2 > 0)
|
||||
{
|
||||
userDetail.ClearedStageCount++;
|
||||
songPlayDetailData.ClearState = ClearState.Clear;
|
||||
}
|
||||
|
||||
if (cardDetail.ScoreUi3 > 0)
|
||||
{
|
||||
userDetail.NoMissStageCount++;
|
||||
songPlayDetailData.ClearState = ClearState.NoMiss;
|
||||
}
|
||||
|
||||
if (cardDetail.ScoreUi4 > 0)
|
||||
{
|
||||
userDetail.FullChainStageCount++;
|
||||
songPlayDetailData.ClearState = ClearState.FullChain;
|
||||
}
|
||||
|
||||
if (cardDetail.ScoreUi6 > 0)
|
||||
{
|
||||
userDetail.PerfectStageCount++;
|
||||
songPlayDetailData.ClearState = ClearState.Perfect;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddSongPlayDataIfNotExist(IDictionary<int, SongPlayData> songPlayDataDict, int musicId)
|
||||
{
|
||||
if (songPlayDataDict.ContainsKey(musicId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var musicData = musics[musicId];
|
||||
var songPlayData = new SongPlayData
|
||||
{
|
||||
Artist = musicData.Artist ?? string.Empty,
|
||||
Title = musicData.Title ?? string.Empty,
|
||||
MusicId = musicId,
|
||||
SongPlaySubDataList = new SongPlayDetailData[SharedConstants.DIFFICULTY_COUNT]
|
||||
};
|
||||
|
||||
for (var i = 0; i < SharedConstants.DIFFICULTY_COUNT; i++)
|
||||
{
|
||||
songPlayData.SongPlaySubDataList[i] = new SongPlayDetailData();
|
||||
}
|
||||
songPlayDataDict[musicId] = songPlayData;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Data;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Net.Mime;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
@@ -9,6 +9,7 @@ using EmbedIO.WebApi;
|
||||
using GCLocalServerRewrite.common;
|
||||
using GCLocalServerRewrite.models;
|
||||
using SQLite.Net2;
|
||||
using Swan;
|
||||
using Swan.Logging;
|
||||
|
||||
namespace GCLocalServerRewrite.controllers;
|
||||
@@ -18,169 +19,170 @@ public class CardServiceController : WebApiController
|
||||
private readonly SQLiteConnection cardSqLiteConnection;
|
||||
private readonly SQLiteConnection musicSqLiteConnection;
|
||||
|
||||
private static readonly ConcurrentDictionary<long, List<OnlineMatchingEntry>> OnlineMatchingEntries = new()
|
||||
{
|
||||
[0xDEADBEEF] = new List<OnlineMatchingEntry>(),
|
||||
[0xCAFEBABE] = new List<OnlineMatchingEntry>(),
|
||||
[0xD0D0CACA] = new List<OnlineMatchingEntry>()
|
||||
};
|
||||
|
||||
public CardServiceController()
|
||||
{
|
||||
cardSqLiteConnection = DatabaseHelper.ConnectDatabase(Configs.CARD_DB_NAME);
|
||||
musicSqLiteConnection = DatabaseHelper.ConnectDatabase(Configs.MUSIC_DB_NAME);
|
||||
cardSqLiteConnection = DatabaseHelper.ConnectDatabase(Configs.SETTINGS.CardDbName);
|
||||
musicSqLiteConnection = DatabaseHelper.ConnectDatabase(Configs.SETTINGS.MusicDbName);
|
||||
}
|
||||
|
||||
[Route(HttpVerbs.Post, "/cardn.cgi")]
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public string CardService([FormField] int gid, [FormField("mac_addr")] string mac, [FormField] int type,
|
||||
[FormField("card_no")] long cardId, [FormField("data")] string xmlData)
|
||||
[FormField("card_no")] long cardId, [FormField("data")] string xmlData, [FormField("cmd_str")] int cmdType)
|
||||
{
|
||||
HttpContext.Response.ContentType = MediaTypeNames.Application.Octet;
|
||||
HttpContext.Response.ContentEncoding = Encoding.Default;
|
||||
HttpContext.Response.ContentEncoding = new UTF8Encoding(false);
|
||||
HttpContext.Response.KeepAlive = true;
|
||||
|
||||
if (gid != Configs.GC4_EX_GID)
|
||||
return ProcessCommand(cmdType, mac, cardId, xmlData, type);
|
||||
}
|
||||
|
||||
private string ProcessCommand(int cmdType, string mac, long cardId, string xmlData, int type)
|
||||
{
|
||||
if (!Enum.IsDefined(typeof(Command), cmdType))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(gid));
|
||||
throw new ArgumentOutOfRangeException(nameof(cmdType), cmdType, $"Cmd type is unknown!\n Data is {xmlData}");
|
||||
}
|
||||
|
||||
var command = (Command)cmdType;
|
||||
|
||||
return command switch
|
||||
{
|
||||
Command.CardReadRequest or Command.CardWriteRequest => ProcessCardRequest(mac, cardId, xmlData, type),
|
||||
Command.ReissueRequest => ProcessReissueRequest(),
|
||||
Command.RegisterRequest => ProcessRegisterRequest(cardId, xmlData),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(command), command, "Command unknown, should never happen!")
|
||||
};
|
||||
}
|
||||
|
||||
private string ProcessRegisterRequest(long cardId, string xmlData)
|
||||
{
|
||||
$"Get card register request, data is \n{xmlData}".Info();
|
||||
Write<Card>(cardId, xmlData);
|
||||
return ConstructResponse(xmlData);
|
||||
}
|
||||
|
||||
private static string ProcessReissueRequest()
|
||||
{
|
||||
"Get reissue request, returning not reissue".Info();
|
||||
return ConstructResponse("", ReturnCode.NotReissue);
|
||||
}
|
||||
|
||||
private string ProcessCardRequest(string mac, long cardId, string xmlData, int type)
|
||||
{
|
||||
if (!Enum.IsDefined(typeof(CardRequestType), type))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(type));
|
||||
throw new ArgumentOutOfRangeException(nameof(type), type, "Card request type is unknown!");
|
||||
}
|
||||
|
||||
var requestType = (CardRequestType)type;
|
||||
|
||||
$"Getting card request, type is {requestType}".Info();
|
||||
|
||||
switch (requestType)
|
||||
{
|
||||
#region ReadRequests
|
||||
|
||||
case CardRequestType.ReadCard:
|
||||
{
|
||||
$"Getting read request, type is {requestType}".Info();
|
||||
var response = Card(cardId, out var returnCode);
|
||||
|
||||
return ConstructResponse(Card(cardId));
|
||||
return ConstructResponse(response, returnCode);
|
||||
}
|
||||
case CardRequestType.ReadCardDetail:
|
||||
{
|
||||
$"Getting read request, type is {requestType}".Info();
|
||||
|
||||
return ConstructResponse(CardDetail(cardId, xmlData));
|
||||
var cardDetail = CardDetail(cardId, xmlData);
|
||||
return ConstructResponse(cardDetail);
|
||||
}
|
||||
case CardRequestType.ReadCardDetails:
|
||||
{
|
||||
$"Getting read request, type is {requestType}".Info();
|
||||
|
||||
return ConstructResponse(CardDetails(cardId));
|
||||
}
|
||||
case CardRequestType.ReadCardBData:
|
||||
{
|
||||
$"Getting read request, type is {requestType}".Info();
|
||||
|
||||
return ConstructResponse(CardBData(cardId));
|
||||
}
|
||||
case CardRequestType.ReadAvatar:
|
||||
{
|
||||
$"Getting read request, type is {requestType}".Info();
|
||||
|
||||
return ConstructResponse(
|
||||
GetStaticCount<Avatar>(cardId, Configs.AVATAR_COUNT, Configs.AVATAR_XPATH));
|
||||
GetStaticCount<Avatar>(cardId, Configs.SETTINGS.AvatarCount, Configs.AVATAR_XPATH));
|
||||
}
|
||||
case CardRequestType.ReadItem:
|
||||
{
|
||||
$"Getting read request, type is {requestType}".Info();
|
||||
|
||||
return ConstructResponse(
|
||||
GetStaticCount<Item>(cardId, Configs.ITEM_COUNT, Configs.ITEM_XPATH));
|
||||
GetStaticCount<Item>(cardId, Configs.SETTINGS.ItemCount, Configs.ITEM_XPATH));
|
||||
}
|
||||
case CardRequestType.ReadSkin:
|
||||
{
|
||||
$"Getting read request, type is {requestType}".Info();
|
||||
|
||||
return ConstructResponse(
|
||||
GetStaticCount<Skin>(cardId, Configs.SKIN_COUNT, Configs.SKIN_XPATH));
|
||||
GetStaticCount<Skin>(cardId, Configs.SETTINGS.SkinCount, Configs.SKIN_XPATH));
|
||||
}
|
||||
case CardRequestType.ReadTitle:
|
||||
{
|
||||
$"Getting read request, type is {requestType}".Info();
|
||||
|
||||
return ConstructResponse(
|
||||
GetStaticCount<Title>(cardId, Configs.TITLE_COUNT, Configs.TITLE_XPATH));
|
||||
GetStaticCount<Title>(cardId, Configs.SETTINGS.TitleCount, Configs.TITLE_XPATH));
|
||||
}
|
||||
case CardRequestType.ReadMusic:
|
||||
{
|
||||
$"Getting read request, type is {requestType}".Info();
|
||||
|
||||
return ConstructResponse(MusicUnlock());
|
||||
}
|
||||
case CardRequestType.ReadEventReward:
|
||||
{
|
||||
$"Getting read request, type is {requestType}".Info();
|
||||
|
||||
return ConstructResponse(
|
||||
GenerateEmptyXML(Configs.EVENT_REWARD));
|
||||
GenerateEmptyXml(Configs.EVENT_REWARD));
|
||||
}
|
||||
case CardRequestType.ReadNavigator:
|
||||
{
|
||||
$"Getting read request, type is {requestType}".Info();
|
||||
|
||||
return ConstructResponse(
|
||||
GetStaticCount<Navigator>(cardId, Configs.NAVIGATOR_COUNT, Configs.NAVIGATOR_XPATH));
|
||||
GetStaticCount<Navigator>(cardId, Configs.SETTINGS.NavigatorCount, Configs.NAVIGATOR_XPATH));
|
||||
}
|
||||
case CardRequestType.ReadMusicExtra:
|
||||
{
|
||||
$"Getting read request, type is {requestType}".Info();
|
||||
|
||||
return ConstructResponse(MusicExtra());
|
||||
}
|
||||
case CardRequestType.ReadMusicAou:
|
||||
{
|
||||
$"Getting read request, type is {requestType}".Info();
|
||||
|
||||
return ConstructResponse(GenerateEmptyXML(Configs.MUSIC_AOU));
|
||||
return ConstructResponse(MusicAouUnlock());
|
||||
}
|
||||
case CardRequestType.ReadCoin:
|
||||
{
|
||||
$"Getting read request, type is {requestType}".Info();
|
||||
|
||||
return ConstructResponse(Coin(cardId));
|
||||
}
|
||||
case CardRequestType.ReadUnlockReward:
|
||||
{
|
||||
$"Getting read request, type is {requestType}".Info();
|
||||
|
||||
return ConstructResponse(UnlockReward(cardId));
|
||||
}
|
||||
case CardRequestType.ReadUnlockKeynum:
|
||||
{
|
||||
$"Getting read request, type is {requestType}".Info();
|
||||
|
||||
return ConstructResponse(UnlockKeynum(cardId));
|
||||
}
|
||||
case CardRequestType.ReadSoundEffect:
|
||||
{
|
||||
$"Getting read request, type is {requestType}".Info();
|
||||
|
||||
return ConstructResponse(
|
||||
GetStaticCount<SoundEffect>(cardId, Configs.SE_COUNT, Configs.SE_XPATH));
|
||||
GetStaticCount<SoundEffect>(cardId, Configs.SETTINGS.SeCount, Configs.SE_XPATH));
|
||||
}
|
||||
case CardRequestType.ReadGetMessage:
|
||||
{
|
||||
$"Getting read request, type is {requestType}".Info();
|
||||
|
||||
return ConstructResponse(GenerateEmptyXML(Configs.GET_MESSAGE));
|
||||
return ConstructResponse(GenerateEmptyXml(Configs.GET_MESSAGE));
|
||||
}
|
||||
case CardRequestType.ReadCond:
|
||||
{
|
||||
$"Getting read request, type is {requestType}".Info();
|
||||
|
||||
return ConstructResponse(GenerateEmptyXML(Configs.COND));
|
||||
return ConstructResponse(GenerateEmptyXml(Configs.COND));
|
||||
}
|
||||
case CardRequestType.ReadTotalTrophy:
|
||||
{
|
||||
$"Getting read request, type is {requestType}".Info();
|
||||
|
||||
return ConstructResponse(TotalTrophy(cardId));
|
||||
}
|
||||
case CardRequestType.SessionStart:
|
||||
case CardRequestType.SessionGet:
|
||||
case CardRequestType.StartSession:
|
||||
case CardRequestType.GetSession:
|
||||
{
|
||||
$"Getting read request, type is {requestType}".Info();
|
||||
|
||||
return ConstructResponse(GetSession(cardId, mac));
|
||||
}
|
||||
|
||||
@@ -188,27 +190,26 @@ public class CardServiceController : WebApiController
|
||||
|
||||
#region WriteRequests
|
||||
|
||||
case CardRequestType.WriteCard:
|
||||
{
|
||||
$"Card Write data is {xmlData}".Info();
|
||||
Write<Card>(cardId, xmlData);
|
||||
return ConstructResponse(xmlData);
|
||||
}
|
||||
case CardRequestType.WriteCardDetail:
|
||||
{
|
||||
$"Getting write request, type is {requestType}\n Data is {xmlData}".Info();
|
||||
Write<CardDetail>(cardId, xmlData);
|
||||
|
||||
$"Card Detail Write data is {xmlData}".Info();
|
||||
WriteCardDetail(cardId, xmlData);
|
||||
return ConstructResponse(xmlData);
|
||||
}
|
||||
case CardRequestType.WriteCardBData:
|
||||
{
|
||||
$"Getting write request, type is {requestType}\n Data is {xmlData}".Info();
|
||||
$"Card BData Write data is {xmlData}".Info();
|
||||
Write<CardBData>(cardId, xmlData);
|
||||
|
||||
return ConstructResponse(xmlData);
|
||||
}
|
||||
case CardRequestType.WriteCard:
|
||||
{
|
||||
$"Getting write request, type is {requestType}\n Data is {xmlData}".Info();
|
||||
Write<Card>(cardId, xmlData);
|
||||
|
||||
WriteCardPlayCount(cardId);
|
||||
return ConstructResponse(xmlData);
|
||||
}
|
||||
// TODO: Maybe one day implement these
|
||||
case CardRequestType.WriteAvatar:
|
||||
case CardRequestType.WriteItem:
|
||||
case CardRequestType.WriteTitle:
|
||||
@@ -219,33 +220,52 @@ public class CardServiceController : WebApiController
|
||||
case CardRequestType.WriteUnlockKeynum:
|
||||
case CardRequestType.WriteSoundEffect:
|
||||
{
|
||||
$"Getting write request, type is {requestType}\n Data is {xmlData}".Info();
|
||||
|
||||
$"Card Write data is {xmlData}".Info();
|
||||
return ConstructResponse(xmlData);
|
||||
}
|
||||
|
||||
#endregion
|
||||
#region OnlineMatching
|
||||
|
||||
case CardRequestType.StartOnlineMatching:
|
||||
{
|
||||
$"Start Online Matching, data is {xmlData}".Info();
|
||||
return ConstructResponse(StartOnlineMatching(cardId, xmlData));
|
||||
}
|
||||
|
||||
case CardRequestType.UpdateOnlineMatching:
|
||||
{
|
||||
$"Update Online Matching, data is {xmlData}".Info();
|
||||
return ConstructResponse(UpdateOnlineMatching(cardId, xmlData));
|
||||
}
|
||||
|
||||
case CardRequestType.UploadOnlineMatchingResult:
|
||||
{
|
||||
$"Get Online Matching result, data is {xmlData}".Info();
|
||||
return ConstructResponse(UploadOnlineMatchingResult(cardId, xmlData));
|
||||
}
|
||||
#endregion
|
||||
|
||||
default:
|
||||
#pragma warning disable CA2208
|
||||
throw new ArgumentOutOfRangeException(nameof(requestType));
|
||||
#pragma warning restore CA2208
|
||||
throw new ArgumentOutOfRangeException(nameof(requestType), requestType, "Request type not captured, should never happen!");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#region ReadImplementation
|
||||
|
||||
private string Card(long cardId)
|
||||
private string Card(long cardId, out ReturnCode returnCode)
|
||||
{
|
||||
var result = cardSqLiteConnection.Table<Card>().Where(card => card.CardId == cardId);
|
||||
|
||||
if (!result.Any())
|
||||
{
|
||||
return GenerateEmptyXML(Configs.CARD);
|
||||
returnCode = ReturnCode.CardNotRegistered;
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var card = result.First();
|
||||
|
||||
returnCode = ReturnCode.Ok;
|
||||
return GenerateSingleXml(card, Configs.CARD_XPATH);
|
||||
}
|
||||
|
||||
@@ -261,11 +281,10 @@ public class CardServiceController : WebApiController
|
||||
|
||||
if (!result.Any())
|
||||
{
|
||||
return GenerateEmptyXML(Configs.CARD_DETAIL);
|
||||
return GenerateEmptyXml(Configs.CARD_DETAIL);
|
||||
}
|
||||
|
||||
var cardDetail = result.First();
|
||||
|
||||
return GenerateSingleXml(cardDetail, Configs.CARD_DETAIL_SINGLE_XPATH);
|
||||
}
|
||||
|
||||
@@ -276,7 +295,7 @@ public class CardServiceController : WebApiController
|
||||
|
||||
if (!result.Any())
|
||||
{
|
||||
return GenerateEmptyXML(Configs.CARD_DETAIL);
|
||||
return GenerateEmptyXml(Configs.CARD_DETAIL);
|
||||
}
|
||||
|
||||
var cardDetails = result.ToList();
|
||||
@@ -291,7 +310,7 @@ public class CardServiceController : WebApiController
|
||||
|
||||
if (!result.Any())
|
||||
{
|
||||
return GenerateEmptyXML(Configs.CARD_BDATA);
|
||||
return GenerateEmptyXml(Configs.CARD_BDATA);
|
||||
}
|
||||
var cardBData = result.First();
|
||||
|
||||
@@ -393,6 +412,13 @@ public class CardServiceController : WebApiController
|
||||
return GenerateRecordsXml(result, Configs.MUSIC_XPATH);
|
||||
}
|
||||
|
||||
private string MusicAouUnlock()
|
||||
{
|
||||
var result = musicSqLiteConnection.Table<MusicAou>().ToList();
|
||||
|
||||
return !result.Any() ? GenerateEmptyXml(Configs.MUSIC_AOU) : GenerateRecordsXml(result, Configs.MUSIC_AOU_XPATH);
|
||||
}
|
||||
|
||||
private string MusicExtra()
|
||||
{
|
||||
var result = musicSqLiteConnection.Table<MusicExtra>().ToList();
|
||||
@@ -404,23 +430,28 @@ public class CardServiceController : WebApiController
|
||||
|
||||
#region HelperMethods
|
||||
|
||||
private static string ConstructResponse(string xml)
|
||||
private static string ConstructResponse(string xml, ReturnCode returnCode = ReturnCode.Ok)
|
||||
{
|
||||
return "1\n" +
|
||||
"10,10\n" +
|
||||
xml;
|
||||
var returnCodeInt = (int)returnCode;
|
||||
if (returnCodeInt == 1)
|
||||
{
|
||||
return $"{returnCodeInt}\n" +
|
||||
"1,1\n" +
|
||||
xml;
|
||||
}
|
||||
return $"{returnCodeInt}";
|
||||
}
|
||||
|
||||
private static string GenerateEmptyXML(string fieldName)
|
||||
private static string GenerateEmptyXml(string fieldName)
|
||||
{
|
||||
var xml = new XDocument(new XElement("root",
|
||||
new XElement(fieldName)));
|
||||
new XElement(fieldName)));
|
||||
xml.Declaration = new XDeclaration("1.0", "UTF-8", null);
|
||||
|
||||
return xml.ToString();
|
||||
}
|
||||
|
||||
private static string GenerateSingleXml<T>(T obj, string xpath) where T: class
|
||||
private static string GenerateSingleXml<T>(T obj, string xpath) where T : class
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
@@ -435,7 +466,7 @@ public class CardServiceController : WebApiController
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
|
||||
private static string GenerateRecordsXml<T>(IReadOnlyList<T> list, string xpath) where T : Record
|
||||
{
|
||||
var stringBuilder = new StringBuilder();
|
||||
@@ -454,7 +485,6 @@ public class CardServiceController : WebApiController
|
||||
writer.Write(list);
|
||||
}
|
||||
|
||||
stringBuilder.Insert(stringBuilder.Length - 5, "/");
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
|
||||
@@ -483,6 +513,190 @@ public class CardServiceController : WebApiController
|
||||
$"Updated {typeof(T)}".Info();
|
||||
}
|
||||
|
||||
private void WriteCardPlayCount(long cardId)
|
||||
{
|
||||
var record = cardSqLiteConnection.Table<CardPlayCount>().Where(count => count.CardId == cardId);
|
||||
|
||||
if (!record.Any())
|
||||
{
|
||||
$"Created new play count data for card {cardId}".Info();
|
||||
var playCount = new CardPlayCount
|
||||
{
|
||||
CardId = cardId,
|
||||
PlayCount = 1,
|
||||
LastPlayed = DateTime.Now
|
||||
};
|
||||
cardSqLiteConnection.InsertOrReplace(playCount);
|
||||
return;
|
||||
}
|
||||
|
||||
var data = record.First();
|
||||
var now = DateTime.Now;
|
||||
var lastPlayedTime = data.LastPlayed;
|
||||
|
||||
if (now <= lastPlayedTime)
|
||||
{
|
||||
$"Current time {now} is less than or equal to last played time! Clock skew detected!".Warn();
|
||||
data.PlayCount = 0;
|
||||
data.LastPlayed = DateTime.Now;
|
||||
cardSqLiteConnection.InsertOrReplace(data);
|
||||
return;
|
||||
}
|
||||
|
||||
DateTime start;
|
||||
DateTime end;
|
||||
|
||||
if (now.Hour >= 8)
|
||||
{
|
||||
start = DateTime.Today.AddHours(8);
|
||||
end = start.AddHours(24);
|
||||
}
|
||||
else
|
||||
{
|
||||
end = DateTime.Today.AddHours(8);
|
||||
start = end.AddHours(-24);
|
||||
}
|
||||
|
||||
data.PlayCount = lastPlayedTime.IsBetween(start, end) ? data.PlayCount + 1 : 0;
|
||||
cardSqLiteConnection.InsertOrReplace(data);
|
||||
$"Updated card play count, current count is {data.PlayCount}".Info();
|
||||
}
|
||||
|
||||
private void WriteCardDetail(long cardId, string xmlData)
|
||||
{
|
||||
var result = cardSqLiteConnection.Table<CardDetail>()
|
||||
.Where(detail => detail.CardId == cardId);
|
||||
|
||||
// Unlock all unlockable songs in card details table when write card detail for the first time
|
||||
if (!result.Any())
|
||||
{
|
||||
UnlockSongs(cardId);
|
||||
}
|
||||
|
||||
var reader = new ChoXmlReader<CardDetail>(new StringReader(xmlData)).WithXPath(Configs.DATA_XPATH);
|
||||
var cardDetail = reader.Read();
|
||||
|
||||
if (cardDetail is null)
|
||||
{
|
||||
throw new HttpRequestException("Write object is null");
|
||||
}
|
||||
|
||||
cardDetail.SetCardId(cardId);
|
||||
cardDetail.LastPlayTime = DateTime.Now;
|
||||
var rowsAffected = cardSqLiteConnection.InsertOrReplace(cardDetail);
|
||||
if (rowsAffected == 0)
|
||||
{
|
||||
throw new ApplicationException("Update database failed!");
|
||||
}
|
||||
|
||||
"Updated card detail".Info();
|
||||
}
|
||||
private void UnlockSongs(long cardId)
|
||||
{
|
||||
var unlockableSongIds = Configs.SETTINGS.UnlockableSongIds;
|
||||
|
||||
if (unlockableSongIds is null)
|
||||
{
|
||||
unlockableSongIds = Configs.DEFAULT_UNLOCKABLE_SONGS;
|
||||
}
|
||||
var detailList = unlockableSongIds.Select(id => new CardDetail
|
||||
{
|
||||
CardId = cardId,
|
||||
Pcol1 = 10,
|
||||
Pcol2 = id,
|
||||
Pcol3 = 0,
|
||||
ScoreUi2 = 1,
|
||||
ScoreUi6 = 1,
|
||||
LastPlayTime = DateTime.Now
|
||||
})
|
||||
.ToList();
|
||||
|
||||
cardSqLiteConnection.InsertOrIgnoreAll(detailList);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region OnlineMatchingImplementation
|
||||
|
||||
private static string StartOnlineMatching(long cardId, string xmlData)
|
||||
{
|
||||
var reader = new ChoXmlReader<OnlineMatchingEntry>(new StringReader(xmlData)).WithXPath(Configs.DATA_XPATH);
|
||||
var entry = reader.Read();
|
||||
|
||||
entry.CardId = cardId;
|
||||
entry.MatchingId = 0xDEADBEEF;
|
||||
entry.EntryNo = OnlineMatchingEntries[0xDEADBEEF].Count;
|
||||
entry.EntryStart = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
entry.MatchingTimeout = 20;
|
||||
entry.MatchingRemainingTime = 3;
|
||||
entry.MatchingWaitTime = 10;
|
||||
entry.Status = 1;
|
||||
entry.Dump().Info();
|
||||
var entries = OnlineMatchingEntries[0xDEADBEEF];
|
||||
var existing = entries.Find(matchingEntry => matchingEntry.CardId == cardId);
|
||||
if (existing is not null)
|
||||
{
|
||||
"Card id already exist in entry! Previous match is not cleaned up! Clearing now".Warn();
|
||||
entries.Clear();
|
||||
}
|
||||
OnlineMatchingEntries[0xDEADBEEF].Add(entry);
|
||||
|
||||
return GenerateRecordsXml(OnlineMatchingEntries[0xDEADBEEF], Configs.ONLINE_MATCHING_XPATH);
|
||||
}
|
||||
|
||||
private static string UpdateOnlineMatching(long cardId, string xmlData)
|
||||
{
|
||||
var reader = new ChoXmlReader<OnlineMatchingUpdateData>(new StringReader(xmlData));
|
||||
var data = reader.Read();
|
||||
var entries = OnlineMatchingEntries[0xDEADBEEF];
|
||||
var entry = entries.Find(matchingEntry => matchingEntry.CardId == cardId);
|
||||
if (entry is null)
|
||||
{
|
||||
throw new HttpException(400,"Entry for this card id does not exist!");
|
||||
}
|
||||
if (data.Action != 0)
|
||||
{
|
||||
$"Action is {data.Action}".Info();
|
||||
}
|
||||
|
||||
entry.MessageId = data.MessageId;
|
||||
if (entry.MatchingRemainingTime <= 0)
|
||||
{
|
||||
entry.Status = 3;
|
||||
entry.Dump().Info();
|
||||
return GenerateRecordsXml(OnlineMatchingEntries[0xDEADBEEF], Configs.ONLINE_MATCHING_XPATH);
|
||||
}
|
||||
|
||||
entry.MatchingRemainingTime--;
|
||||
entry.Dump().Info();
|
||||
|
||||
return GenerateRecordsXml(OnlineMatchingEntries[0xDEADBEEF], Configs.ONLINE_MATCHING_XPATH);
|
||||
}
|
||||
|
||||
private static string UploadOnlineMatchingResult(long cardId, string xmlData)
|
||||
{
|
||||
var reader = new ChoXmlReader<OnlineMatchingResultData>(new StringReader(xmlData));
|
||||
var data = reader.Read();
|
||||
|
||||
var entries = OnlineMatchingEntries[0xDEADBEEF];
|
||||
var entry = entries.Find(matchingEntry => matchingEntry.CardId == cardId);
|
||||
if (entry is null)
|
||||
{
|
||||
throw new HttpException(400,"Entry for this card id does not exist!");
|
||||
}
|
||||
|
||||
if (entry.MatchingId != data.MatchingId)
|
||||
{
|
||||
throw new HttpException(400,"Matching Id mismatch!");
|
||||
}
|
||||
|
||||
OnlineMatchingEntries[0xDEADBEEF].Remove(entry);
|
||||
var result = new OnlineMatchingResult
|
||||
{
|
||||
Status = 1
|
||||
};
|
||||
return GenerateSingleXml(result, Configs.ONLINE_BATTLE_RESULT_XPATH);
|
||||
}
|
||||
#endregion
|
||||
|
||||
private enum CardRequestType
|
||||
@@ -510,8 +724,8 @@ public class CardServiceController : WebApiController
|
||||
ReadTotalTrophy = 8468,
|
||||
|
||||
// Sessions
|
||||
SessionGet = 401,
|
||||
SessionStart = 402,
|
||||
GetSession = 401,
|
||||
StartSession = 402,
|
||||
|
||||
// Write data
|
||||
WriteCard = 771,
|
||||
@@ -525,6 +739,37 @@ public class CardServiceController : WebApiController
|
||||
WriteCoin = 980,
|
||||
WriteSkin = 933,
|
||||
WriteUnlockKeynum = 1020,
|
||||
WriteSoundEffect = 8969
|
||||
WriteSoundEffect = 8969,
|
||||
|
||||
// Online matching
|
||||
StartOnlineMatching = 8705,
|
||||
UpdateOnlineMatching = 8961,
|
||||
UploadOnlineMatchingResult = 8709
|
||||
}
|
||||
|
||||
private enum Command
|
||||
{
|
||||
CardReadRequest = 256,
|
||||
CardWriteRequest = 768,
|
||||
RegisterRequest = 512,
|
||||
ReissueRequest = 1536
|
||||
}
|
||||
|
||||
private enum ReturnCode
|
||||
{
|
||||
/// <summary>
|
||||
/// 処理は正常に完了しました in debug string
|
||||
/// </summary>
|
||||
Ok = 1,
|
||||
|
||||
/// <summary>
|
||||
/// 未登録のカードです in debug string
|
||||
/// </summary>
|
||||
CardNotRegistered = 23,
|
||||
|
||||
/// <summary>
|
||||
/// 再発行予約がありません in debug string
|
||||
/// </summary>
|
||||
NotReissue = 27
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ public class IncomServiceController : WebApiController
|
||||
public string IncomService()
|
||||
{
|
||||
HttpContext.Response.ContentType = MediaTypeNames.Text.Plain;
|
||||
HttpContext.Response.ContentEncoding = Encoding.Default;
|
||||
HttpContext.Response.ContentEncoding = new UTF8Encoding(false);
|
||||
HttpContext.Response.KeepAlive = true;
|
||||
|
||||
return "1+1";
|
||||
@@ -24,7 +24,7 @@ public class IncomServiceController : WebApiController
|
||||
public string IncomAllService()
|
||||
{
|
||||
HttpContext.Response.ContentType = MediaTypeNames.Text.Plain;
|
||||
HttpContext.Response.ContentEncoding = Encoding.Default;
|
||||
HttpContext.Response.ContentEncoding = new UTF8Encoding(false);
|
||||
HttpContext.Response.KeepAlive = true;
|
||||
|
||||
return "1+1";
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
using System.Net.Mime;
|
||||
using System.Text;
|
||||
using EmbedIO;
|
||||
using EmbedIO.Routing;
|
||||
using EmbedIO.WebApi;
|
||||
using GCLocalServerRewrite.common;
|
||||
using GCLocalServerRewrite.models;
|
||||
using SQLite.Net2;
|
||||
using Swan;
|
||||
using Swan.Logging;
|
||||
|
||||
namespace GCLocalServerRewrite.controllers;
|
||||
|
||||
public class OptionServiceController : WebApiController
|
||||
{
|
||||
|
||||
private readonly SQLiteConnection cardSqLiteConnection;
|
||||
|
||||
public OptionServiceController()
|
||||
{
|
||||
cardSqLiteConnection = DatabaseHelper.ConnectDatabase(Configs.SETTINGS.CardDbName);
|
||||
}
|
||||
|
||||
[Route(HttpVerbs.Get, "/PlayInfo.php")]
|
||||
public string OptionService([QueryField("card_id")] long cardId)
|
||||
{
|
||||
|
||||
HttpContext.Response.ContentType = MediaTypeNames.Text.Plain;
|
||||
HttpContext.Response.ContentEncoding = new UTF8Encoding(false);
|
||||
HttpContext.Response.KeepAlive = true;
|
||||
|
||||
return "1\n" +
|
||||
$"{GetPlayCount(cardId)}";
|
||||
}
|
||||
|
||||
private int GetPlayCount(long cardId)
|
||||
{
|
||||
var record = cardSqLiteConnection.Table<CardPlayCount>().Where(count => count.CardId == cardId);
|
||||
|
||||
if (!record.Any())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var now = DateTime.Now;
|
||||
var data = record.First();
|
||||
var lastPlayedTime = data.LastPlayed;
|
||||
|
||||
if (now <= lastPlayedTime)
|
||||
{
|
||||
$"Current time {now} is less than or equal to last played time! Clock skew detected!".Warn();
|
||||
return 0;
|
||||
}
|
||||
|
||||
DateTime start;
|
||||
DateTime end;
|
||||
if (now.Hour >= 8)
|
||||
{
|
||||
start = DateTime.Today.AddHours(8);
|
||||
end = start.AddHours(24);
|
||||
}
|
||||
else
|
||||
{
|
||||
end = DateTime.Today.AddHours(8);
|
||||
start = end.AddHours(-24);
|
||||
}
|
||||
|
||||
return lastPlayedTime.IsBetween(start, end) ? data.PlayCount : 0;
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ using EmbedIO.Routing;
|
||||
using EmbedIO.WebApi;
|
||||
using GCLocalServerRewrite.common;
|
||||
using GCLocalServerRewrite.models;
|
||||
using SQLite.Net2;
|
||||
using Swan;
|
||||
using Swan.Logging;
|
||||
// ReSharper disable UnusedMember.Global
|
||||
@@ -15,16 +16,26 @@ namespace GCLocalServerRewrite.controllers;
|
||||
|
||||
public class RankController : WebApiController
|
||||
{
|
||||
private readonly SQLiteConnection cardSqLiteConnection;
|
||||
private readonly SQLiteConnection musicSqLiteConnection;
|
||||
|
||||
public RankController()
|
||||
{
|
||||
cardSqLiteConnection = DatabaseHelper.ConnectDatabase(Configs.SETTINGS.CardDbName);
|
||||
musicSqLiteConnection = DatabaseHelper.ConnectDatabase(Configs.SETTINGS.MusicDbName);
|
||||
}
|
||||
|
||||
|
||||
[Route(HttpVerbs.Get, "/ranking.php")]
|
||||
public string Rank([QueryField("cmd_type")] int type)
|
||||
{
|
||||
HttpContext.Response.ContentType = MediaTypeNames.Application.Octet;
|
||||
HttpContext.Response.ContentEncoding = Encoding.Default;
|
||||
HttpContext.Response.ContentEncoding = new UTF8Encoding(false);
|
||||
HttpContext.Response.KeepAlive = true;
|
||||
|
||||
if (!Enum.IsDefined(typeof(RankType), type))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(type));
|
||||
throw new ArgumentOutOfRangeException(nameof(type), type, "Rank type out of range");
|
||||
}
|
||||
|
||||
var requestType = (RankType)type;
|
||||
@@ -41,7 +52,7 @@ public class RankController : WebApiController
|
||||
case RankType.PlayNumRank:
|
||||
$"Getting rank request, type is {requestType}".Info();
|
||||
|
||||
return ConstructResponse(RankTemp("play_num_rank"));
|
||||
return ConstructResponse(PlayNumRank());
|
||||
case RankType.EventRank:
|
||||
$"Getting rank request, type is {requestType}".Info();
|
||||
|
||||
@@ -97,6 +108,64 @@ public class RankController : WebApiController
|
||||
return stringWriter.ToString();
|
||||
}
|
||||
|
||||
private string PlayNumRank()
|
||||
{
|
||||
var rankStatus = new RankStatus
|
||||
{
|
||||
Rows = 30,
|
||||
TableName = "play_num_rank",
|
||||
Status = 1,
|
||||
EndDate = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
|
||||
};
|
||||
|
||||
var playNumRankContainer = new PlayNumRankContainer
|
||||
{
|
||||
PlayNumRankRecords = GetPlayNumRankRecords(),
|
||||
RankStatus = rankStatus
|
||||
};
|
||||
|
||||
var sb = new StringBuilder();
|
||||
|
||||
using (var writer = new ChoXmlWriter<PlayNumRankContainer>(sb))
|
||||
{
|
||||
writer.Configuration.UseXmlSerialization = true;
|
||||
writer.Configuration.OmitXmlDeclaration = false;
|
||||
writer.Configuration.IgnoreRootName = true;
|
||||
writer.Write(playNumRankContainer);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private List<PlayNumRankRecord> GetPlayNumRankRecords()
|
||||
{
|
||||
var records = new List<PlayNumRankRecord>();
|
||||
var musics = musicSqLiteConnection.Table<Music>().ToList().OrderBy(arg => Guid.NewGuid()).Take(30).ToList();
|
||||
|
||||
for (var i = 0; i < musics.Count; i++)
|
||||
{
|
||||
var music = musics[i];
|
||||
var index = i + 1;
|
||||
var record = new PlayNumRankRecord
|
||||
{
|
||||
Id = index,
|
||||
Rank = index,
|
||||
Rank2 = index + 1,
|
||||
PrevRank = musics.Count - i,
|
||||
PrevRank2 = index + 1,
|
||||
Artist = music.Artist,
|
||||
Pcol1 = music.MusicId,
|
||||
ScoreBi1 = index,
|
||||
Title = music.Title
|
||||
};
|
||||
records.Add(record);
|
||||
}
|
||||
|
||||
|
||||
return records;
|
||||
}
|
||||
|
||||
|
||||
private enum RankType
|
||||
{
|
||||
GlobalRank = 4119,
|
||||
|
||||
@@ -13,7 +13,7 @@ public class ResponeServiceController : WebApiController
|
||||
public string ResponeService()
|
||||
{
|
||||
HttpContext.Response.ContentType = MediaTypeNames.Text.Plain;
|
||||
HttpContext.Response.ContentEncoding = Encoding.Default;
|
||||
HttpContext.Response.ContentEncoding = new UTF8Encoding(false);
|
||||
HttpContext.Response.KeepAlive = true;
|
||||
|
||||
return "1";
|
||||
|
||||
@@ -15,11 +15,13 @@ namespace GCLocalServerRewrite.controllers;
|
||||
|
||||
public class ServerController : WebApiController
|
||||
{
|
||||
private static readonly string DataUrl = $"https://{Configs.SETTINGS.ServerIp}/{Configs.STATIC_FOLDER}/{Configs.SETTINGS.EventFolder}";
|
||||
|
||||
[Route(HttpVerbs.Get, "/certify.php")]
|
||||
public string Certify([QueryData] NameValueCollection parameters)
|
||||
{
|
||||
HttpContext.Response.ContentType = MediaTypeNames.Text.Plain;
|
||||
HttpContext.Response.ContentEncoding = Encoding.Default;
|
||||
HttpContext.Response.ContentEncoding = new UTF8Encoding(false);
|
||||
HttpContext.Response.KeepAlive = true;
|
||||
|
||||
var gid = parameters["gid"];
|
||||
@@ -63,14 +65,14 @@ public class ServerController : WebApiController
|
||||
md5.ComputeHash(Encoding.UTF8.GetBytes(gid)).Select(b => b.ToString("x2")));
|
||||
|
||||
|
||||
var response = "host=card_id=7020392000147361\n" +
|
||||
var response = $"host=card_id=7020392000147361,relay_addr={Configs.SETTINGS.RelayServer},relay_port={Configs.SETTINGS.RelayPort}\n" +
|
||||
"no=1337\n" +
|
||||
"name=123\n" +
|
||||
"pref=nesys\n" +
|
||||
"addr=nesys@home\n" +
|
||||
"x-next-time=15\n" +
|
||||
$"x-img=http://localhost{Configs.STATIC_BASE_ROUTE}/news.png\n" +
|
||||
$"x-ranking=http://localhost{Configs.RANK_BASE_ROUTE}/ranking.php\n" +
|
||||
$"x-img=http://{Configs.SETTINGS.ServerIp}{Configs.STATIC_BASE_ROUTE}/news.png\n" +
|
||||
$"x-ranking=http://{Configs.SETTINGS.ServerIp}{Configs.RANK_BASE_ROUTE}/ranking.php\n" +
|
||||
$"ticket={ticket}";
|
||||
|
||||
return response;
|
||||
@@ -80,7 +82,7 @@ public class ServerController : WebApiController
|
||||
public string Cursel()
|
||||
{
|
||||
HttpContext.Response.ContentType = MediaTypeNames.Text.Plain;
|
||||
HttpContext.Response.ContentEncoding = Encoding.Default;
|
||||
HttpContext.Response.ContentEncoding = new UTF8Encoding(false);
|
||||
HttpContext.Response.KeepAlive = true;
|
||||
|
||||
return "1\n";
|
||||
@@ -90,18 +92,34 @@ public class ServerController : WebApiController
|
||||
public string Data()
|
||||
{
|
||||
HttpContext.Response.ContentType = MediaTypeNames.Text.Plain;
|
||||
HttpContext.Response.ContentEncoding = Encoding.Default;
|
||||
HttpContext.Response.ContentEncoding = new UTF8Encoding(false);
|
||||
HttpContext.Response.KeepAlive = true;
|
||||
|
||||
return "count=1\n" +
|
||||
"nexttime=0\n";
|
||||
var responseList = Configs.SETTINGS.ResponseData.ToList();
|
||||
var count = responseList.Count;
|
||||
var dataString = new StringBuilder();
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var data = responseList[i];
|
||||
var fileUrl = data.FileName;
|
||||
if (Configs.SETTINGS.DownloadEvents)
|
||||
{
|
||||
fileUrl = data.FileName.StartsWith("/") ? $"{DataUrl}{data.FileName}" : $"{DataUrl}/{data.FileName}";
|
||||
}
|
||||
dataString.Append($"{i},{fileUrl},{data.NotBeforeUnixTime},{data.NotAfterUnixTime},{data.Md5},{data.Index}");
|
||||
dataString.Append('\n');
|
||||
}
|
||||
|
||||
return $"count={count}\n" +
|
||||
"nexttime=1\n" +
|
||||
dataString.ToString().TrimEnd('\n');
|
||||
}
|
||||
|
||||
[Route(HttpVerbs.Get, "/gameinfo.php")]
|
||||
public string GameInfo()
|
||||
{
|
||||
HttpContext.Response.ContentType = MediaTypeNames.Text.Plain;
|
||||
HttpContext.Response.ContentEncoding = Encoding.Default;
|
||||
HttpContext.Response.ContentEncoding = new UTF8Encoding(false);
|
||||
HttpContext.Response.KeepAlive = true;
|
||||
|
||||
return "0\n" +
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
using EmbedIO;
|
||||
using EmbedIO.Routing;
|
||||
using EmbedIO.WebApi;
|
||||
using Swan.Logging;
|
||||
|
||||
namespace GCLocalServerRewrite.controllers
|
||||
{
|
||||
// TODO: Add proper update check response
|
||||
public class UpdateController : WebApiController
|
||||
{
|
||||
[Route(HttpVerbs.Get, "/check.php")]
|
||||
public string CheckUpdate()
|
||||
{
|
||||
var parameters = HttpContext.GetRequestQueryData();
|
||||
foreach (var key in parameters.AllKeys)
|
||||
{
|
||||
$"Key {key}: {parameters[key]}".Info();
|
||||
}
|
||||
|
||||
HttpContext.Response.StatusCode = 404;
|
||||
return "Not found";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ public class UploadServiceController : WebApiController
|
||||
public string UploadService()
|
||||
{
|
||||
HttpContext.Response.ContentType = MediaTypeNames.Text.Plain;
|
||||
HttpContext.Response.ContentEncoding = Encoding.Default;
|
||||
HttpContext.Response.ContentEncoding = new UTF8Encoding(false);
|
||||
HttpContext.Response.KeepAlive = true;
|
||||
|
||||
return "1\n" +
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -11,10 +11,10 @@ public class Avatar : Record , IIdModel, ICardIdModel
|
||||
public int AvatarId { get; set; }
|
||||
|
||||
[XmlElement("created")]
|
||||
public string? Created { get; set; } = "1";
|
||||
public string Created { get; set; } = "1";
|
||||
|
||||
[XmlElement("modified")]
|
||||
public string? Modified { get; set; } = "1";
|
||||
public string Modified { get; set; } = "1";
|
||||
|
||||
[XmlElement("new_flag")]
|
||||
public int NewFlag { get; set; } = 1;
|
||||
|
||||
@@ -11,12 +11,12 @@ public class Card : ICardIdModel
|
||||
[Column("card_id")]
|
||||
[ChoXmlElementRecordField(FieldName = "card_id")]
|
||||
[XmlElement(ElementName = "card_id")]
|
||||
public long? CardId { get; set; }
|
||||
public long CardId { get; set; }
|
||||
|
||||
[Column("player_name")]
|
||||
[ChoXmlElementRecordField(FieldName = "player_name")]
|
||||
[XmlElement(ElementName = "player_name")]
|
||||
public string? PlayerName { get; set; }
|
||||
public string PlayerName { get; set; } = string.Empty;
|
||||
|
||||
[Column("score_i1")]
|
||||
[ChoXmlElementRecordField(FieldName = "score_i1")]
|
||||
@@ -41,17 +41,17 @@ public class Card : ICardIdModel
|
||||
[Column("achieve_status")]
|
||||
[ChoXmlElementRecordField(FieldName = "achieve_status")]
|
||||
[XmlElement("achieve_status")]
|
||||
public string? AchieveStatus { get; set; } = string.Empty;
|
||||
public string AchieveStatus { get; set; } = string.Empty;
|
||||
|
||||
[Column("created")]
|
||||
[ChoXmlElementRecordField(FieldName = "created")]
|
||||
[XmlElement("created")]
|
||||
public string Created { get; set; } = "1";
|
||||
public string Created { get; set; } = "2017-01-01 08:00:00";
|
||||
|
||||
[Column("updated")]
|
||||
[ChoXmlElementRecordField(FieldName = "updated")]
|
||||
[XmlElement("updated")]
|
||||
public string Updated { get; set; } = "1";
|
||||
[Column("modified")]
|
||||
[ChoXmlElementRecordField(FieldName = "modified")]
|
||||
[XmlElement("modified")]
|
||||
public string Modified { get; set; } = "2017-01-01 08:00:00";
|
||||
|
||||
public void SetCardId(long cardId)
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@ public class CardBData : ICardIdModel
|
||||
[Column("card_id")]
|
||||
[ChoXmlElementRecordField(FieldName = "card_id")]
|
||||
[XmlElement(ElementName = "card_id")]
|
||||
public long? CardId { get; set; }
|
||||
public long CardId { get; set; }
|
||||
|
||||
[Column("bdata")]
|
||||
[ChoXmlElementRecordField(FieldName = "bdata")]
|
||||
|
||||
@@ -90,6 +90,10 @@ public class CardDetail : Record, ICardIdModel
|
||||
[ChoXmlElementRecordField(FieldName = "fcol3")]
|
||||
[XmlElement("fcol3")]
|
||||
public int Fcol3 { get; set; }
|
||||
|
||||
[Column("last_play_time")]
|
||||
[XmlIgnore]
|
||||
public DateTime LastPlayTime { get; set; } = DateTime.MinValue;
|
||||
|
||||
public void SetCardId(long cardId)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
using SQLite.Net2;
|
||||
|
||||
namespace GCLocalServerRewrite.models;
|
||||
|
||||
public class CardPlayCount
|
||||
{
|
||||
[PrimaryKey]
|
||||
[Column("card_id")]
|
||||
public long CardId { get; set; }
|
||||
|
||||
[Column("play_count")]
|
||||
public int PlayCount { get; set; }
|
||||
|
||||
[Column("last_played_time")]
|
||||
public DateTime LastPlayed { get; set; }
|
||||
}
|
||||
@@ -17,8 +17,8 @@ public class Coin
|
||||
public int MonthlyCoins { get; set; }
|
||||
|
||||
[XmlElement("created")]
|
||||
public string? Created { get; set; } = "1";
|
||||
public string Created { get; set; } = "1";
|
||||
|
||||
[XmlElement("modified")]
|
||||
public string? Modified { get; set; } = "1";
|
||||
public string Modified { get; set; } = "1";
|
||||
}
|
||||
@@ -14,13 +14,13 @@ public class Item : Record, IIdModel, ICardIdModel
|
||||
public int ItemNum { get; set; } = 90;
|
||||
|
||||
[XmlElement("created")]
|
||||
public string? Created { get; set; } = "1";
|
||||
public string Created { get; set; } = "1";
|
||||
|
||||
[XmlElement("modified")]
|
||||
public string? Modified { get; set; } = "1";
|
||||
public string Modified { get; set; } = "1";
|
||||
|
||||
[XmlElement("new_flag")]
|
||||
public int NewFlag { get; set; } = 1;
|
||||
public int NewFlag { get; set; }
|
||||
|
||||
[XmlElement("use_flag")]
|
||||
public int UseFlag { get; set; } = 1;
|
||||
|
||||
@@ -12,20 +12,26 @@ public class Music : Record
|
||||
public int MusicId { get; set; }
|
||||
|
||||
[Column("title")]
|
||||
[XmlElement(ElementName = "title", IsNullable = true)]
|
||||
public string? Title { get; set; }
|
||||
[XmlElement(ElementName = "title")]
|
||||
public string? Title { get; set; } = string.Empty;
|
||||
|
||||
[Column("artist")]
|
||||
[XmlElement(ElementName = "artist", IsNullable = true)]
|
||||
public string? Artist { get; set; }
|
||||
[XmlElement(ElementName = "artist")]
|
||||
public string? Artist
|
||||
{
|
||||
get => _artist;
|
||||
set => _artist = value ?? string.Empty;
|
||||
}
|
||||
|
||||
private string? _artist = string.Empty;
|
||||
|
||||
[Column("release_date")]
|
||||
[XmlElement(ElementName = "release_date", IsNullable = true)]
|
||||
public string? ReleaseDate { get; set; }
|
||||
[XmlElement(ElementName = "release_date")]
|
||||
public string ReleaseDate { get; set; } = "2013-01-01 08:00:00";
|
||||
|
||||
[Column("end_date")]
|
||||
[XmlElement(ElementName = "end_date", IsNullable = true)]
|
||||
public string? EndDate { get; set; }
|
||||
[XmlElement(ElementName = "end_date")]
|
||||
public string EndDate { get; set; } = "2030-01-01 08:00:00";
|
||||
|
||||
[Column("new_flag")]
|
||||
[XmlElement("new_flag")]
|
||||
@@ -34,4 +40,8 @@ public class Music : Record
|
||||
[Column("use_flag")]
|
||||
[XmlElement("use_flag")]
|
||||
public int UseFlag { get; set; }
|
||||
|
||||
[Ignore]
|
||||
[XmlElement("calc_flag")]
|
||||
public uint CalcFlag { get; set; } = 2;
|
||||
}
|
||||
@@ -11,26 +11,6 @@ public class MusicAou : Record
|
||||
[XmlElement("music_id")]
|
||||
public int MusicId { get; set; }
|
||||
|
||||
[Column("title")]
|
||||
[XmlElement(ElementName = "title", IsNullable = true)]
|
||||
public string? Title { get; set; }
|
||||
|
||||
[Column("artist")]
|
||||
[XmlElement(ElementName = "artist", IsNullable = true)]
|
||||
public string? Artist { get; set; }
|
||||
|
||||
[Column("release_date")]
|
||||
[XmlElement(ElementName = "release_date", IsNullable = true)]
|
||||
public string? ReleaseDate { get; set; }
|
||||
|
||||
[Column("end_date")]
|
||||
[XmlElement(ElementName = "end_date", IsNullable = true)]
|
||||
public string? EndDate { get; set; }
|
||||
|
||||
[Column("new_flag")]
|
||||
[XmlElement("new_flag")]
|
||||
public int NewFlag { get; set; }
|
||||
|
||||
[Column("use_flag")]
|
||||
[XmlElement("use_flag")]
|
||||
public int UseFlag { get; set; }
|
||||
|
||||
@@ -11,13 +11,13 @@ public class Navigator : Record, IIdModel, ICardIdModel
|
||||
public int NavigatorId { get; set; }
|
||||
|
||||
[XmlElement("created")]
|
||||
public string? Created { get; set; } = "1";
|
||||
public string Created { get; set; } = "1";
|
||||
|
||||
[XmlElement("modified")]
|
||||
public string? Modified { get; set; } = "1";
|
||||
public string Modified { get; set; } = "1";
|
||||
|
||||
[XmlElement("new_flag")]
|
||||
public int NewFlag { get; set; } = 1;
|
||||
public int NewFlag { get; set; }
|
||||
|
||||
[XmlElement("use_flag")]
|
||||
public int UseFlag { get; set; } = 1;
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
using System.Xml.Serialization;
|
||||
using ChoETL;
|
||||
|
||||
namespace GCLocalServerRewrite.models;
|
||||
|
||||
public class OnlineMatchingEntry: Record
|
||||
{
|
||||
[XmlElement(ElementName = "machine_id")]
|
||||
[ChoXmlElementRecordField(FieldName = "machine_id")]
|
||||
public long MachineId { get; set; }
|
||||
|
||||
[XmlElement(ElementName = "event_id")]
|
||||
[ChoXmlElementRecordField(FieldName = "event_id")]
|
||||
public long EventId { get; set; }
|
||||
|
||||
[XmlElement(ElementName = "matching_id")]
|
||||
[ChoXmlElementRecordField(FieldName = "matching_id")]
|
||||
public long MatchingId { get; set; }
|
||||
|
||||
[XmlElement(ElementName = "entry_no")]
|
||||
[ChoXmlElementRecordField(FieldName = "entry_no")]
|
||||
public long EntryNo { get; set; }
|
||||
|
||||
[XmlElement(ElementName = "entry_start")]
|
||||
[ChoXmlElementRecordField(FieldName = "entry_start")]
|
||||
public string EntryStart { get; set; } = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
[XmlElement(ElementName = "status")]
|
||||
[ChoXmlElementRecordField(FieldName = "status")]
|
||||
public long Status { get; set; } = 1;
|
||||
|
||||
[XmlElement(ElementName = "card_id")]
|
||||
[ChoXmlElementRecordField(FieldName = "card_id")]
|
||||
public long CardId { get; set; }
|
||||
|
||||
[XmlElement(ElementName = "player_name")]
|
||||
[ChoXmlElementRecordField(FieldName = "player_name")]
|
||||
public string PlayerName { get; set; } = string.Empty;
|
||||
|
||||
[XmlElement(ElementName = "avatar_id")]
|
||||
[ChoXmlElementRecordField(FieldName = "avatar_id")]
|
||||
public long AvatarId { get; set; }
|
||||
|
||||
[XmlElement(ElementName = "title_id")]
|
||||
[ChoXmlElementRecordField(FieldName = "title_id")]
|
||||
public long TitleId { get; set; }
|
||||
|
||||
[XmlElement(ElementName = "class_id")]
|
||||
[ChoXmlElementRecordField(FieldName = "class_id")]
|
||||
public long ClassId { get; set; }
|
||||
|
||||
[XmlElement(ElementName = "group_id")]
|
||||
[ChoXmlElementRecordField(FieldName = "group_id")]
|
||||
public long GroupId { get; set; }
|
||||
|
||||
[XmlElement(ElementName = "tenpo_id")]
|
||||
[ChoXmlElementRecordField(FieldName = "tenpo_id")]
|
||||
public long TenpoId { get; set; }
|
||||
|
||||
[XmlElement(ElementName = "tenpo_name")]
|
||||
[ChoXmlElementRecordField(FieldName = "tenpo_name")]
|
||||
public string TenpoName { get; set; } = "1337";
|
||||
|
||||
[XmlElement(ElementName = "pref_id")]
|
||||
[ChoXmlElementRecordField(FieldName = "pref_id")]
|
||||
public long PrefId { get; set; }
|
||||
|
||||
[XmlElement(ElementName = "pref")]
|
||||
[ChoXmlElementRecordField(FieldName = "pref")]
|
||||
public string Pref { get; set; } = "nesys";
|
||||
|
||||
[XmlElement(ElementName = "message_id")]
|
||||
[ChoXmlElementRecordField(FieldName = "message_id")]
|
||||
public long MessageId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Communication timeout?
|
||||
/// </summary>
|
||||
[XmlElement(ElementName = "matching_timeout")]
|
||||
[ChoXmlElementRecordField(FieldName = "matching_timeout")]
|
||||
public long MatchingTimeout { get; set; } = 99;
|
||||
|
||||
/// <summary>
|
||||
/// Wait time
|
||||
/// </summary>
|
||||
[XmlElement(ElementName = "matching_wait_time")]
|
||||
[ChoXmlElementRecordField(FieldName = "matching_wait_time")]
|
||||
public long MatchingWaitTime { get; set; } = 10;
|
||||
|
||||
/// <summary>
|
||||
/// Seems not used
|
||||
/// </summary>
|
||||
[XmlElement(ElementName = "matching_remaining_time")]
|
||||
[ChoXmlElementRecordField(FieldName = "matching_remaining_time")]
|
||||
public long MatchingRemainingTime { get; set; } = 89;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace GCLocalServerRewrite.models;
|
||||
|
||||
public class OnlineMatchingResult
|
||||
{
|
||||
[XmlElement(ElementName = "status")]
|
||||
public int Status { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using ChoETL;
|
||||
|
||||
namespace GCLocalServerRewrite.models;
|
||||
|
||||
[ChoXmlRecordObject(XPath = "/root/data")]
|
||||
public class OnlineMatchingResultData
|
||||
{
|
||||
[ChoXmlElementRecordField(FieldName = "event_id")]
|
||||
public long EventId { get; set; }
|
||||
|
||||
[ChoXmlElementRecordField(FieldName = "matching_id")]
|
||||
public long MatchingId { get; set; }
|
||||
|
||||
[ChoXmlElementRecordField(FieldName = "class_id")]
|
||||
public long ClassId { get; set; }
|
||||
|
||||
[ChoXmlElementRecordField(FieldName = "group_id")]
|
||||
public long GroupId { get; set; }
|
||||
|
||||
[ChoXmlElementRecordField(FieldName = "result_score")]
|
||||
public long ResultScore { get; set; }
|
||||
|
||||
[ChoXmlElementRecordField(FieldName = "result_star")]
|
||||
public long ResultStar { get; set; }
|
||||
|
||||
[ChoXmlElementRecordField(FieldName = "result_rank")]
|
||||
public long ResultRank { get; set; }
|
||||
|
||||
[ChoXmlElementRecordField(FieldName = "music_id_1st")]
|
||||
public long MusicIdFirst { get; set; }
|
||||
|
||||
[ChoXmlElementRecordField(FieldName = "music_id_2nd")]
|
||||
public long MusicIdSecond { get; set; }
|
||||
|
||||
[ChoXmlElementRecordField(FieldName = "music_id_3rd")]
|
||||
public long MusicIdThird { get; set; }
|
||||
|
||||
[ChoXmlElementRecordField(FieldName = "difficulty_lv_1st")]
|
||||
public long DifficultyFirst { get; set; }
|
||||
|
||||
[ChoXmlElementRecordField(FieldName = "difficulty_lv_2nd")]
|
||||
public long DifficultySecond { get; set; }
|
||||
|
||||
[ChoXmlElementRecordField(FieldName = "difficulty_lv_3rd")]
|
||||
public long DifficultyThird { get; set; }
|
||||
|
||||
[ChoXmlElementRecordField(FieldName = "item_id_1st")]
|
||||
public long ItemIdFirst { get; set; }
|
||||
|
||||
[ChoXmlElementRecordField(FieldName = "item_id_2nd")]
|
||||
public long ItemIdSecond { get; set; }
|
||||
|
||||
[ChoXmlElementRecordField(FieldName = "item_id_3rd")]
|
||||
public long ItemIdThird { get; set; }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using ChoETL;
|
||||
|
||||
namespace GCLocalServerRewrite.models;
|
||||
|
||||
[ChoXmlRecordObject(XPath = "/root/data")]
|
||||
// ReSharper disable once ClassNeverInstantiated.Global
|
||||
public class OnlineMatchingUpdateData
|
||||
{
|
||||
[ChoXmlElementRecordField(FieldName = "action")]
|
||||
public long Action { get; set; }
|
||||
|
||||
[ChoXmlElementRecordField(FieldName = "event_id")]
|
||||
public long EventId { get; set; }
|
||||
|
||||
[ChoXmlElementRecordField(FieldName = "matching_id")]
|
||||
public long MatchingId { get; set; }
|
||||
|
||||
[ChoXmlElementRecordField(FieldName = "message_id")]
|
||||
public long MessageId { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace GCLocalServerRewrite.models;
|
||||
|
||||
[XmlRoot("root")]
|
||||
public class PlayNumRankContainer
|
||||
{
|
||||
[XmlArray("play_num_rank")]
|
||||
public List<PlayNumRankRecord> PlayNumRankRecords { get; set; } = new();
|
||||
|
||||
[XmlElement("ranking_status")]
|
||||
public RankStatus? RankStatus { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace GCLocalServerRewrite.models;
|
||||
|
||||
[XmlType("record")]
|
||||
public class PlayNumRankRecord
|
||||
{
|
||||
[XmlAttribute(AttributeName = "id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[XmlElement("rank")]
|
||||
public int Rank { get; set; }
|
||||
|
||||
[XmlElement("rank2")]
|
||||
public int Rank2 { get; set; }
|
||||
|
||||
[XmlElement("prev_rank")]
|
||||
public int PrevRank { get; set; }
|
||||
|
||||
[XmlElement("prev_rank2")]
|
||||
public int PrevRank2 { get; set; }
|
||||
|
||||
[XmlElement("pcol1")]
|
||||
public int Pcol1 { get; set; }
|
||||
|
||||
[XmlElement("score_bi1")]
|
||||
public int ScoreBi1 { get; set; }
|
||||
|
||||
[XmlElement("title")]
|
||||
public string? Title { get; set; }
|
||||
|
||||
[XmlElement("artist")]
|
||||
public string? Artist { get; set; }
|
||||
}
|
||||
@@ -2,16 +2,17 @@
|
||||
|
||||
namespace GCLocalServerRewrite.models;
|
||||
|
||||
[XmlType("ranking_status")]
|
||||
public class RankStatus
|
||||
{
|
||||
[XmlElement("table_name")]
|
||||
public string? TableName { get; set; }
|
||||
[XmlElement("table_name")]
|
||||
public string TableName { get; set; } = "rank_table";
|
||||
|
||||
[XmlElement("start_date")]
|
||||
public string StartDate { get; set; } = "2017-05-30";
|
||||
public string StartDate { get; set; } = "2021-05-30";
|
||||
|
||||
[XmlElement("end_date")]
|
||||
public string EndDate { get; set; } = "2018-05-30";
|
||||
public string EndDate { get; set; } = "2022-06-08";
|
||||
|
||||
[XmlElement("status")]
|
||||
public int Status { get; set; }
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
using System.Xml.Serialization;
|
||||
using ChoETL;
|
||||
using SQLite.Net2;
|
||||
|
||||
namespace GCLocalServerRewrite.models;
|
||||
|
||||
public class Record
|
||||
{
|
||||
[XmlAttribute(AttributeName = "id")]
|
||||
[Ignore]
|
||||
public int RecordId { get; set; }
|
||||
}
|
||||
@@ -7,11 +7,11 @@ public class Session
|
||||
[XmlElement(ElementName = "card_id")]
|
||||
public long CardId { get; set; }
|
||||
|
||||
[XmlElement(ElementName = "mac_addr")]
|
||||
public string? Mac { get; set; }
|
||||
[XmlElement(ElementName = "mac_addr")]
|
||||
public string Mac { get; set; } = "000000000000";
|
||||
|
||||
[XmlElement(ElementName = "session_id")]
|
||||
public string? SessionId { get; set; }
|
||||
public string SessionId { get; set; } = "12345678901234567890123456789012";
|
||||
|
||||
[XmlElement(ElementName = "expires")]
|
||||
public int Expires { get; set; }
|
||||
|
||||
@@ -11,13 +11,13 @@ public class Skin : Record, IIdModel, ICardIdModel
|
||||
public int SkinId { get; set; }
|
||||
|
||||
[XmlElement("created")]
|
||||
public string? Created { get; set; } = "1";
|
||||
public string Created { get; set; } = "1";
|
||||
|
||||
[XmlElement("modified")]
|
||||
public string? Modified { get; set; } = "1";
|
||||
public string Modified { get; set; } = "1";
|
||||
|
||||
[XmlElement("new_flag")]
|
||||
public int NewFlag { get; set; } = 1;
|
||||
public int NewFlag { get; set; }
|
||||
|
||||
[XmlElement("use_flag")]
|
||||
public int UseFlag { get; set; } = 1;
|
||||
|
||||
@@ -11,13 +11,13 @@ public class SoundEffect : Record, IIdModel, ICardIdModel
|
||||
public int SeId { get; set; }
|
||||
|
||||
[XmlElement("created")]
|
||||
public string? Created { get; set; } = "1";
|
||||
public string Created { get; set; } = "1";
|
||||
|
||||
[XmlElement("modified")]
|
||||
public string? Modified { get; set; } = "1";
|
||||
public string Modified { get; set; } = "1";
|
||||
|
||||
[XmlElement("new_flag")]
|
||||
public int NewFlag { get; set; } = 1;
|
||||
public int NewFlag { get; set; }
|
||||
|
||||
[XmlElement("use_flag")]
|
||||
public int UseFlag { get; set; } = 1;
|
||||
|
||||
@@ -11,13 +11,13 @@ public class Title : Record, IIdModel, ICardIdModel
|
||||
public int TitleId { get; set; }
|
||||
|
||||
[XmlElement("created")]
|
||||
public string? Created { get; set; } = "1";
|
||||
public string Created { get; set; } = "1";
|
||||
|
||||
[XmlElement("modified")]
|
||||
public string? Modified { get; set; } = "1";
|
||||
public string Modified { get; set; } = "1";
|
||||
|
||||
[XmlElement("new_flag")]
|
||||
public int NewFlag { get; set; } = 1;
|
||||
public int NewFlag { get; set; }
|
||||
|
||||
[XmlElement("use_flag")]
|
||||
public int UseFlag { get; set; } = 1;
|
||||
|
||||
@@ -26,8 +26,8 @@ public class UnlockKeynum : Record
|
||||
public int CashFlag { get; set; }
|
||||
|
||||
[XmlElement("created")]
|
||||
public string? Created { get; set; } = "1";
|
||||
public string Created { get; set; } = "1";
|
||||
|
||||
[XmlElement("modified")]
|
||||
public string? Modified { get; set; } = "1";
|
||||
public string Modified { get; set; } = "1";
|
||||
}
|
||||
@@ -14,16 +14,16 @@ public class UnlockReward : Record
|
||||
public int RewardType { get; set; }
|
||||
|
||||
[XmlElement(ElementName = "open_date")]
|
||||
public string? OpenDate { get; set; } = "2021-05-30";
|
||||
public string OpenDate { get; set; } = "2021-05-30";
|
||||
|
||||
[XmlElement(ElementName = "close_date")]
|
||||
public string? CloseDate { get; set; } = "2030-05-30";
|
||||
public string CloseDate { get; set; } = "2030-05-30";
|
||||
|
||||
[XmlElement(ElementName = "open_time")]
|
||||
public string? OpenTime { get; set; } = "00:00:01";
|
||||
public string OpenTime { get; set; } = "00:00:01";
|
||||
|
||||
[XmlElement(ElementName = "close_time")]
|
||||
public string? CloseTime { get; set; } = "23:59:59";
|
||||
public string CloseTime { get; set; } = "23:59:59";
|
||||
|
||||
[XmlElement(ElementName = "target_id")]
|
||||
public int TargetId { get; set; }
|
||||
@@ -50,8 +50,8 @@ public class UnlockReward : Record
|
||||
public long CloseUnixTime { get; set; } = 1906387199;
|
||||
|
||||
[XmlElement("created")]
|
||||
public string? Created { get; set; } = "1";
|
||||
public string Created { get; set; } = "1";
|
||||
|
||||
[XmlElement("modified")]
|
||||
public string? Modified { get; set; } = "1";
|
||||
public string Modified { get; set; } = "1";
|
||||
}
|
||||
@@ -2,17 +2,17 @@
|
||||
using EmbedIO.Actions;
|
||||
using EmbedIO.Files;
|
||||
using EmbedIO.WebApi;
|
||||
using GCLocalServerRewrite.backports;
|
||||
using GCLocalServerRewrite.common;
|
||||
using GCLocalServerRewrite.controllers;
|
||||
using GCLocalServerRewrite.models;
|
||||
using Swan.Logging;
|
||||
using System.Text;
|
||||
|
||||
namespace GCLocalServerRewrite.server;
|
||||
|
||||
public class Server
|
||||
{
|
||||
public static WebServer CreateWebServer(string[] urlPrefixes)
|
||||
public static WebServer CreateWebServer(IEnumerable<string> urlPrefixes)
|
||||
{
|
||||
InitializeDatabase();
|
||||
var cert = CertificateHelper.InitializeCertificate();
|
||||
@@ -22,32 +22,48 @@ public class Server
|
||||
.WithCertificate(cert)
|
||||
.WithMode(HttpListenerMode.EmbedIO))
|
||||
.WithLocalSessionManager()
|
||||
.WithCors(
|
||||
"http://unosquare.github.io,http://run.plnkr.co",
|
||||
"content-type, accept",
|
||||
"post")
|
||||
.WithWebApi(Configs.CARD_SERVICE_BASE_ROUTE, CustomResponseSerializer.None(true),
|
||||
.WithCors()
|
||||
.WithWebApi(Configs.API_BASE_ROUTE, module => module.WithController<ApiController>())
|
||||
.WithWebApi(Configs.CARD_SERVICE_BASE_ROUTE, ResponseSerializer.None(true),
|
||||
module => module.WithController<CardServiceController>())
|
||||
.WithWebApi(Configs.UPLOAD_SERVICE_BASE_ROUTE, CustomResponseSerializer.None(true),
|
||||
.WithWebApi(Configs.OPTION_SERVICE_BASE_ROUTE, ResponseSerializer.None(true),
|
||||
module => module.WithController<OptionServiceController>())
|
||||
.WithWebApi(Configs.UPLOAD_SERVICE_BASE_ROUTE, ResponseSerializer.None(true),
|
||||
module => module.WithController<UploadServiceController>())
|
||||
.WithWebApi(Configs.RESPONE_SERVICE_BASE_ROUTE, CustomResponseSerializer.None(true),
|
||||
.WithWebApi(Configs.RESPONE_SERVICE_BASE_ROUTE, ResponseSerializer.None(true),
|
||||
module => module.WithController<ResponeServiceController>())
|
||||
.WithWebApi(Configs.INCOM_SERVICE_BASE_ROUTE, CustomResponseSerializer.None(true),
|
||||
.WithWebApi(Configs.INCOM_SERVICE_BASE_ROUTE, ResponseSerializer.None(true),
|
||||
module => module.WithController<IncomServiceController>())
|
||||
.WithWebApi(Configs.ALIVE_BASE_ROUTE, CustomResponseSerializer.None(true),
|
||||
.WithWebApi(Configs.ALIVE_BASE_ROUTE, ResponseSerializer.None(true),
|
||||
module => module.WithController<AliveController>())
|
||||
.WithWebApi(Configs.SERVER_BASE_ROUTE, CustomResponseSerializer.None(true),
|
||||
.WithWebApi(Configs.SERVER_BASE_ROUTE, ResponseSerializer.None(true),
|
||||
module => module.WithController<ServerController>())
|
||||
.WithWebApi(Configs.RANK_BASE_ROUTE, CustomResponseSerializer.None(true),
|
||||
.WithWebApi(Configs.RANK_BASE_ROUTE, ResponseSerializer.None(true),
|
||||
module => module.WithController<RankController>())
|
||||
.WithWebApi(Configs.UPDATE_SERVICE_BASE_ROUTE, ResponseSerializer.None(true),
|
||||
module => module.WithController<UpdateController>())
|
||||
.WithStaticFolder(Configs.STATIC_BASE_ROUTE, PathHelper.HtmlRootPath, true, m => m
|
||||
.WithContentCaching(Configs.USE_FILE_CACHE))
|
||||
|
||||
.WithContentCaching(Configs.USE_FILE_CACHE))
|
||||
// Add static files after other modules to avoid conflicts
|
||||
.WithStaticFolder("/", PathHelper.HtmlRootPath, true, m => m
|
||||
.WithContentCaching(Configs.USE_FILE_CACHE))
|
||||
.WithStaticFolder("/", PathHelper.HtmlRootPath, true, m =>
|
||||
{
|
||||
m.WithContentCaching(Configs.USE_FILE_CACHE);
|
||||
m.OnMappingFailed = async (context, info) =>
|
||||
{
|
||||
var htmlContents = await File.ReadAllTextAsync(Path.Combine(PathHelper.HtmlRootPath, "index.html"));
|
||||
context.Response.StatusCode = 200;
|
||||
await context.SendStringAsync(htmlContents, "text/html", Encoding.UTF8);
|
||||
};
|
||||
})
|
||||
.WithModule(new ActionModule("/", HttpVerbs.Any,
|
||||
ctx => ctx.SendDataAsync(new { Message = "Error" })));
|
||||
server.AddCustomMimeType(".dll", "application/octet-stream");
|
||||
server.AddCustomMimeType(".blat", "application/octet-stream");
|
||||
server.AddCustomMimeType(".dat", "application/octet-stream");
|
||||
server.AddCustomMimeType(".json", "application/json");
|
||||
server.AddCustomMimeType(".wasm", "application/wasm");
|
||||
server.AddCustomMimeType(".woff", "application/font-woff");
|
||||
server.AddCustomMimeType(".woff2", "application/font-woff2");
|
||||
|
||||
// Listen for state changes.
|
||||
server.StateChanged += (_, e) => $"WebServer New State - {e.NewState}".Info();
|
||||
@@ -57,9 +73,9 @@ public class Server
|
||||
|
||||
private static void InitializeDatabase()
|
||||
{
|
||||
DatabaseHelper.InitializeLocalDatabase(Configs.CARD_DB_NAME,
|
||||
typeof(Card), typeof(CardBData), typeof(CardDetail));
|
||||
DatabaseHelper.InitializeLocalDatabase(Configs.MUSIC_DB_NAME,
|
||||
DatabaseHelper.InitializeLocalDatabase(Configs.SETTINGS.CardDbName,
|
||||
typeof(Card), typeof(CardBData), typeof(CardDetail), typeof(CardPlayCount));
|
||||
DatabaseHelper.InitializeLocalDatabase(Configs.SETTINGS.MusicDbName,
|
||||
typeof(Music), typeof(MusicAou), typeof(MusicExtra));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System.Net;
|
||||
|
||||
namespace GCRelayServer;
|
||||
|
||||
public class DictEntry
|
||||
{
|
||||
public List<EndPoint> EndPoints { get; set; } = new();
|
||||
|
||||
public DateTime LastAccessTime { get; set; } = DateTime.Now;
|
||||
|
||||
public void AddEndpoint(EndPoint endPoint, bool shouldClear = false)
|
||||
{
|
||||
if (shouldClear)
|
||||
{
|
||||
EndPoints.Clear();
|
||||
}
|
||||
if (EndPoints.Contains(endPoint))
|
||||
{
|
||||
return;
|
||||
}
|
||||
EndPoints.Add(endPoint);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BinarySerializer" Version="8.6.3-alpha" />
|
||||
<PackageReference Include="NetCoreServer" Version="6.2.0" />
|
||||
<PackageReference Include="Swan.Logging" Version="6.0.2-beta.69" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,59 @@
|
||||
using System.Net;
|
||||
using Swan.Logging;
|
||||
|
||||
namespace GCRelayServer
|
||||
{
|
||||
|
||||
public static class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
#if DEBUG
|
||||
ConsoleLogger.Instance.LogLevel = LogLevel.Debug;
|
||||
#endif
|
||||
// UDP server port
|
||||
var port = 3333;
|
||||
if (args.Length > 0)
|
||||
{
|
||||
port = int.Parse(args[0]);
|
||||
}
|
||||
|
||||
$"UDP server port: {port}".Info();
|
||||
|
||||
// Create a new UDP echo server
|
||||
var server = new RelayServer(IPAddress.Any, port);
|
||||
|
||||
// Start the server
|
||||
"Server starting...".Info();
|
||||
server.Start();
|
||||
"Server started".Info();
|
||||
|
||||
"Press Enter to stop the server or '!' to restart the server...".Info();
|
||||
|
||||
// Perform text input
|
||||
for (;;)
|
||||
{
|
||||
var line = Console.ReadLine();
|
||||
if (string.IsNullOrEmpty(line))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Restart the server
|
||||
if (line != "!")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
"Server restarting...".Info();
|
||||
server.Restart();
|
||||
"Server restarted".Info();
|
||||
}
|
||||
|
||||
// Stop the server
|
||||
"Server stopping...".Info();
|
||||
server.Stop();
|
||||
"Server stopped, press any key to close".Info();
|
||||
Console.ReadKey(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using BinarySerialization;
|
||||
|
||||
namespace GCRelayServer;
|
||||
|
||||
public class RelayPacket
|
||||
{
|
||||
[FieldOrder(0)]
|
||||
[FieldEndianness(Endianness.Big)]
|
||||
public ushort Magic;
|
||||
|
||||
[FieldOrder(1)]
|
||||
[FieldEndianness(Endianness.Big)]
|
||||
public ushort RemainingSize;
|
||||
|
||||
[FieldOrder(2)]
|
||||
[FieldEndianness(Endianness.Big)]
|
||||
public ushort RequestMainType;
|
||||
|
||||
[FieldOrder(3)]
|
||||
[FieldCount(6)]
|
||||
public byte[] Unknown0 = Array.Empty<byte>();
|
||||
|
||||
[FieldOrder(4)]
|
||||
[FieldEndianness(Endianness.Big)]
|
||||
public ushort RequestSubType;
|
||||
|
||||
[FieldOrder(5)]
|
||||
[FieldEndianness(Endianness.Big)]
|
||||
public ushort Unknown1;
|
||||
|
||||
[FieldOrder(6)]
|
||||
[FieldEndianness(Endianness.Big)]
|
||||
public ushort DataSize;
|
||||
|
||||
[FieldOrder(7)]
|
||||
[FieldEndianness(Endianness.Big)]
|
||||
public ushort Unknown2;
|
||||
|
||||
[FieldOrder(8)]
|
||||
[FieldEndianness(Endianness.Big)]
|
||||
public uint MatchingId;
|
||||
|
||||
[FieldOrder(9)]
|
||||
[FieldEndianness(Endianness.Big)]
|
||||
public uint EntryNo;
|
||||
|
||||
[FieldOrder(10)]
|
||||
[FieldEndianness(Endianness.Big)]
|
||||
public uint MachineId;
|
||||
|
||||
[FieldOrder(11)]
|
||||
[FieldEndianness(Endianness.Big)]
|
||||
public uint Unknown3;
|
||||
|
||||
[FieldOrder(12)]
|
||||
public byte[] Data = Array.Empty<byte>();
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace GCRelayServer;
|
||||
|
||||
public static class RelayPacketTypes
|
||||
{
|
||||
public const ushort HEART_BEAT = 0xB0;
|
||||
|
||||
public const ushort HEART_BEAT_RESPONSE = 0xB1;
|
||||
|
||||
public const ushort START_MATCHING = 0xA0;
|
||||
|
||||
public const ushort START_MATCHING_RESPONSE = 0xA1;
|
||||
|
||||
public const ushort REGISTER_MATCHING = 0xA6;
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using BinarySerialization;
|
||||
using Swan.Logging;
|
||||
|
||||
namespace GCRelayServer;
|
||||
|
||||
public class RelayServer : NetCoreServer.UdpServer
|
||||
{
|
||||
private ConcurrentDictionary<uint, DictEntry> matchingDictionary = new();
|
||||
public RelayServer(IPAddress address, int port) : base(address, port) {}
|
||||
|
||||
|
||||
protected override void OnStarted()
|
||||
{
|
||||
// Start receive datagrams
|
||||
ReceiveAsync();
|
||||
}
|
||||
|
||||
protected override void OnReceived(EndPoint endpoint, byte[] buffer, long offset, long size)
|
||||
{
|
||||
var serializer = new BinarySerializer();
|
||||
var inputStream = new MemoryStream(buffer, (int)offset, (int)size, false);
|
||||
var packet = serializer.Deserialize<RelayPacket>(inputStream);
|
||||
|
||||
if (!IsValidPacket(packet))
|
||||
{
|
||||
"Received malformed packet!".Warn();
|
||||
return;
|
||||
}
|
||||
|
||||
$"Received packet from {endpoint}, type is 0x{packet.RequestSubType:X2}".Info();
|
||||
|
||||
switch (packet.RequestSubType)
|
||||
{
|
||||
case RelayPacketTypes.HEART_BEAT:
|
||||
{
|
||||
for (var i = 0; i < 2; i++)
|
||||
{
|
||||
SendPacketSingle(endpoint, packet, RelayPacketTypes.HEART_BEAT_RESPONSE);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case RelayPacketTypes.START_MATCHING:
|
||||
{
|
||||
AddEntry(packet.MatchingId, endpoint);
|
||||
for (var i = 0; i < 2; i++)
|
||||
{
|
||||
SendPacketSingle(endpoint, packet, RelayPacketTypes.START_MATCHING_RESPONSE);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case RelayPacketTypes.REGISTER_MATCHING:
|
||||
{
|
||||
var entry = AddEntry(packet.MatchingId, endpoint);
|
||||
SendPacketToOthers(entry.EndPoints, packet, endpoint);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
var entry = GetEntry(packet.MatchingId);
|
||||
if (entry is null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
SendPacketToOthers(entry.EndPoints, packet, endpoint);
|
||||
break;
|
||||
}
|
||||
}
|
||||
ReceiveAsync();
|
||||
}
|
||||
private void SendPacketSingle(EndPoint endpoint, RelayPacket packet, ushort subType)
|
||||
{
|
||||
packet.RequestSubType = subType;
|
||||
var serializer = new BinarySerializer();
|
||||
var sendStream = new MemoryStream(1024);
|
||||
serializer.Serialize(sendStream, packet);
|
||||
|
||||
$"Send packet to {endpoint}, type is 0x{packet.RequestSubType:X2}".Info();
|
||||
SendAsync(endpoint, sendStream.GetBuffer(), 0, sendStream.Length);
|
||||
}
|
||||
|
||||
private void SendPacketToOthers(IEnumerable<EndPoint> endPoints, RelayPacket packet, EndPoint owner)
|
||||
{
|
||||
if (owner is not IPEndPoint ipEndPoint)
|
||||
{
|
||||
"Endpoint is not IP endpoint! This should not happen!".Fatal();
|
||||
throw new ApplicationException();
|
||||
}
|
||||
|
||||
foreach (var endPoint in endPoints.Where(endPoint => !ipEndPoint.Equals(endPoint)))
|
||||
{
|
||||
SendPacketSingle(endPoint, packet, packet.RequestSubType);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnError(SocketError error)
|
||||
{
|
||||
$"Relay server caught an error with code {error}".Error();
|
||||
}
|
||||
|
||||
private static bool IsValidPacket(RelayPacket packet)
|
||||
{
|
||||
var totalSize = packet.Magic + packet.RemainingSize;
|
||||
var actualSize = 36 + packet.Data.Length;
|
||||
if (totalSize != actualSize)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return packet.Data.Length == packet.DataSize;
|
||||
}
|
||||
|
||||
private DictEntry AddEntry(uint matchingId, EndPoint endPoint)
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
var entry = matchingDictionary.GetValueOrDefault(matchingId, new DictEntry());
|
||||
var shouldClear = false;
|
||||
|
||||
if (entry.LastAccessTime <= now && now - entry.LastAccessTime >= TimeSpan.FromMinutes(10))
|
||||
{
|
||||
$"Entry for matching id {matchingId:X8} has expired! Clients will be cleared!".Info();
|
||||
shouldClear = true;
|
||||
}
|
||||
if (entry.EndPoints.Count >= 4)
|
||||
{
|
||||
$"Entry for matching id {matchingId:X8} contains more than 4 clients! Clients will be cleared!".Warn();
|
||||
shouldClear = true;
|
||||
}
|
||||
entry.AddEndpoint(endPoint, shouldClear);
|
||||
|
||||
entry.LastAccessTime = DateTime.Now;
|
||||
matchingDictionary[matchingId] = entry;
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
private DictEntry? GetEntry(uint matchingId)
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
|
||||
if (!matchingDictionary.ContainsKey(matchingId))
|
||||
{
|
||||
$"Entry for matching id {matchingId:X8} does not exist!".Warn();
|
||||
return null;
|
||||
}
|
||||
|
||||
var entry = matchingDictionary[matchingId];
|
||||
if (entry.LastAccessTime <= now && now - entry.LastAccessTime >= TimeSpan.FromMinutes(10))
|
||||
{
|
||||
$"Entry for matching id {matchingId:X8} has expired!".Warn();
|
||||
return null;
|
||||
}
|
||||
|
||||
entry.LastAccessTime = DateTime.Now;
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<Router AppAssembly="@typeof(App).Assembly">
|
||||
<Found Context="routeData">
|
||||
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/>
|
||||
</Found>
|
||||
<NotFound>
|
||||
<PageTitle>Not found</PageTitle>
|
||||
<LayoutView Layout="@typeof(MainLayout)">
|
||||
<p role="alert">Sorry, there's nothing at this address.</p>
|
||||
</LayoutView>
|
||||
</NotFound>
|
||||
</Router>
|
||||
@@ -0,0 +1,12 @@
|
||||
<linker>
|
||||
<assembly fullname="MudBlazor" preserve="all">
|
||||
<type fullname="MudBlazor.FilterOperator" preserve="all" />
|
||||
<type fullname="MudBlazor.FilterOperator/String" preserve="all" />
|
||||
<type fullname="MudBlazor.FilterOperator/Number" preserve="all" />
|
||||
<type fullname="MudBlazor.FilterOperator/Enum" preserve="all" />
|
||||
<type fullname="MudBlazor.FilterOperator/Boolean" preserve="all" />
|
||||
<type fullname="MudBlazor.FilterOperator/DateTime" preserve="all" />
|
||||
<type fullname="MudBlazor.FilterDefinition`1" preserve="all" />
|
||||
<type fullname="MudBlazor.Filter" preserve="all" />
|
||||
</assembly>
|
||||
</linker>
|
||||
@@ -0,0 +1,81 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<PublishTrimmed>true</PublishTrimmed>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<RunAOTCompilation>False</RunAOTCompilation>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<RunAOTCompilation>True</RunAOTCompilation>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<SuppressTrimAnalysisWarnings>false</SuppressTrimAnalysisWarnings>
|
||||
<TrimmerSingleWarn>false</TrimmerSingleWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="ILLink.Descriptors.xml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="ILLink.Descriptors.xml">
|
||||
<LogicalName>ILLink.Descriptors.xml</LogicalName>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="GenFu" Version="1.6.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="6.0.6" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="6.0.6" PrivateAssets="all" />
|
||||
<PackageReference Include="MudBlazor" Version="6.0.11" />
|
||||
<PackageReference Include="protobuf-net" Version="3.1.17" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SharedProject\SharedProject.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Update="wwwroot\appsettings.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
</Content>
|
||||
<Content Update="wwwroot\data\avatar.dat">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="wwwroot\data\avatar.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
</Content>
|
||||
<Content Update="wwwroot\data\navigator.dat">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="wwwroot\data\navigator.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
</Content>
|
||||
<Content Update="wwwroot\data\title.dat">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="wwwroot\data\title.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
</Content>
|
||||
<Content Update="wwwroot\news.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Utils" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,25 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31717.71
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MudAdmin", "MudAdmin.csproj", "{D6DF8A15-5B8D-4302-90D2-FC71264B18CC}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{D6DF8A15-5B8D-4302-90D2-FC71264B18CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D6DF8A15-5B8D-4302-90D2-FC71264B18CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D6DF8A15-5B8D-4302-90D2-FC71264B18CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D6DF8A15-5B8D-4302-90D2-FC71264B18CC}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {58C61F27-917C-435B-8900-9B3CE8F8DCD4}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,73 @@
|
||||
@using SharedProject.common
|
||||
@using System.Text.RegularExpressions
|
||||
@inject HttpClient Client
|
||||
@inject ILogger<ChangePlayerNameDialog> Logger
|
||||
|
||||
<MudDialog>
|
||||
<TitleContent>
|
||||
<MudText Typo="Typo.h6">
|
||||
Change Player Name
|
||||
</MudText>
|
||||
</TitleContent>
|
||||
<DialogContent>
|
||||
<MudForm @bind-IsValid="IsValid">
|
||||
<MudTextField Value="@Data.CardId" Label="CardId" ReadOnly="true"/>
|
||||
<MudTextField @bind-Value="Data.PlayerName"
|
||||
Immediate="true"
|
||||
Counter="SharedConstants.MAX_PLAYER_NAME_LENGTH"
|
||||
MaxLength="SharedConstants.MAX_PLAYER_NAME_LENGTH"
|
||||
Validation="ValidatePlayerName"/>
|
||||
</MudForm>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">Cancel</MudButton>
|
||||
<MudButton Color="Color.Primary" OnClick="Submit" Disabled="@(!IsValid)">Confirm</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
|
||||
@code {
|
||||
|
||||
[CascadingParameter]
|
||||
MudDialogInstance MudDialog { get; set; } = null!;
|
||||
|
||||
[Parameter]
|
||||
public SharedProject.models.User Data { get; set; } = null!;
|
||||
|
||||
private string originalName = string.Empty;
|
||||
|
||||
private bool IsValid { get; set; }
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
base.OnInitialized();
|
||||
originalName = new string(Data.PlayerName);
|
||||
}
|
||||
|
||||
async Task Submit()
|
||||
{
|
||||
Logger.LogInformation("Data is {cardId}, {name}", Data.CardId, Data.PlayerName);
|
||||
var response = await Client.PostAsJsonAsync("api/Users/SetPlayerName", Data);
|
||||
var result = await response.Content.ReadFromJsonAsync<bool>();
|
||||
Logger.LogInformation("SetPlayerName result is {result}", result);
|
||||
MudDialog.Close(DialogResult.Ok(result));
|
||||
}
|
||||
|
||||
void Cancel()
|
||||
{
|
||||
Data.PlayerName = originalName;
|
||||
MudDialog.Cancel();
|
||||
}
|
||||
|
||||
private static string? ValidatePlayerName(string playerName)
|
||||
{
|
||||
const string pattern = @"^[a-zA-Z0-9!?,./\-+:<>_\\@*#&=() ]{1,8}$";
|
||||
|
||||
return playerName.Length switch
|
||||
{
|
||||
0 => "Player name cannot be empty!",
|
||||
> 8 => "Player name cannot be longer than 8 characters!",
|
||||
_ => !Regex.IsMatch(playerName, pattern) ? "Player name contains invalid character!" : null
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
@using SharedProject.models
|
||||
@inject HttpClient Client
|
||||
@inject ILogger<FavoriteDialog> Logger
|
||||
|
||||
<MudDialog>
|
||||
<TitleContent>
|
||||
@if (!Data.IsFavorite)
|
||||
{
|
||||
<MudText Typo="Typo.h6">
|
||||
<MudIcon Icon="@Icons.Material.Filled.BookmarkAdd" Color="Color.Secondary"/>
|
||||
Add to favorite?
|
||||
</MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText Typo="Typo.h6">
|
||||
<MudIcon Icon="@Icons.Material.Filled.BookmarkRemove" Color="Color.Secondary"/>
|
||||
Remove from favorite?
|
||||
</MudText>
|
||||
}
|
||||
</TitleContent>
|
||||
<DialogContent>
|
||||
<MudTextField Value="@Data.Title" Label="Song Title" ReadOnly="true"/>
|
||||
<MudTextField Value="@Data.Artist" Label="Artist Name" ReadOnly="true"/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">Cancel</MudButton>
|
||||
<MudButton Color="Color.Primary" OnClick="Submit">Confirm</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
@code {
|
||||
[CascadingParameter]
|
||||
MudDialogInstance MudDialog { get; set; } = null!;
|
||||
|
||||
[Parameter]
|
||||
public SongPlayData Data { get; set; } = null!;
|
||||
|
||||
[Parameter]
|
||||
public long CardId { get; set; }
|
||||
|
||||
async Task Submit()
|
||||
{
|
||||
var postData = new MusicFavoriteData
|
||||
{
|
||||
CardId = CardId,
|
||||
IsFavorite = !Data.IsFavorite,
|
||||
MusicId = Data.MusicId
|
||||
};
|
||||
Logger.LogInformation("Data is {cardId}, {musicId}", CardId, Data.MusicId);
|
||||
var response = await Client.PostAsJsonAsync("api/UserDetail/SetMusicFavorite", postData);
|
||||
var result = await response.Content.ReadFromJsonAsync<bool>();
|
||||
Logger.LogInformation("Favorite result is {result}", result);
|
||||
MudDialog.Close(DialogResult.Ok(result));
|
||||
}
|
||||
|
||||
void Cancel() => MudDialog.Cancel();
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
@page "/"
|
||||
|
||||
<PageTitle>Index</PageTitle>
|
||||
|
||||
<MudText Typo="Typo.h3" GutterBottom="true">Admin page for GC local server</MudText>
|
||||
<MudText Class="mb-8">Powered by MudBlazor</MudText>
|
||||
@@ -0,0 +1,305 @@
|
||||
@page "/user/{CardId:long}"
|
||||
@using SharedProject.models
|
||||
@using SharedProject.enums
|
||||
|
||||
<PageTitle>User</PageTitle>
|
||||
<MudContainer>
|
||||
@if (pageLoading)
|
||||
{
|
||||
<MudSkeleton Width="1184px" Height="57px"/>
|
||||
<MudSkeleton Width="1184px" Height="57px"/>
|
||||
<MudSkeleton Width="1184px" Height="57px"/>
|
||||
}
|
||||
else
|
||||
{
|
||||
if (userDetail is null)
|
||||
{
|
||||
<MudText Typo="Typo.h3">No Data</MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudExpansionPanels>
|
||||
<MudExpansionPanel Text="Total Result">
|
||||
<MudList>
|
||||
<MudListSubheader>Player Name: @userDetail.PlayerName</MudListSubheader>
|
||||
<MudListItem>Total Score: @userDetail.TotalScore</MudListItem>
|
||||
<MudListItem>Average Score: @userDetail.AverageScore</MudListItem>
|
||||
<MudListItem>Played Song Count: @userDetail.PlayedSongCount / @userDetail.TotalSongCount</MudListItem>
|
||||
<MudListItem>Cleared Stage Count: @userDetail.ClearedStageCount / @userDetail.TotalStageCount</MudListItem>
|
||||
<MudListItem>No Miss Stage Count: @userDetail.NoMissStageCount / @userDetail.TotalStageCount</MudListItem>
|
||||
<MudListItem>Full Chain Stage Count: @userDetail.FullChainStageCount / @userDetail.TotalStageCount</MudListItem>
|
||||
<MudListItem>Perfect Stage Count: @userDetail.PerfectStageCount / @userDetail.TotalStageCount</MudListItem>
|
||||
<MudListItem>S and Above Stage Count: @userDetail.SAboveStageCount / @userDetail.TotalStageCount</MudListItem>
|
||||
<MudListItem>S+ and Above Stage Count: @userDetail.SPlusAboveStageCount / @userDetail.TotalStageCount</MudListItem>
|
||||
<MudListItem>S++ and Above Stage Count: @userDetail.SPlusPlusAboveStageCount / @userDetail.TotalStageCount</MudListItem>
|
||||
</MudList>
|
||||
</MudExpansionPanel>
|
||||
<MudExpansionPanel Text="PlayOptions">
|
||||
<MudStack>
|
||||
<MudSelect @bind-Value="@playOption.FastSlowIndicator"
|
||||
Label="FAST/SLOW show setting">
|
||||
@foreach (var item in Enum.GetValues<PlayOptions.FastSlowIndicator>())
|
||||
{
|
||||
<MudSelectItem Value="@item">@item.GetHelpText()</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
|
||||
<MudSelect @bind-Value="@playOption.FeverTrance"
|
||||
Label="FEVER/TRANCE show setting">
|
||||
@foreach (var item in Enum.GetValues<PlayOptions.FeverTranceShow>())
|
||||
{
|
||||
<MudSelectItem Value="@item">@item.GetHelpText()</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
|
||||
<MudStack Row="true">
|
||||
<MudField Label="Navigator setting">
|
||||
@NavigatorIdToString(playOption.NavigatorId)
|
||||
</MudField>
|
||||
<MudButton Variant="Variant.Text"
|
||||
|
||||
OnClick="OnChangeNavigatorButtonClick">
|
||||
Change Navigator
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
|
||||
<MudStack Row="true">
|
||||
<MudField Label="Avatar setting">
|
||||
@AvatarIdToString(playOption.AvatarId)
|
||||
</MudField>
|
||||
<MudButton Variant="Variant.Text"
|
||||
OnClick="OnChangeAvatarButtonClick">
|
||||
Change Avatar
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
|
||||
<MudStack Row="true">
|
||||
<MudField Label="Title setting">
|
||||
@TitleIdToString(playOption.TitleId)
|
||||
</MudField>
|
||||
<MudButton Variant="Variant.Text"
|
||||
OnClick="OnChangeTitleButtonClick">
|
||||
Change Title
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
|
||||
<MudOverlay @bind-Visible="IsNavigatorOverlayVisible"
|
||||
DarkBackground="true">
|
||||
<MudPaper>
|
||||
<MudContainer MaxWidth="MaxWidth.Large">
|
||||
<MudStack>
|
||||
<MudDataGrid T="Navigator"
|
||||
MultiSelection="false" @bind-SelectedItem="SelectedNavigator"
|
||||
FixedHeader="true" FixedFooter="true" Height="70vh"
|
||||
Items="navigatorDictionary.Values" ReadOnly="true" Hover="true"
|
||||
Sortable="true" Filterable="true" Hideable="true">
|
||||
<ToolBarContent>
|
||||
<MudText Typo="Typo.h6">Navigators</MudText>
|
||||
<MudSpacer/>
|
||||
</ToolBarContent>
|
||||
<Columns>
|
||||
<Column T="Navigator" Field="@nameof(Navigator.Id)"/>
|
||||
<Column T="Navigator" Field="@nameof(Navigator.IdString)"/>
|
||||
<Column T="Navigator" Field="@nameof(Navigator.NameEntryString)" Title="Navigator Name"
|
||||
SortBy="NameEntrySortByFunc"/>
|
||||
<Column T="Navigator" Field="@nameof(Navigator.ToolTipJp)" Title="Navigator Description (Japanese)"/>
|
||||
<Column T="Navigator" Field="@nameof(Navigator.ToolTipEn)" Title="Navigator Description (English)"/>
|
||||
<Column T="Navigator" Field="@nameof(Navigator.Genre)" Title="Navigator Type"/>
|
||||
</Columns>
|
||||
<PagerContent>
|
||||
<MudDataGridPager T="Navigator"/>
|
||||
</PagerContent>
|
||||
</MudDataGrid>
|
||||
<MudText Align="Align.Left">
|
||||
Currently selected navigator:<br>
|
||||
@SelectedNavigator
|
||||
</MudText>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" FullWidth="true"
|
||||
OnClick="OnNavigatorOverlayClosed">
|
||||
Close
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
</MudContainer>
|
||||
</MudPaper>
|
||||
</MudOverlay>
|
||||
|
||||
<MudOverlay @bind-Visible="IsAvatarOverlayVisible"
|
||||
DarkBackground="true">
|
||||
<MudPaper>
|
||||
<MudContainer MaxWidth="MaxWidth.Large">
|
||||
<MudStack>
|
||||
<MudDataGrid T="Avatar"
|
||||
MultiSelection="false" @bind-SelectedItem="SelectedAvatar"
|
||||
FixedHeader="true" FixedFooter="true" Height="70vh"
|
||||
Items="avatarDictionary.Values" ReadOnly="true" Hover="true"
|
||||
Sortable="true" Filterable="true" Hideable="true">
|
||||
<ToolBarContent>
|
||||
<MudText Typo="Typo.h6">Avatars</MudText>
|
||||
<MudSpacer/>
|
||||
</ToolBarContent>
|
||||
<Columns>
|
||||
<Column T="Avatar" Field="@nameof(Avatar.Id)"/>
|
||||
<Column T="Avatar" Field="@nameof(Avatar.IdString)"/>
|
||||
<Column T="Avatar" Field="@nameof(Avatar.FullName)" Title="Avatar Name"/>
|
||||
<Column T="Avatar" Field="@nameof(Avatar.AcquireMethodJp)" Title="Acquire Method (Japanese)"/>
|
||||
<Column T="Avatar" Field="@nameof(Avatar.AcquireMethodEn)" Title="Acquire Method (English)"/>
|
||||
</Columns>
|
||||
<PagerContent>
|
||||
<MudDataGridPager T="Avatar"/>
|
||||
</PagerContent>
|
||||
</MudDataGrid>
|
||||
<MudText Align="Align.Left">
|
||||
Currently selected avatar:<br>
|
||||
@SelectedAvatar
|
||||
</MudText>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" FullWidth="true"
|
||||
OnClick="OnAvatarOverlayClosed">
|
||||
Close
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
</MudContainer>
|
||||
</MudPaper>
|
||||
</MudOverlay>
|
||||
|
||||
<MudOverlay @bind-Visible="IsTitleOverlayVisible"
|
||||
DarkBackground="true">
|
||||
<MudPaper>
|
||||
<MudContainer MaxWidth="MaxWidth.Large">
|
||||
<MudStack>
|
||||
<MudDataGrid T="Title"
|
||||
MultiSelection="false" @bind-SelectedItem="SelectedTitle"
|
||||
FixedHeader="true" FixedFooter="true" Height="70vh"
|
||||
Items="titleDictionary.Values" ReadOnly="true" Hover="true"
|
||||
Sortable="true" Filterable="true" Hideable="true">
|
||||
<ToolBarContent>
|
||||
<MudText Typo="Typo.h6">Titles</MudText>
|
||||
<MudSpacer/>
|
||||
</ToolBarContent>
|
||||
<Columns>
|
||||
<Column T="Title" Field="@nameof(Title.Id)"/>
|
||||
<Column T="Title" Field="@nameof(Title.IdString)"/>
|
||||
<Column T="Title" Field="@nameof(Title.NameJp)" Title="Title Name"/>
|
||||
<Column T="Title" Field="@nameof(Title.UnlockRequirementJp)" Title="Unlock Requirement (Japanese)"/>
|
||||
<Column T="Title" Field="@nameof(Title.UnlockRequirementEng)" Title="Unlock Requirement (English)"/>
|
||||
<Column T="Title" Field="@nameof(Title.Type)" Title="Title Type"/>
|
||||
</Columns>
|
||||
<PagerContent>
|
||||
<MudDataGridPager T="Title"/>
|
||||
</PagerContent>
|
||||
</MudDataGrid>
|
||||
<MudText Align="Align.Left">
|
||||
Currently selected title:<br>
|
||||
@SelectedTitle
|
||||
</MudText>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" FullWidth="true"
|
||||
OnClick="OnTitleOverlayClosed">
|
||||
Close
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
</MudContainer>
|
||||
</MudPaper>
|
||||
</MudOverlay>
|
||||
|
||||
<MudButton Disabled="@isSavingOptions"
|
||||
OnClick="SaveOptions"
|
||||
Variant="Variant.Filled"
|
||||
Color="Color.Info">
|
||||
@if (isSavingOptions)
|
||||
{
|
||||
<MudProgressCircular Class="ms-n1" Size="Size.Small" Indeterminate="true"/>
|
||||
<MudText Class="ms-2">Saving...</MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudIcon Icon="@Icons.Filled.Save"></MudIcon>
|
||||
<MudText>Save</MudText>
|
||||
}
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
</MudExpansionPanel>
|
||||
<MudExpansionPanel Text="SongPlayData">
|
||||
<MudDataGrid T="SongPlayData"
|
||||
Items="@songPlayDataList"
|
||||
Sortable="true"
|
||||
Filterable="true">
|
||||
<ToolBarContent>
|
||||
<MudText Typo="Typo.h6">Played Songs</MudText>
|
||||
</ToolBarContent>
|
||||
<Columns>
|
||||
<Column T="SongPlayData" Sortable="false" Filterable="false">
|
||||
<CellTemplate>
|
||||
<MudButton Variant="Variant.Outlined" Size="Size.Small"
|
||||
OnClick="@(() => OnShowDetailsClick(context.Item))">
|
||||
@(context.Item.ShowDetails ? "Hide" : "Show") Song Play Details
|
||||
</MudButton>
|
||||
</CellTemplate>
|
||||
</Column>
|
||||
<Column T="SongPlayData" Field="@nameof(SongPlayData.IsFavorite)" Sortable="false" Title="Favorite">
|
||||
<CellTemplate>
|
||||
<MudToggleIconButton Toggled="@context.Item.IsFavorite"
|
||||
ToggledChanged="@(() => OnFavoriteToggled(context.Item))"
|
||||
Icon="@Icons.Material.Filled.FavoriteBorder"
|
||||
Color="@Color.Secondary"
|
||||
Title="Add to favorite"
|
||||
ToggledIcon="@Icons.Material.Filled.Favorite"
|
||||
ToggledColor="@Color.Secondary"
|
||||
ToggledTitle="Remove from favorite"/>
|
||||
</CellTemplate>
|
||||
</Column>
|
||||
<Column T="SongPlayData" Field="@nameof(SongPlayData.Title)" Title="Song Title"/>
|
||||
<Column T="SongPlayData" Field="@nameof(SongPlayData.Artist)" Title="Artist"/>
|
||||
<Column T="SongPlayData" Field="@nameof(SongPlayData.TotalPlayCount)" Title="Total Play Count"/>
|
||||
<Column T="SongPlayData" Field="@nameof(SongPlayData.LastPlayTime)" Title="Last Play Time"/>
|
||||
</Columns>
|
||||
<ChildRowContent>
|
||||
@if (context.ShowDetails)
|
||||
{
|
||||
<MudTr>
|
||||
<td colspan="5">
|
||||
<MudCard Elevation="0">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.body1">Song Play Details</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pa-0">
|
||||
<MudTable Items="@context.SongPlaySubDataList"
|
||||
Context="SongPlayDetail"
|
||||
Elevation="0"
|
||||
Filter="data => data.ClearState != ClearState.NotPlayed">
|
||||
<HeaderContent>
|
||||
<MudTh>Difficulty</MudTh>
|
||||
<MudTh>Clear State</MudTh>
|
||||
<MudTh>Play Count</MudTh>
|
||||
<MudTh>Rating</MudTh>
|
||||
<MudTh>Score</MudTh>
|
||||
<MudTh>Max Chain</MudTh>
|
||||
<MudTh>Last Play Time</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="Difficulty">@SongPlayDetail.Difficulty</MudTd>
|
||||
<MudTd DataLabel="Clear State">
|
||||
@(SongPlayDetail.Score != 1000000 ? SongPlayDetail.ClearState : ClearState.Perfect)
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Play Count">@SongPlayDetail.PlayCount</MudTd>
|
||||
<MudTd DataLabel="Rating">@CalculateRating(SongPlayDetail.Score)</MudTd>
|
||||
<MudTd DataLabel="Score">@SongPlayDetail.Score</MudTd>
|
||||
<MudTd DataLabel="Max Chain">@SongPlayDetail.MaxChain</MudTd>
|
||||
<MudTd DataLabel="Last Play Time">@SongPlayDetail.LastPlayTime</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</td>
|
||||
</MudTr>
|
||||
}
|
||||
</ChildRowContent>
|
||||
<PagerContent>
|
||||
<MudDataGridPager T="SongPlayData"/>
|
||||
</PagerContent>
|
||||
</MudDataGrid>
|
||||
</MudExpansionPanel>
|
||||
</MudExpansionPanels>
|
||||
}
|
||||
}
|
||||
</MudContainer>
|
||||
@@ -0,0 +1,228 @@
|
||||
using System.Net.Http.Json;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
using ProtoBuf;
|
||||
using SharedProject.common;
|
||||
using SharedProject.models;
|
||||
|
||||
namespace MudAdmin.Pages;
|
||||
|
||||
public partial class User
|
||||
{
|
||||
[Inject]
|
||||
public HttpClient Client { get; set; } = null!;
|
||||
|
||||
[Inject]
|
||||
public IDialogService DialogService { get; set; } = null!;
|
||||
|
||||
[Inject]
|
||||
public ILogger<User> Logger { get; set; } = null!;
|
||||
|
||||
private Dictionary<long, Avatar> avatarDictionary = new();
|
||||
|
||||
private bool isSavingOptions;
|
||||
|
||||
private Dictionary<long, Navigator> navigatorDictionary = new();
|
||||
|
||||
private bool pageLoading = true;
|
||||
|
||||
private PlayOption playOption = new();
|
||||
|
||||
private List<SongPlayData> songPlayDataList = new();
|
||||
|
||||
private Dictionary<long, Title> titleDictionary = new();
|
||||
|
||||
private UserDetail? userDetail;
|
||||
|
||||
[Parameter]
|
||||
public long CardId { get; set; }
|
||||
|
||||
private bool IsTitleOverlayVisible { get; set; }
|
||||
|
||||
private bool IsNavigatorOverlayVisible { get; set; }
|
||||
|
||||
private bool IsAvatarOverlayVisible { get; set; }
|
||||
|
||||
private Title? SelectedTitle { get; set; }
|
||||
|
||||
private Avatar? SelectedAvatar { get; set; }
|
||||
|
||||
private Navigator? SelectedNavigator { get; set; }
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
userDetail = await Client.GetFromJsonAsync<UserDetail>($"api/UserDetail/{CardId}");
|
||||
if (userDetail is null)
|
||||
{
|
||||
pageLoading = false;
|
||||
return;
|
||||
}
|
||||
songPlayDataList = userDetail.SongPlayDataList ?? new List<SongPlayData>();
|
||||
playOption = userDetail.PlayOption;
|
||||
|
||||
var navigatorStream = await Client.GetStreamAsync(SharedConstants.NAVIGATOR_DAT_URI);
|
||||
var navigators = Serializer.Deserialize<Navigators>(navigatorStream);
|
||||
if (navigators?.NavigatorList != null)
|
||||
{
|
||||
navigatorDictionary = navigators.NavigatorList.ToDictionary(navigator => (long)navigator.Id);
|
||||
}
|
||||
|
||||
var avatarStream = await Client.GetStreamAsync(SharedConstants.AVATAR_DAT_URI);
|
||||
var avatars = Serializer.Deserialize<List<Avatar>>(avatarStream);
|
||||
if (avatars != null)
|
||||
{
|
||||
avatarDictionary = avatars.ToDictionary(avatar => (long)avatar.Id);
|
||||
}
|
||||
|
||||
var titleStream = await Client.GetStreamAsync(SharedConstants.TITLE_DAT_URI);
|
||||
var titles = Serializer.Deserialize<List<Title>>(titleStream);
|
||||
if (titles != null)
|
||||
{
|
||||
titleDictionary = titles.ToDictionary(title => (long)title.Id);
|
||||
}
|
||||
|
||||
SetSelected();
|
||||
pageLoading = false;
|
||||
}
|
||||
|
||||
private void SetSelected()
|
||||
{
|
||||
SelectedTitle = titleDictionary.GetValueOrDefault(playOption.TitleId);
|
||||
SelectedAvatar = avatarDictionary.GetValueOrDefault(playOption.AvatarId);
|
||||
SelectedNavigator = navigatorDictionary.GetValueOrDefault(playOption.NavigatorId);
|
||||
}
|
||||
|
||||
private void OnShowDetailsClick(SongPlayData data)
|
||||
{
|
||||
data.ShowDetails = !data.ShowDetails;
|
||||
}
|
||||
|
||||
private async Task SaveOptions()
|
||||
{
|
||||
isSavingOptions = true;
|
||||
var postData = new PlayOption
|
||||
{
|
||||
CardId = CardId,
|
||||
FastSlowIndicator = playOption.FastSlowIndicator,
|
||||
FeverTrance = playOption.FeverTrance,
|
||||
AvatarId = playOption.AvatarId,
|
||||
NavigatorId = playOption.NavigatorId,
|
||||
TitleId = playOption.TitleId
|
||||
};
|
||||
var result = await Client.PostAsJsonAsync("api/UserDetail/SetPlayOption", postData);
|
||||
isSavingOptions = false;
|
||||
}
|
||||
|
||||
private static string CalculateRating(int score)
|
||||
{
|
||||
var grade = SharedConstants.Grades.Where(g => g.Score <= score).Select(g => g.Grade).Last();
|
||||
return grade;
|
||||
}
|
||||
|
||||
private async Task OnFavoriteToggled(SongPlayData data)
|
||||
{
|
||||
var options = new DialogOptions
|
||||
{
|
||||
CloseOnEscapeKey = false,
|
||||
DisableBackdropClick = true,
|
||||
FullWidth = true
|
||||
};
|
||||
var parameters = new DialogParameters();
|
||||
parameters.Add("Data", data);
|
||||
parameters.Add("CardId", CardId);
|
||||
var dialog = DialogService.Show<FavoriteDialog>("Favorite", parameters, options);
|
||||
var result = await dialog.Result;
|
||||
|
||||
if (result.Cancelled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if ((bool)result.Data)
|
||||
{
|
||||
Logger.LogInformation("Changed!");
|
||||
data.IsFavorite = !data.IsFavorite;
|
||||
}
|
||||
}
|
||||
|
||||
private Task<IEnumerable<long>> SearchAvatar(string value)
|
||||
{
|
||||
var result = string.IsNullOrEmpty(value) ? avatarDictionary.Keys : avatarDictionary.Where(pair => pair.Value.ToString().Contains(value, StringComparison.InvariantCultureIgnoreCase)).Select(pair => pair.Key);
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
|
||||
private Task<IEnumerable<long>> SearchTitle(string value)
|
||||
{
|
||||
var result = string.IsNullOrEmpty(value) ? titleDictionary.Keys : titleDictionary.Where(pair => pair.Value.ToString().Contains(value, StringComparison.InvariantCultureIgnoreCase)).Select(pair => pair.Key);
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
|
||||
private Task<IEnumerable<long>> SearchNavigator(string value)
|
||||
{
|
||||
var result = string.IsNullOrEmpty(value) ? navigatorDictionary.Keys : navigatorDictionary.Where(pair => pair.Value.ToString().Contains(value, StringComparison.InvariantCultureIgnoreCase)).Select(pair => pair.Key);
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
|
||||
private string AvatarIdToString(long id)
|
||||
{
|
||||
return avatarDictionary.ContainsKey(id) ? avatarDictionary[id].ToString() : $"No Data for {id}!";
|
||||
}
|
||||
|
||||
private string NavigatorIdToString(long id)
|
||||
{
|
||||
return navigatorDictionary.ContainsKey(id) ? navigatorDictionary[id].ToString() : $"No Data for {id}!";
|
||||
}
|
||||
|
||||
private string TitleIdToString(long id)
|
||||
{
|
||||
return titleDictionary.ContainsKey(id) ? titleDictionary[id].ToString() : $"No Data for {id}!";
|
||||
}
|
||||
|
||||
private void OnChangeTitleButtonClick()
|
||||
{
|
||||
IsTitleOverlayVisible = true;
|
||||
}
|
||||
|
||||
private void OnChangeNavigatorButtonClick()
|
||||
{
|
||||
IsNavigatorOverlayVisible = true;
|
||||
}
|
||||
|
||||
private void OnChangeAvatarButtonClick()
|
||||
{
|
||||
IsAvatarOverlayVisible = true;
|
||||
}
|
||||
|
||||
private void OnTitleOverlayClosed()
|
||||
{
|
||||
IsTitleOverlayVisible = false;
|
||||
if (SelectedTitle != null)
|
||||
{
|
||||
playOption.TitleId = SelectedTitle.Id;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnNavigatorOverlayClosed()
|
||||
{
|
||||
IsNavigatorOverlayVisible = false;
|
||||
if (SelectedNavigator != null)
|
||||
{
|
||||
playOption.NavigatorId = SelectedNavigator.Id;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAvatarOverlayClosed()
|
||||
{
|
||||
IsAvatarOverlayVisible = false;
|
||||
if (SelectedAvatar != null)
|
||||
{
|
||||
playOption.AvatarId = SelectedAvatar.Id;
|
||||
}
|
||||
}
|
||||
|
||||
private static object NameEntrySortByFunc(Navigator navigator)
|
||||
{
|
||||
return navigator.NameEntry1?.ToString().ToLowerInvariant() ?? string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
@page "/users"
|
||||
@using models = SharedProject.models
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject HttpClient Client
|
||||
@inject IDialogService DialogService
|
||||
@inject ILogger<Users> Logger
|
||||
|
||||
<PageTitle>Users</PageTitle>
|
||||
|
||||
<MudContainer>
|
||||
@if (users is null)
|
||||
{
|
||||
<MudGrid>
|
||||
@for (var i = 0; i < 5; i++)
|
||||
{
|
||||
<MudItem>
|
||||
<MudCard>
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudSkeleton SkeletonType="SkeletonType.Rectangle" Height="32px"/>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudSkeleton Width="80px" Height="32px"/>
|
||||
<MudSkeleton Width="147px" Height="28px"/>
|
||||
</MudCardContent>
|
||||
<MudCardActions>
|
||||
<MudSkeleton SkeletonType="SkeletonType.Rectangle" Width="99px" Height="25px" Class="ml-2"/>
|
||||
</MudCardActions>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
}
|
||||
</MudGrid>
|
||||
}
|
||||
else if (users.Count != 0)
|
||||
{
|
||||
<MudGrid>
|
||||
@foreach (var user in users)
|
||||
{
|
||||
<MudItem>
|
||||
<MudCard>
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.h5">@user.PlayerName</MudText>
|
||||
</CardHeaderContent>
|
||||
<CardHeaderActions>
|
||||
<MudIconButton Icon="@Icons.Filled.Edit" Color="Color.Default"
|
||||
OnClick="() => OnEditPlayerNameClicked(user)"/>
|
||||
</CardHeaderActions>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudText Typo="Typo.h6">Card ID</MudText>
|
||||
<MudText>@user.CardId</MudText>
|
||||
</MudCardContent>
|
||||
<MudCardActions>
|
||||
<MudButton Variant="Variant.Text" Color="Color.Primary" OnClick="() => OnCheckDetailClick(user)">Check detail</MudButton>
|
||||
</MudCardActions>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
}
|
||||
</MudGrid>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText Typo="Typo.h3">No Data</MudText>
|
||||
}
|
||||
</MudContainer>
|
||||
|
||||
@code {
|
||||
private List<models.User>? users;
|
||||
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
users = await Client.GetFromJsonAsync<List<models.User>>("api/Users") ?? new List<models.User>();
|
||||
}
|
||||
|
||||
private void OnCheckDetailClick(models.User user)
|
||||
{
|
||||
NavigationManager.NavigateTo($"user/{user.CardId}");
|
||||
}
|
||||
|
||||
private async Task OnEditPlayerNameClicked(models.User user)
|
||||
{
|
||||
var options = new DialogOptions
|
||||
{
|
||||
CloseOnEscapeKey = false,
|
||||
DisableBackdropClick = true,
|
||||
FullWidth = true
|
||||
};
|
||||
var parameters = new DialogParameters();
|
||||
parameters.Add("Data", user);
|
||||
var dialog = DialogService.Show<ChangePlayerNameDialog>("Favorite", parameters, options);
|
||||
var result = await dialog.Result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using System.Text.Json;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
||||
using MudAdmin;
|
||||
using MudBlazor.Services;
|
||||
|
||||
var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
||||
builder.RootComponents.Add<App>("#app");
|
||||
builder.RootComponents.Add<HeadOutlet>("head::after");
|
||||
|
||||
builder.Services.AddScoped(sp => new HttpClient
|
||||
{
|
||||
BaseAddress = new Uri(builder.Configuration.GetValue<string>("BaseUrl"))
|
||||
});
|
||||
builder.Services.AddMudServices();
|
||||
|
||||
await builder.Build().RunAsync();
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:65283",
|
||||
"sslPort": 44398
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"Mudblazor.Template": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": "true",
|
||||
"launchBrowser": true,
|
||||
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
|
||||
"applicationUrl": "https://localhost:5001;http://localhost:5000",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
<MudThemeProvider @ref="@MudThemeProvider" @bind-IsDarkMode="@IsDarkMode"/>
|
||||
<MudDialogProvider/>
|
||||
<MudSnackbarProvider/>
|
||||
|
||||
<MudLayout>
|
||||
<MudAppBar Elevation="0" Dense="true">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Menu" Color="Color.Inherit" Edge="Edge.Start"
|
||||
OnClick="@((e) => DrawerToggle())"/>
|
||||
<MudSpacer/>
|
||||
<MudTooltip Duration="1000" Text="Powered by MudBlazor">
|
||||
<MudIconButton Icon="@Icons.Custom.Brands.MudBlazor" Color="Color.Inherit"
|
||||
Link="https://mudblazor.com/" Target="_blank"/>
|
||||
</MudTooltip>
|
||||
<MudTooltip Duration="1000" Text="Github Repository">
|
||||
<MudIconButton Icon="@Icons.Custom.Brands.GitHub" Color="Color.Inherit"
|
||||
Link="https://github.com/asesidaa/GC-local-server-rewrite/" Target="_blank"/>
|
||||
</MudTooltip>
|
||||
<MudTooltip Duration="1000" Text="@(IsDarkMode ? "Switch to Light Mode" : "Switch to DarkMode")">
|
||||
<MudToggleIconButton @bind-Toggled="@IsDarkMode"
|
||||
Icon="@Icons.Material.Outlined.DarkMode" Color="Color.Inherit"
|
||||
ToggledIcon="@Icons.Material.Filled.LightMode"/>
|
||||
</MudTooltip>
|
||||
</MudAppBar>
|
||||
<MudDrawer @bind-Open="drawerOpen" Elevation="1">
|
||||
<MudDrawerHeader>
|
||||
<MudText Typo="Typo.h6">GC local server Admin</MudText>
|
||||
</MudDrawerHeader>
|
||||
<NavMenu/>
|
||||
</MudDrawer>
|
||||
<MudMainContent>
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="my-16 pt-16">
|
||||
@Body
|
||||
</MudContainer>
|
||||
</MudMainContent>
|
||||
</MudLayout>
|
||||
|
||||
@code {
|
||||
bool drawerOpen = true;
|
||||
|
||||
public bool IsDarkMode { get; set; }
|
||||
|
||||
public MudThemeProvider MudThemeProvider { get; set; } = null!;
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
IsDarkMode = await MudThemeProvider.GetSystemPreference();
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void DrawerToggle()
|
||||
{
|
||||
drawerOpen = !drawerOpen;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<MudNavMenu>
|
||||
<MudNavLink Href="" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.Home">Home</MudNavLink>
|
||||
<MudNavLink Href="users" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.List">Users</MudNavLink>
|
||||
</MudNavMenu>
|
||||
@@ -0,0 +1,11 @@
|
||||
@using System.Net.Http
|
||||
@using System.Net.Http.Json
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||
@using Microsoft.AspNetCore.Components.WebAssembly.Http
|
||||
@using Microsoft.JSInterop
|
||||
@using MudBlazor
|
||||
@using MudAdmin
|
||||
@using MudAdmin.Shared
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"System": "Information",
|
||||
"Microsoft": "Information"
|
||||
}
|
||||
},
|
||||
"BaseUrl": "https://127.0.0.1"
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
@@ -0,0 +1,25 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<title>MudAdmin</title>
|
||||
<base href="/" />
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" />
|
||||
<link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app">Loading...</div>
|
||||
|
||||
<div id="blazor-error-ui">
|
||||
An unhandled error has occurred.
|
||||
<a href="" class="reload">Reload</a>
|
||||
<a class="dismiss">🗙</a>
|
||||
</div>
|
||||
<script src="_framework/blazor.webassembly.js"></script>
|
||||
<script src="_content/MudBlazor/MudBlazor.min.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 818 B |
@@ -4,36 +4,212 @@ This is a rewritten version of GC local server, original version is [here](http
|
||||
|
||||
Server functionality is basically the same, but the code should be now easier to work with, and can be extended esaily.
|
||||
|
||||
Now it supports card registration.
|
||||
|
||||
# Usage
|
||||
## Usage
|
||||
|
||||
Download latest release from release page.
|
||||
|
||||
Extract anywhere.
|
||||
|
||||
If you don't have the modified NesysService.exe, modify the host file, add the following lines
|
||||
|
||||
```
|
||||
127.0.0.1 cert.nesys.jp
|
||||
127.0.0.1 data.nesys.jp
|
||||
127.0.0.1 nesys.taito.co.jp
|
||||
127.0.0.1 fjm170920zero.nesica.net
|
||||
```
|
||||
|
||||
Open mmc.exe, delete any certificate in `LocalMachine/My` or `LocalMachine/Trusted root` named `Taito Arcade Machine CA` (if present, most likely not)
|
||||
|
||||
Open exe with **admin privileges** for certificate generating functionalities to work.
|
||||
|
||||
Open game using teknoparrot loader (you can find that in discord). The loader should be also opened with **admin privileges**, otherwise it will not work.
|
||||
Open game using openparrot loader (you can find that in discord). The loader should be also opened with **admin privileges**, otherwise it will not work.
|
||||
|
||||
# Config
|
||||
## Config
|
||||
|
||||
The config file is GC-local-server-rewrite.exe.config.
|
||||
The config file is ~~GC-local-server-rewrite.exe.config~~ config.json
|
||||
|
||||
If you are using 4EX data, it should work out of box.
|
||||
If you are using 4MAX 4.71 data, it should work out of box.
|
||||
|
||||
If you are using 4MAX data, first change MusicDBName to bundled music4MAX.db3
|
||||
If you are using 4MAX 4.65 data, MusicDbName=music4MAX465.db3
|
||||
|
||||
To unlock new avatars/navgators/skins/sound effects/titles from 4MAX, change the corresponding count in config.
|
||||
If you are using 4MAX 4.61 data, first change MusicDbName to bundled music4MAX.db3
|
||||
|
||||
You can get the corresponding count in data/boot/*.dat file.
|
||||
If you are using 4EX 4.52 data, change MusicDbName to bundled music.db3
|
||||
|
||||
# Missing functions
|
||||
To unlock new avatars/navgators/skins/sound effects/titles from other version, change the corresponding count in config.
|
||||
|
||||
- [ ] Item/coin comsuming
|
||||
You can get the corresponding count in data/boot/*.dat file. It is the first 2 bytes (uint16) encoded in big endian.
|
||||
|
||||
## Event files
|
||||
|
||||
You can now add solo events. In the config file, change the following section
|
||||
|
||||
```
|
||||
"ResponseData": [
|
||||
{
|
||||
"FileName": "/event_103_20201125.evt", // This is the file name of the file to be read
|
||||
"NotBeforeUnixTime": 1335677127,
|
||||
"NotAfterUnixTime": 1966397127, // Be sure that NotBeforeUnixTime<= current time <= NotAfterUnixTime
|
||||
"Md5": "9ef6c4d5ca381583a2d99b626ce06b5e", // File MD5, must match
|
||||
"Index": 0 // Special value, must be 0 for .evt file, this is the file controlling event
|
||||
},
|
||||
{
|
||||
"FileName": "/event_20201125_reg.jpg",
|
||||
"NotBeforeUnixTime": 1335677127,
|
||||
"NotAfterUnixTime": 1966397127,
|
||||
"Md5": "8e3fe25bf50dcbed13dbb54cc18b1efa",
|
||||
"Index": 1 // Special value, must be 1 for event_reg.jpg
|
||||
},
|
||||
{
|
||||
"FileName": "/event_20201125_sgreg.png",
|
||||
"NotBeforeUnixTime": 1335677127,
|
||||
"NotAfterUnixTime": 1966397127,
|
||||
"Md5": "e0abb0503fe0c530d8a68e36994264c6",
|
||||
"Index": 2 // Special value, must be 2 for event_sreg.jpg, this is the event picture in mode selection
|
||||
},
|
||||
{
|
||||
"FileName": "/news_big_20201125_0.jpg",
|
||||
"NotBeforeUnixTime": 1335677127,
|
||||
"NotAfterUnixTime": 1966397127,
|
||||
"Md5": "4a0f66431f6449279dc046149d1dd882",
|
||||
"Index": 0 // Special value, must be 0 for first news_big.jpg, this is the picture shown before demo
|
||||
},
|
||||
{
|
||||
"FileName": "/news_big_20201125_2.jpg",
|
||||
"NotBeforeUnixTime": 1335677127,
|
||||
"NotAfterUnixTime": 1966397127,
|
||||
"Md5": "8e3fe25bf50dcbed13dbb54cc18b1efa",
|
||||
"Index": 2 // Special value, can be 2~9 for 2nd to 9th news_big.jpg, so there can be up to 9 of these
|
||||
},
|
||||
{
|
||||
"FileName": "/news_small_20201125_1.jpg",
|
||||
"NotBeforeUnixTime": 1335677127,
|
||||
"NotAfterUnixTime": 1966397127,
|
||||
"Md5": "e20135bcd41c98875aec2b52eb9fcd06",
|
||||
"Index": 1 // Special value, must be 1 for news_small.jpg, this is shown after game over
|
||||
},
|
||||
{
|
||||
"FileName": "/telop_20201125.txt",
|
||||
"NotBeforeUnixTime": 1335677127,
|
||||
"NotAfterUnixTime": 1966397127,
|
||||
"Md5": "ee228de44d6656a9ec0bb7f1a0ca64e1",
|
||||
"Index": 0 // Special value, must be 0 for telop, this is the text in scrolling banner
|
||||
},
|
||||
{
|
||||
"FileName": "/event_unlock_20201125.cmp",
|
||||
"NotBeforeUnixTime": 1335677127,
|
||||
"NotAfterUnixTime": 1966397127,
|
||||
"Md5": "623b0f10125cbe19c5394d992930ae8c",
|
||||
"Index": 8 // Special value, must be 8 for .cmp file
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
There are several types of data. File name starts with `event` is event config file. Sample
|
||||
|
||||
```
|
||||
event_id = 1
|
||||
title = "Test event"
|
||||
comment = ""
|
||||
open_date = (2021, 10, 12)
|
||||
close_date = (2022, 12, 24)
|
||||
open_time = (8, 0, 0)
|
||||
close_time = (23, 59, 0)
|
||||
ver = "4.65.00"
|
||||
play_open_time = (0, 0, 0)
|
||||
play_close_time = (0, 0, 0)
|
||||
type = 0
|
||||
challenge_num = 0
|
||||
play_mode = 1 // 1 is solo, 0 is online so currently not availabe
|
||||
|
||||
select_music_data = (185, 191, 257, 261, 263, 267, 357, 358, 363, 364, 412, 416, 419, 494, 517, 518, 558, 572, 578, 579, 580, 699, 712, 766, 775, 789, 811, 812, 813, 814) // Enabled music
|
||||
|
||||
// Reward is not implemented, also everything has already been unlocked
|
||||
reward0.type = 2
|
||||
reward0.start = 1
|
||||
reward0.end = 50
|
||||
reward0.title = 5159
|
||||
reward0.avatar = 0
|
||||
reward0.item = 21
|
||||
reward0.item_num = 3
|
||||
reward0.trophy_num = 2
|
||||
reward0.music = 0
|
||||
reward0.navigator = 0
|
||||
```
|
||||
|
||||
File name starts with `telop` is the scrolling banner, it's just a plain text file with banner content.
|
||||
|
||||
Other files like `option` and `cap` should also be possible, but I have not tested these.
|
||||
|
||||
The configured file should be put into the `event` folder, which you can change the position by using jconfig (regedit->event path)
|
||||
|
||||
The event files can also be downloaded from the server. Put the event files into `static/wwwroot/event` folder and the game will download the specified event files from the server.
|
||||
|
||||
## Missing functions
|
||||
|
||||
- [ ] Item/coin consuming
|
||||
- [ ] Unlocking system (I don't like these two so I just hardcode them, if you are interested you can implement that and add a switch to enable, PRs are welcome)
|
||||
- [ ] Ranking system (Is this really needed for a local server?)
|
||||
- [ ] Proper update check response (Now it will just throw 404)
|
||||
- [x] ~~PlayInfo.php (Now it will just throw 404, I don't know what data it expext)~~
|
||||
- [x] ~~Online matching (and online matching events)~~ Initial support
|
||||
|
||||
# Difficulty unlocking
|
||||
## Difficulty unlocking
|
||||
|
||||
This is processed on client side, so if you like to unlock all difficuties, just use bemani patcher.
|
||||
This is processed on client side, so if you like to unlock all difficulties, just use Bemani patcher with 4.52 exe (they are compatible).
|
||||
|
||||
## Deteled songs
|
||||
|
||||
If you see a lot of duplicate "Play Merrily" in the original tab, this is because in the songs db deleted songs are added back.
|
||||
|
||||
To enable these, try use the omnimixed version of stage_param.dat. That can fix this issue
|
||||
|
||||
## Local network
|
||||
|
||||
If your game and server is not on the same computer, try modify in config, change `ServerIp` to your server IP.
|
||||
|
||||
From server, open mmc.exe, export certificate named "Taito Arcade Machine CA" and "GC local server" with private key and import to game computer.
|
||||
|
||||
The cert "Taito Arcade Machine CA" goes into LocalMachine/My and Trusted root, the other only LocalMachine/My.
|
||||
|
||||
## Windows XP
|
||||
|
||||
If you are using Windows XP (e.g. using real machine), it will not recognize the generated certificate since it uses SHA256.
|
||||
|
||||
You will have to generate the certificates yourself.
|
||||
|
||||
The root certificate should have CN=Taito Arcade Machine CA, while the server certificate should have DNS entries for the domains in the host file.
|
||||
|
||||
The most important bit, **choose MD5 or SHA1 as signature algorithm**.
|
||||
|
||||
# Web interface
|
||||
|
||||
A basic web interface for check scores and set options.
|
||||
|
||||
If you want to use the interface besides localhost(127.0.0.1), change in appsettings.json:
|
||||
|
||||
```
|
||||
"BaseUrl": "http://192.168.1.1" // Change to your server ip
|
||||
```
|
||||
|
||||
Notice that due to certificate issues, you need to use http for `BaseUrl` and when accessing the web page, otherwise it will show insecure or fail.
|
||||
|
||||
If you really want https all along, make sure you have changed the `ServerIp` and have regenerated the certificate.
|
||||
|
||||
# Relay server
|
||||
|
||||
Relay server is simple, just run the executable. Default port is 3333, you can change it by provide a command line argument, e.g.
|
||||
|
||||
```
|
||||
./GCRelayServer 12345
|
||||
```
|
||||
|
||||
In main server's config.json, change the following fields to specify the relay server:
|
||||
|
||||
```
|
||||
"RelayServer": "127.0.0.1", // Server IP
|
||||
"RelayPort": 3333, // Server port
|
||||
```
|
||||
|
||||
Currently the server implementation only allow one matching at a time. Notice that if one matching ends abnormally, you will need to restart the main server.
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>SharedProject</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="protobuf-net" Version="3.1.17" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace SharedProject.common;
|
||||
|
||||
public record ScoreGradeMap(int Score, string Grade)
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{{ Score = {Score}, Grade = {Grade} }}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
namespace SharedProject.common;
|
||||
|
||||
public static class SharedConstants
|
||||
{
|
||||
public const int DIFFICULTY_COUNT = 4;
|
||||
|
||||
public const int E_SCORE_THRESHOLD = 0;
|
||||
|
||||
public const int D_SCORE_THRESHOLD = 300000;
|
||||
|
||||
public const int C_SCORE_THRESHOLD = 500000;
|
||||
|
||||
public const int B_SCORE_THRESHOLD = 700000;
|
||||
|
||||
public const int A_SCORE_THRESHOLD = 800000;
|
||||
|
||||
public const int S_SCORE_THRESHOLD = 900000;
|
||||
|
||||
public const int S_PLUS_SCORE_THRESHOLD = 950000;
|
||||
|
||||
public const int S_PLUS_PLUS_SCORE_THRESHOLD = 990000;
|
||||
|
||||
public const int MAX_PLAYER_NAME_LENGTH = 8;
|
||||
|
||||
public const string NAVIGATOR_DAT_URI = "data/navigator.dat";
|
||||
|
||||
public const string AVATAR_DAT_URI = "data/avatar.dat";
|
||||
|
||||
public const string TITLE_DAT_URI = "data/title.dat";
|
||||
|
||||
public static readonly ScoreGradeMap[] Grades =
|
||||
{
|
||||
new(E_SCORE_THRESHOLD, "E"),
|
||||
new(D_SCORE_THRESHOLD, "D"),
|
||||
new(C_SCORE_THRESHOLD, "C"),
|
||||
new(B_SCORE_THRESHOLD, "B"),
|
||||
new(A_SCORE_THRESHOLD, "A"),
|
||||
new(S_SCORE_THRESHOLD, "S"),
|
||||
new(S_PLUS_SCORE_THRESHOLD, "S+"),
|
||||
new(S_PLUS_PLUS_SCORE_THRESHOLD, "S++")
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace SharedProject.enums;
|
||||
|
||||
public enum ClearState
|
||||
{
|
||||
NotPlayed = 0,
|
||||
Failed,
|
||||
Clear,
|
||||
NoMiss,
|
||||
FullChain,
|
||||
Perfect
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace SharedProject.enums;
|
||||
|
||||
public enum Difficulty
|
||||
{
|
||||
Simple = 0,
|
||||
Normal = 1,
|
||||
Hard = 2,
|
||||
Extra = 3
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
namespace SharedProject.enums;
|
||||
|
||||
public static class Extensions
|
||||
{
|
||||
public static string GetHelpText(this PlayOptions.FastSlowIndicator indicator)
|
||||
{
|
||||
return indicator switch
|
||||
{
|
||||
PlayOptions.FastSlowIndicator.NotUsed => "Do not show FAST/SLOW indicator",
|
||||
PlayOptions.FastSlowIndicator.NearAvatar => "Show FAST/SLOW indicator near avatar",
|
||||
PlayOptions.FastSlowIndicator.NearJudgement => "Show FAST/SLOW indicator near judgement text",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(indicator), indicator, null)
|
||||
};
|
||||
}
|
||||
|
||||
public static string GetHelpText(this PlayOptions.FeverTranceShow show)
|
||||
{
|
||||
return show switch
|
||||
{
|
||||
PlayOptions.FeverTranceShow.NotUsed => "Do not show FEVER/TRANCE",
|
||||
PlayOptions.FeverTranceShow.Show => "Show FEVER/TRANCE",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(show), show, null)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using ProtoBuf;
|
||||
|
||||
namespace SharedProject.enums;
|
||||
|
||||
[ProtoContract]
|
||||
public enum NavigatorDefaultAvailability
|
||||
{
|
||||
NotAvailable = 0,
|
||||
Available = 1,
|
||||
AvailableWithVoice = 2,
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using ProtoBuf;
|
||||
|
||||
namespace SharedProject.enums;
|
||||
|
||||
[ProtoContract]
|
||||
public enum NavigatorGenre
|
||||
{
|
||||
Default = 1,
|
||||
Original = 2,
|
||||
Game = 3,
|
||||
Touhou = 4,
|
||||
Vocaloid = 5,
|
||||
Collab = 6,
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace SharedProject.enums;
|
||||
|
||||
public static class PlayOptions
|
||||
{
|
||||
public enum FastSlowIndicator
|
||||
{
|
||||
NotUsed = 3,
|
||||
NearAvatar = 1,
|
||||
NearJudgement = 2
|
||||
}
|
||||
|
||||
public enum FeverTranceShow
|
||||
{
|
||||
NotUsed = 2,
|
||||
Show = 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using ProtoBuf;
|
||||
|
||||
namespace SharedProject.enums;
|
||||
|
||||
[ProtoContract]
|
||||
public enum TitleUnlockType
|
||||
{
|
||||
Invalid = 0,
|
||||
Default = 1,
|
||||
Clear = 2,
|
||||
NoMiss = 3,
|
||||
FullChain = 4,
|
||||
SRankSimpleStages = 5,
|
||||
SRankNormalStages = 6,
|
||||
SRankHardStages = 7,
|
||||
SRankExtraStages = 8,
|
||||
SRankAllDifficulties = 9,
|
||||
SPlusRankAllDifficulties = 10,
|
||||
SPlusPlusRankAllDifficulties = 11,
|
||||
Event = 12,
|
||||
Prefecture = 13,
|
||||
ChainMilestone = 14,
|
||||
Adlibs = 15,
|
||||
ConsecutiveNoMiss = 16,
|
||||
ClearsUsingItems = 17,
|
||||
Avatars = 18,
|
||||
MultiplayerStarsTotal = 19,
|
||||
SongSet20 = 20,
|
||||
SongSet21 = 21,
|
||||
SongSet22 = 22,
|
||||
SongSet23 = 23,
|
||||
SongSet24 = 24,
|
||||
SongSet25 = 25,
|
||||
SongSet26 = 26,
|
||||
ProfileLevel = 27,
|
||||
Perfect = 28,
|
||||
OnlineMatching = 29,
|
||||
Trophies = 30,
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using ProtoBuf;
|
||||
|
||||
namespace SharedProject.models;
|
||||
|
||||
[ProtoContract]
|
||||
public class Avatar
|
||||
{
|
||||
[ProtoMember(1)]
|
||||
public uint Id { get; set; }
|
||||
|
||||
[ProtoMember(2)]
|
||||
public string? IdString { get; set; }
|
||||
|
||||
[ProtoMember(3)]
|
||||
public string? FullName { get; set; }
|
||||
|
||||
[ProtoMember(4)]
|
||||
public string? Name { get; set; }
|
||||
|
||||
[ProtoMember(5)]
|
||||
public string? Variant { get; set; }
|
||||
|
||||
[ProtoMember(6)]
|
||||
public string? AcquireMethodJp { get; set; }
|
||||
|
||||
[ProtoMember(7)]
|
||||
public string? AcquireMethodEn { get; set; }
|
||||
|
||||
public override string ToString() {
|
||||
return $"{Id}: {FullName}, {AcquireMethodEn}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace SharedProject.models;
|
||||
|
||||
public class MusicFavoriteData
|
||||
{
|
||||
[JsonPropertyName(nameof(CardId))]
|
||||
public long CardId { get; set; }
|
||||
|
||||
[JsonPropertyName(nameof(MusicId))]
|
||||
public int MusicId { get; set; }
|
||||
|
||||
[JsonPropertyName(nameof(IsFavorite))]
|
||||
public bool IsFavorite { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using ProtoBuf;
|
||||
|
||||
namespace SharedProject.models;
|
||||
|
||||
[ProtoContract]
|
||||
public class NameEntry
|
||||
{
|
||||
[ProtoMember(1)]
|
||||
public string? NameWithVariant { get; set; }
|
||||
[ProtoMember(2)]
|
||||
public string? NameWithoutVariant{ get; set; }
|
||||
[ProtoMember(3)]
|
||||
public string? Variant{ get; set; }
|
||||
[ProtoMember(4)]
|
||||
public string? IllustrationCredit{ get; set; }
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{NameWithVariant}, {IllustrationCredit}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using ProtoBuf;
|
||||
using SharedProject.enums;
|
||||
|
||||
namespace SharedProject.models;
|
||||
|
||||
[ProtoContract]
|
||||
public class Navigator
|
||||
{
|
||||
[ProtoMember(1)]
|
||||
public uint Id { get; set; }
|
||||
|
||||
[ProtoMember(2)]
|
||||
public string? IdString { get; set; }
|
||||
|
||||
[ProtoMember(3)]
|
||||
public string? FileName { get; set; }
|
||||
|
||||
[ProtoMember(4)]
|
||||
public NameEntry? NameEntry0 { get; set; }
|
||||
|
||||
[ProtoMember(5)]
|
||||
public NameEntry? NameEntry1 { get; set; }
|
||||
|
||||
[ProtoMember(6)]
|
||||
public NavigatorGenre Genre { get; set; }
|
||||
|
||||
[ProtoMember(7)]
|
||||
public NavigatorDefaultAvailability DefaultAvailability { get; set; }
|
||||
|
||||
[ProtoMember(8)]
|
||||
public string? ToolTipJp { get; set; }
|
||||
|
||||
[ProtoMember(9)]
|
||||
public string? ToolTipEn { get; set; }
|
||||
|
||||
[ProtoIgnore]
|
||||
public string? NameEntryString => NameEntry0?.ToString();
|
||||
|
||||
public override string ToString() {
|
||||
return $"{Id}: {NameEntry1?.NameWithVariant}, {NameEntry1?.IllustrationCredit}";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using ProtoBuf;
|
||||
|
||||
namespace SharedProject.models;
|
||||
|
||||
[ProtoContract]
|
||||
public class Navigators
|
||||
{
|
||||
[ProtoMember(1)]
|
||||
public int Count { get; set; }
|
||||
|
||||
[ProtoMember(2)]
|
||||
public List<Navigator>? NavigatorList { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using SharedProject.enums;
|
||||
|
||||
namespace SharedProject.models;
|
||||
|
||||
public class PlayOption
|
||||
{
|
||||
[JsonPropertyName(nameof(CardId))]
|
||||
public long CardId { get; set; }
|
||||
|
||||
[JsonPropertyName(nameof(FastSlowIndicator))]
|
||||
public PlayOptions.FastSlowIndicator FastSlowIndicator { get; set; }
|
||||
|
||||
[JsonPropertyName(nameof(FeverTrance))]
|
||||
public PlayOptions.FeverTranceShow FeverTrance { get; set; }
|
||||
|
||||
[JsonPropertyName(nameof(AvatarId))]
|
||||
public long AvatarId { get; set; }
|
||||
|
||||
[JsonPropertyName(nameof(NavigatorId))]
|
||||
public long NavigatorId { get; set; }
|
||||
|
||||
[JsonPropertyName(nameof(TitleId))]
|
||||
public long TitleId { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using SharedProject.common;
|
||||
using SharedProject.enums;
|
||||
|
||||
namespace SharedProject.models;
|
||||
|
||||
public class SongPlayData
|
||||
{
|
||||
public string Title { get; set; } = string.Empty;
|
||||
|
||||
public string Artist { get; set; } = string.Empty;
|
||||
|
||||
public int MusicId { get; set; }
|
||||
|
||||
public SongPlayDetailData[] SongPlaySubDataList { get; set; } = new SongPlayDetailData[SharedConstants.DIFFICULTY_COUNT];
|
||||
|
||||
public bool IsFavorite { get; set; }
|
||||
|
||||
public bool ShowDetails { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public int TotalPlayCount
|
||||
{
|
||||
get
|
||||
{
|
||||
return SongPlaySubDataList.Sum(data => data.PlayCount);
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public DateTime LastPlayTime
|
||||
{
|
||||
get
|
||||
{
|
||||
var songPlayDetailData = SongPlaySubDataList.Where(data => data.ClearState != ClearState.NotPlayed)
|
||||
.MinBy(data => data.LastPlayTime);
|
||||
return songPlayDetailData?.LastPlayTime ?? DateTime.MaxValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using SharedProject.enums;
|
||||
|
||||
namespace SharedProject.models;
|
||||
|
||||
public class SongPlayDetailData
|
||||
{
|
||||
public int Score { get; set; }
|
||||
|
||||
public int PlayCount { get; set; }
|
||||
|
||||
public int MaxChain { get; set; }
|
||||
|
||||
public Difficulty Difficulty { get; set; }
|
||||
|
||||
public ClearState ClearState { get; set; }
|
||||
|
||||
public DateTime LastPlayTime { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using ProtoBuf;
|
||||
using SharedProject.enums;
|
||||
|
||||
namespace SharedProject.models;
|
||||
|
||||
[ProtoContract]
|
||||
public class Title
|
||||
{
|
||||
[ProtoMember(1)]
|
||||
public uint Id { get; set; }
|
||||
|
||||
[ProtoMember(2)]
|
||||
public string? IdString { get; set; }
|
||||
|
||||
[ProtoMember(3)]
|
||||
public string? NameJp { get; set; }
|
||||
|
||||
[ProtoMember(4)]
|
||||
public string? NameEng { get; set; }
|
||||
|
||||
[ProtoMember(5)]
|
||||
public string? UnlockRequirementJp { get; set; }
|
||||
|
||||
[ProtoMember(6)]
|
||||
public string? UnlockRequirementEng { get; set; }
|
||||
|
||||
[ProtoMember(7)]
|
||||
public TitleUnlockType Type { get; set; }
|
||||
public override string ToString() {
|
||||
return $"{Id}: {NameEng}, {UnlockRequirementEng}";
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user