commit d9c717de6f766e6e2844398f452a53d4d4f01a8b Author: Yuchen Ji Date: Tue Apr 5 01:55:32 2022 +0800 Initial commit diff --git a/.idea/.idea.Embedio-test/.idea/.gitignore b/.idea/.idea.Embedio-test/.idea/.gitignore new file mode 100644 index 0000000..0d0330a --- /dev/null +++ b/.idea/.idea.Embedio-test/.idea/.gitignore @@ -0,0 +1,13 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/projectSettingsUpdater.xml +/.idea.Embedio-test.iml +/modules.xml +/contentModel.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/.idea.Embedio-test/.idea/dataSources.xml b/.idea/.idea.Embedio-test/.idea/dataSources.xml new file mode 100644 index 0000000..336bd4f --- /dev/null +++ b/.idea/.idea.Embedio-test/.idea/dataSources.xml @@ -0,0 +1,29 @@ + + + + + sqlite.xerial + true + org.sqlite.JDBC + jdbc:sqlite:C:\Users\10614\RiderProjects\Embedio-test\GC-local-server-rewrite\db\card.db3 + $ProjectFileDir$ + + + file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.36.0.3/sqlite-jdbc-3.36.0.3.jar + + + + + sqlite.xerial + true + org.sqlite.JDBC + jdbc:sqlite:C:\Users\10614\RiderProjects\Embedio-test\GC-local-server-rewrite\db\music.db3 + $ProjectFileDir$ + + + file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.36.0.3/sqlite-jdbc-3.36.0.3.jar + + + + + \ No newline at end of file diff --git a/.idea/.idea.Embedio-test/.idea/encodings.xml b/.idea/.idea.Embedio-test/.idea/encodings.xml new file mode 100644 index 0000000..df87cf9 --- /dev/null +++ b/.idea/.idea.Embedio-test/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/.idea.Embedio-test/.idea/indexLayout.xml b/.idea/.idea.Embedio-test/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/.idea/.idea.Embedio-test/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.GC-local-server-rewrite/.idea/.gitignore b/.idea/.idea.GC-local-server-rewrite/.idea/.gitignore new file mode 100644 index 0000000..1d36e00 --- /dev/null +++ b/.idea/.idea.GC-local-server-rewrite/.idea/.gitignore @@ -0,0 +1,13 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/modules.xml +/.idea.GC-local-server-rewrite.iml +/projectSettingsUpdater.xml +/contentModel.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/.idea.GC-local-server-rewrite/.idea/dataSources.xml b/.idea/.idea.GC-local-server-rewrite/.idea/dataSources.xml new file mode 100644 index 0000000..7aefeff --- /dev/null +++ b/.idea/.idea.GC-local-server-rewrite/.idea/dataSources.xml @@ -0,0 +1,17 @@ + + + + + sqlite.xerial + true + org.sqlite.JDBC + jdbc:sqlite:C:\Users\10614\RiderProjects\GC-local-server-rewrite\GC-local-server-rewrite\db\card.db3 + $ProjectFileDir$ + + + file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.36.0.3/sqlite-jdbc-3.36.0.3.jar + + + + + \ No newline at end of file diff --git a/.idea/.idea.GC-local-server-rewrite/.idea/encodings.xml b/.idea/.idea.GC-local-server-rewrite/.idea/encodings.xml new file mode 100644 index 0000000..df87cf9 --- /dev/null +++ b/.idea/.idea.GC-local-server-rewrite/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/.idea.GC-local-server-rewrite/.idea/indexLayout.xml b/.idea/.idea.GC-local-server-rewrite/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/.idea/.idea.GC-local-server-rewrite/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.GC-local-server-rewrite/.idea/vcs.xml b/.idea/.idea.GC-local-server-rewrite/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/.idea.GC-local-server-rewrite/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/GC-local-server-rewrite.sln b/GC-local-server-rewrite.sln new file mode 100644 index 0000000..23ac0c6 --- /dev/null +++ b/GC-local-server-rewrite.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +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 +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D5FFF2E8-6591-4967-8883-A28F453F0524}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {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 + EndGlobalSection +EndGlobal diff --git a/GC-local-server-rewrite.sln.DotSettings.user b/GC-local-server-rewrite.sln.DotSettings.user new file mode 100644 index 0000000..df95ebc --- /dev/null +++ b/GC-local-server-rewrite.sln.DotSettings.user @@ -0,0 +1,6 @@ + + True + True + True + True + True \ No newline at end of file diff --git a/GC-local-server-rewrite/.gitignore b/GC-local-server-rewrite/.gitignore new file mode 100644 index 0000000..c32c614 --- /dev/null +++ b/GC-local-server-rewrite/.gitignore @@ -0,0 +1,437 @@ +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### VisualStudio template +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + diff --git a/GC-local-server-rewrite/GC-local-server-rewrite.csproj b/GC-local-server-rewrite/GC-local-server-rewrite.csproj new file mode 100644 index 0000000..b2a88c3 --- /dev/null +++ b/GC-local-server-rewrite/GC-local-server-rewrite.csproj @@ -0,0 +1,25 @@ + + + + Exe + net6.0 + GCLocalServerRewrite + enable + enable + + + + + + + + + + + + + + + + + diff --git a/GC-local-server-rewrite/GC-local-server-rewrite.exe.config.xml b/GC-local-server-rewrite/GC-local-server-rewrite.exe.config.xml new file mode 100644 index 0000000..b272d74 --- /dev/null +++ b/GC-local-server-rewrite/GC-local-server-rewrite.exe.config.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/GC-local-server-rewrite/Program.cs b/GC-local-server-rewrite/Program.cs new file mode 100644 index 0000000..fe04695 --- /dev/null +++ b/GC-local-server-rewrite/Program.cs @@ -0,0 +1,85 @@ +using GCLocalServerRewrite.common; +using GCLocalServerRewrite.server; +using SQLitePCL; +using Swan; +using Swan.Logging; + +namespace GCLocalServerRewrite; + +internal class Program +{ + private static void Main(string[] args) + { + Batteries_V2.Init(); + + InitializeLogging(); + + var urlPrefixes = args.Length > 0 ? args : new[] { "http://*:80", "https://*:443" }; + + using (var cts = new CancellationTokenSource()) + { + Task.WaitAll( + RunWebServerAsync(urlPrefixes, cts.Token), + Task.CompletedTask, + WaitForUserBreakAsync(cts.Cancel)); + } + + // Clean up + "Bye".Info(nameof(Program)); + Terminal.Flush(); + + Console.WriteLine("Press any key to exit."); + WaitForKeypress(); + } + + private static void InitializeLogging() + { + if (!Directory.Exists(PathHelper.LogRootPath)) + { + Directory.CreateDirectory(PathHelper.LogRootPath); + } + + Logger.RegisterLogger(new FileLogger(PathHelper.LogRootPath, true)); + } + + + /// + /// Create and run a web server. + /// + /// + /// + private static async Task RunWebServerAsync(string[] urlPrefixes, CancellationToken cancellationToken) + { + using var server = Server.CreateWebServer(urlPrefixes); + await server.RunAsync(cancellationToken).ConfigureAwait(false); + } + + /// + /// Prompt the user to press any key; + /// when a key is next pressed, call the specified action to cancel operations. + /// + /// Cancel Action to call + private static async Task WaitForUserBreakAsync(Action cancel) + { + // Be sure to run in parallel. + await Task.Yield(); + + "Press any key to stop the web server.".Info(nameof(Program)); + WaitForKeypress(); + "Stopping...".Info(nameof(Program)); + cancel(); + } + + /// + /// Clear the console input buffer and wait for a keypress + /// + private static void WaitForKeypress() + { + while (Console.KeyAvailable) + { + Console.ReadKey(true); + } + + Console.ReadKey(true); + } +} \ No newline at end of file diff --git a/GC-local-server-rewrite/backports/CustomResponseSerializer.cs b/GC-local-server-rewrite/backports/CustomResponseSerializer.cs new file mode 100644 index 0000000..d6de2ab --- /dev/null +++ b/GC-local-server-rewrite/backports/CustomResponseSerializer.cs @@ -0,0 +1,74 @@ +using EmbedIO; + +namespace GCLocalServerRewrite.backports; + +/// +/// Provides custom response serializer callbacks. +/// +public static class CustomResponseSerializer +{ + private static readonly ResponseSerializerCallback CHUNKED_ENCODING_BASE_SERIALIZER = GetBaseSerializer(false); + private static readonly ResponseSerializerCallback BUFFERING_BASE_SERIALIZER = GetBaseSerializer(true); + + /// + /// Sends data in a HTTP response without serialization. + /// + /// + /// to write the response body to a memory buffer first, + /// then send it all together with a Content-Length header; to use chunked + /// transfer encoding. + /// + /// A that can be used to serialize data to a HTTP response. + /// + /// + /// s and one-dimensional arrays of s + /// are sent to the client unchanged; every other type is converted to a string. + /// + /// + /// The ContentType set on the response is used to negotiate + /// a compression method, according to request headers. + /// + /// + /// Strings (and other types converted to strings) are sent with the encoding specified by + /// . + /// + /// + 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); + } + }; + } +} \ No newline at end of file diff --git a/GC-local-server-rewrite/common/CertificateHelper.cs b/GC-local-server-rewrite/common/CertificateHelper.cs new file mode 100644 index 0000000..261fbba --- /dev/null +++ b/GC-local-server-rewrite/common/CertificateHelper.cs @@ -0,0 +1,234 @@ +using System.Diagnostics; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using CertificateManager; +using CertificateManager.Models; +using Microsoft.Extensions.DependencyInjection; +using Swan; +using Swan.Logging; + +namespace GCLocalServerRewrite.common; + +public static class CertificateHelper +{ + private const X509KeyUsageFlags ROOT_CA_X509_KEY_USAGE_FLAGS = X509KeyUsageFlags.KeyCertSign | + X509KeyUsageFlags.DataEncipherment | + X509KeyUsageFlags.KeyEncipherment | + X509KeyUsageFlags.DigitalSignature; + + private const X509KeyStorageFlags X509_KEY_STORAGE_FLAGS_MACHINE = X509KeyStorageFlags.PersistKeySet + | X509KeyStorageFlags.MachineKeySet; + + private const X509KeyUsageFlags CERT_X509_KEY_USAGE_FLAGS = X509KeyUsageFlags.DataEncipherment | + X509KeyUsageFlags.KeyEncipherment | + X509KeyUsageFlags.DigitalSignature; + + private static readonly DistinguishedName ROOT_CA_DISTINGUISHED_NAME = new() + { + CommonName = Configs.ROOT_CA_CN + }; + + private static readonly DistinguishedName CERT_DISTINGUISHED_NAME = new() + { + CommonName = Configs.CERT_CN + }; + + private static readonly BasicConstraints ROOT_CA_BASIC_CONSTRAINTS = new() + { + CertificateAuthority = true, + HasPathLengthConstraint = true, + PathLengthConstraint = 3, + Critical = true + }; + + public static readonly BasicConstraints CERT_BASIC_CONSTRAINTS = new() + { + CertificateAuthority = false, + HasPathLengthConstraint = false, + PathLengthConstraint = 0, + Critical = true, + }; + + private static readonly SubjectAlternativeName SUBJECT_ALTERNATIVE_NAME = new() + { + DnsName = new List + { + "localhost" + } + }; + + private static readonly ValidityPeriod VALIDITY_PERIOD = new() + { + ValidFrom = DateTime.UtcNow, + ValidTo = DateTime.UtcNow.AddYears(3) + }; + + private static readonly OidCollection OID_COLLECTION = new() + { + OidLookup.AnyPurpose + }; + + public static X509Certificate2 InitializeCertificate() + { + if (CertificateExists()) + { + var existingCert = GetCertificate(StoreName.My, StoreLocation.LocalMachine, Configs.CERT_CN); + + if (existingCert != null) + { + return existingCert; + } + } + + RemovePreviousCert(StoreName.My, StoreLocation.LocalMachine); + RemovePreviousCert(StoreName.Root, StoreLocation.LocalMachine); + + var serviceProvider = new ServiceCollection() + .AddCertificateManager().BuildServiceProvider(); + + var createCertificates = serviceProvider.GetService(); + + if (createCertificates == null) + { + throw SelfCheck.Failure("Cannot initialize CreateCertificates service!"); + } + + var rootCa = createCertificates.NewRsaSelfSignedCertificate( + ROOT_CA_DISTINGUISHED_NAME, + ROOT_CA_BASIC_CONSTRAINTS, + VALIDITY_PERIOD, + SUBJECT_ALTERNATIVE_NAME, + OID_COLLECTION, + ROOT_CA_X509_KEY_USAGE_FLAGS, + new RsaConfiguration() + ); + + var cert = createCertificates.NewRsaChainedCertificate( + CERT_DISTINGUISHED_NAME, + CERT_BASIC_CONSTRAINTS, + VALIDITY_PERIOD, + SUBJECT_ALTERNATIVE_NAME, + rootCa, + OID_COLLECTION, + CERT_X509_KEY_USAGE_FLAGS, + new RsaConfiguration() + ); + + var exportService = serviceProvider.GetService(); + + if (exportService == null) + { + throw SelfCheck.Failure("Cannot initialize ImportExportCertificate service!"); + } + + var rootCaPfxBytes = exportService.ExportRootPfx(null, rootCa); + var certPfxBytes = exportService.ExportChainedCertificatePfx(null, cert, rootCa); + + var rootCaWithPrivateKey = new X509Certificate2(rootCaPfxBytes, (string)null!, + X509_KEY_STORAGE_FLAGS_MACHINE); + + 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); + + return certWithPrivateKey; + } + + private static void AddCertToStore(X509Certificate2 cert, StoreName storeName, StoreLocation storeLocation) + { + try + { + var store = new X509Store(storeName, storeLocation); + store.Open(OpenFlags.ReadWrite); + store.Add(cert); + + store.Close(); + } + catch (Exception e) + { + e.Error(e.Source ?? "", e.Message); + } + } + + private static void RemovePreviousCert(StoreName storeName, StoreLocation storeLocation) + { + try + { + var store = new X509Store(storeName, storeLocation); + store.Open(OpenFlags.ReadWrite); + var result = store.Certificates.Find(X509FindType.FindByIssuerName, Configs.ROOT_CA_CN, true); + + if (result.Any()) + { + store.RemoveRange(result); + "Removed previous certs!".Info(); + } + + store.Close(); + } + catch (Exception e) + { + e.Error(e.Source ?? "", e.Message); + } + } + + private static bool CertificateExists() + { + try + { + var store = new X509Store(StoreName.My, StoreLocation.LocalMachine); + store.Open(OpenFlags.ReadOnly); + var result = store.Certificates.Find(X509FindType.FindByIssuerName, Configs.ROOT_CA_CN, true); + + if (result.Count == 2) + { + "Certificate exists!".Info(); + + return true; + } + + store.Close(); + "Certificate not found! Will generate new certs...".Info(); + + return false; + } + catch (Exception e) + { + e.Error(e.Source ?? "", e.Message); + + return false; + } + } + + private static X509Certificate2? GetCertificate(StoreName storeName, StoreLocation storeLocation, string commonName) + { + try + { + var store = new X509Store(storeName, storeLocation); + store.Open(OpenFlags.ReadWrite); + var result = store.Certificates.Find(X509FindType.FindBySubjectDistinguishedName, + $"CN={commonName}", true); + + if (result.Any()) + { + $"Certificate CN={commonName} found!".Info(); + + return result.First(); + } + + store.Close(); + + return null; + } + catch (Exception e) + { + e.Error(e.Source ?? "", e.Message); + + return null; + } + } +} \ No newline at end of file diff --git a/GC-local-server-rewrite/common/Configs.cs b/GC-local-server-rewrite/common/Configs.cs new file mode 100644 index 0000000..ac6b612 --- /dev/null +++ b/GC-local-server-rewrite/common/Configs.cs @@ -0,0 +1,140 @@ +using System.Configuration; + +namespace GCLocalServerRewrite.common; + +public static class Configs +{ + public const bool USE_FILE_CACHE = true; + + public const string ROOT_CA_CN = "Taito Arcade Machine CA"; + + public const string CERT_CN = "GC local server"; + + public const string DB_FOLDER = "db"; + + public const string LOG_FOLDER = "log"; + + public const string LOG_BASE_NAME = "log"; + + public const string STATIC_FOLDER = "static"; + + public const string CARD_SERVICE_BASE_ROUTE = "/service/card"; + + public const string UPLOAD_SERVICE_BASE_ROUTE = "/service/upload"; + + public const string RESPONE_SERVICE_BASE_ROUTE = "/service/respone"; + + public const string INCOM_SERVICE_BASE_ROUTE = "/service/incom"; + + public const string RANK_BASE_ROUTE = "/ranking"; + + public const string ALIVE_BASE_ROUTE = "/alive"; + + public const string SERVER_BASE_ROUTE = "/server"; + + public const string STATIC_BASE_ROUTE = "/static"; + + public const string CARD_DB_NAME = "card.db3"; + + public const string MUSIC_DB_NAME = "music.db3"; + + public const string ROOT_XPATH = "/root"; + + public const string DATA_XPATH = $"{ROOT_XPATH}/data"; + + public const string CARD = "card"; + + public const string CARD_XPATH = $"{ROOT_XPATH}/{CARD}"; + + public const string CARD_DETAIL = "card_detail"; + + public const string CARD_DETAIL_XPATH = $"{ROOT_XPATH}/{CARD_DETAIL}"; + + public const string CARD_DETAIL_RECORD_XPATH = $"{CARD_DETAIL_XPATH}/record"; + + public const string CARD_BDATA = "card_bdata"; + + public const string CARD_BDATA_XPATH = $"{ROOT_XPATH}/{CARD_BDATA}"; + + public const string MUSIC = "music"; + + public const string MUSIC_XPATH = $"{ROOT_XPATH}/{MUSIC}/record"; + + public const string MUSIC_EXTRA = "music_extra"; + + public const string MUSIC_EXTRA_XPATH = $"{ROOT_XPATH}/{MUSIC_EXTRA}/record"; + + public const string MUSIC_AOU = "music_aou"; + + public const string MUSIC_AOU_XPATH = $"{ROOT_XPATH}/{MUSIC_AOU}"; + + public const string ITEM = "item"; + + public const string ITEM_XPATH = $"{ROOT_XPATH}/{ITEM}/record"; + + public const string AVATAR = "avatar"; + + public const string AVATAR_XPATH = $"{ROOT_XPATH}/{AVATAR}/record"; + + public const string SKIN = "skin"; + + public const string SKIN_XPATH = $"{ROOT_XPATH}/{SKIN}/record"; + + public const string TITLE = "title"; + + public const string TITLE_XPATH = $"{ROOT_XPATH}/{TITLE}/record"; + + public const string NAVIGATOR = "navigator"; + + public const string NAVIGATOR_XPATH = $"{ROOT_XPATH}/{NAVIGATOR}/record"; + + public const string COIN = "coin"; + + public const string COIN_XPATH = $"{ROOT_XPATH}/{COIN}"; + + public const string UNLOCK_REWARD = "unlock_reward"; + + public const string UNLOCK_REWARD_XPATH = $"{ROOT_XPATH}/{UNLOCK_REWARD}/record"; + + public const string UNLOCK_KEYNUM = "unlock_keynum"; + + public const string UNLOCK_KEYNUM_XPATH = $"{ROOT_XPATH}/{UNLOCK_KEYNUM}/record"; + + public const string SOUND_EFFECT = "sound_effect"; + + public const string SE_XPATH = $"{ROOT_XPATH}/{SOUND_EFFECT}/record"; + + public const string GET_MESSAGE = "get_message"; + + public const string TOTAL_TROPHY = "total_trophy"; + + public const string TOTAL_TROPHY_XPATH = $"{ROOT_XPATH}/{TOTAL_TROPHY}"; + + public const string EVENT_REWARD = "event_reward"; + + public const string COND = "cond"; + + public const string SESSION_XPATH = $"{ROOT_XPATH}/session"; + + public const string RANK_STATUS_XPATH = $"{ROOT_XPATH}/ranking_status"; + + public const int GC4_EX_GID = 303801; + + public static readonly int AVATAR_COUNT = int.Parse( + ConfigurationManager.AppSettings.Get("AvatarCount") ?? "294"); + + public static readonly int NAVIGATOR_COUNT = int.Parse( + ConfigurationManager.AppSettings.Get("NavigatorCount") ?? "71"); + + public static readonly int ITEM_COUNT = int.Parse( + ConfigurationManager.AppSettings.Get("ItemCount") ?? "21"); + + public static readonly int TITLE_COUNT = int.Parse( + ConfigurationManager.AppSettings.Get("TitleCount") ?? "4942"); + + public static readonly int SKIN_COUNT = int.Parse( + ConfigurationManager.AppSettings.Get("SkinCount") ?? "21"); + + public static readonly int SE_COUNT = int.Parse( + ConfigurationManager.AppSettings.Get("SeCount") ?? "25"); +} \ No newline at end of file diff --git a/GC-local-server-rewrite/common/DatabaseHelper.cs b/GC-local-server-rewrite/common/DatabaseHelper.cs new file mode 100644 index 0000000..2499ee3 --- /dev/null +++ b/GC-local-server-rewrite/common/DatabaseHelper.cs @@ -0,0 +1,40 @@ +using SQLite.Net2; + +namespace GCLocalServerRewrite.common; + +public class DatabaseHelper +{ + /// + /// Static method to allow local data services to initialise their associated database conveniently. + /// + /// The SQLite database name + /// The SQLite database tables to create (if required) + /// An initialised SQLite database connection + public static SQLiteConnection InitializeLocalDatabase(string databaseName, params Type[] tables) + { + if (!Directory.Exists(PathHelper.DataBaseRootPath)) + { + Directory.CreateDirectory(PathHelper.DataBaseRootPath); + } + + var databasePath = Path.Combine(PathHelper.DataBaseRootPath, databaseName); + + var database = new SQLiteConnection(databasePath); + + foreach (var table in tables) + { + database.CreateTable(table); + } + + return database; + } + + public static SQLiteConnection ConnectDatabase(string databaseName) + { + var databasePath = Path.Combine(PathHelper.DataBaseRootPath, databaseName); + + var database = new SQLiteConnection(databasePath); + + return database; + } +} \ No newline at end of file diff --git a/GC-local-server-rewrite/common/PathHelper.cs b/GC-local-server-rewrite/common/PathHelper.cs new file mode 100644 index 0000000..5026d69 --- /dev/null +++ b/GC-local-server-rewrite/common/PathHelper.cs @@ -0,0 +1,39 @@ +using System.Diagnostics; + +namespace GCLocalServerRewrite.common; + +public class PathHelper +{ + /// + /// Gets the local path of html/static files. + /// + public static string HtmlRootPath => Path.Combine(BasePath, Configs.STATIC_FOLDER); + + /// + /// Root path for database, when debug, it's under source root, when release, it's the exe dir + /// + public static string DataBaseRootPath => Path.Combine(BasePath, Configs.DB_FOLDER); + + public static string LogRootPath => Path.Combine(BasePath, Configs.LOG_FOLDER, Configs.LOG_BASE_NAME); + + private static string BasePath + { + get + { + var assemblyPath = Path.GetDirectoryName(typeof(Program).Assembly.Location); + +#if DEBUG + + Debug.Assert(assemblyPath != null, $"{nameof(assemblyPath)} != null"); + + var parentFullName = Directory.GetParent(assemblyPath)?.Parent?.Parent?.FullName; + + Debug.Assert(parentFullName != null, $"{nameof(parentFullName)} != null"); + + return parentFullName; +#else + return assemblyPath; +#endif + } + } +} \ No newline at end of file diff --git a/GC-local-server-rewrite/controllers/AliveController.cs b/GC-local-server-rewrite/controllers/AliveController.cs new file mode 100644 index 0000000..977697c --- /dev/null +++ b/GC-local-server-rewrite/controllers/AliveController.cs @@ -0,0 +1,34 @@ +using System.Net; +using System.Net.Mime; +using System.Text; +using EmbedIO; +using EmbedIO.Routing; +using EmbedIO.WebApi; + +namespace GCLocalServerRewrite.controllers; + +public class AliveController : WebApiController +{ + [Route(HttpVerbs.Get, "/i.php")] + // ReSharper disable once UnusedMember.Global + public string Check() + { + HttpContext.Response.ContentType = MediaTypeNames.Text.Html; + HttpContext.Response.ContentEncoding = Encoding.Default; + HttpContext.Response.KeepAlive = true; + + return "REMOTE ADDRESS: 127.0.0.1\n" + + "SERVER NAME:nesys.home\n" + + "SERVER ADDR:239.1.1.1"; + } + + [Route(HttpVerbs.Get, "/{id}/Alive.txt")] + // ReSharper disable once UnusedMember.Global + public void AliveFile() + { + HttpContext.Response.SetEmptyResponse((int)HttpStatusCode.OK); + HttpContext.Response.ContentType = MediaTypeNames.Text.Plain; + HttpContext.Response.ContentEncoding = Encoding.Default; + HttpContext.Response.KeepAlive = true; + } +} \ No newline at end of file diff --git a/GC-local-server-rewrite/controllers/CardServiceController.cs b/GC-local-server-rewrite/controllers/CardServiceController.cs new file mode 100644 index 0000000..4b5b3c8 --- /dev/null +++ b/GC-local-server-rewrite/controllers/CardServiceController.cs @@ -0,0 +1,529 @@ +using System.Data; +using System.Net.Mime; +using System.Text; +using System.Xml.Linq; +using ChoETL; +using EmbedIO; +using EmbedIO.Routing; +using EmbedIO.WebApi; +using GCLocalServerRewrite.common; +using GCLocalServerRewrite.models; +using SQLite.Net2; +using Swan.Logging; + +namespace GCLocalServerRewrite.controllers; + +public class CardServiceController : WebApiController +{ + private readonly SQLiteConnection cardSqLiteConnection; + private readonly SQLiteConnection musicSqLiteConnection; + + public CardServiceController() + { + cardSqLiteConnection = DatabaseHelper.ConnectDatabase(Configs.CARD_DB_NAME); + musicSqLiteConnection = DatabaseHelper.ConnectDatabase(Configs.MUSIC_DB_NAME); + } + + [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) + { + HttpContext.Response.ContentType = MediaTypeNames.Application.Octet; + HttpContext.Response.ContentEncoding = Encoding.Default; + HttpContext.Response.KeepAlive = true; + + if (gid != Configs.GC4_EX_GID) + { + throw new ArgumentOutOfRangeException(nameof(gid)); + } + + if (!Enum.IsDefined(typeof(CardRequestType), type)) + { + throw new ArgumentOutOfRangeException(nameof(type)); + } + + var requestType = (CardRequestType)type; + + switch (requestType) + { + #region ReadRequests + + case CardRequestType.ReadCard: + { + $"Getting read request, type is {requestType}".Info(); + + return ConstructResponse(Card(cardId)); + } + case CardRequestType.ReadCardDetail: + { + $"Getting read request, type is {requestType}".Info(); + + return ConstructResponse(CardDetail(cardId, xmlData)); + } + 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(cardId, Configs.AVATAR_COUNT, Configs.AVATAR_XPATH)); + } + case CardRequestType.ReadItem: + { + $"Getting read request, type is {requestType}".Info(); + + return ConstructResponse( + GetStaticCount(cardId, Configs.ITEM_COUNT, Configs.ITEM_XPATH)); + } + case CardRequestType.ReadSkin: + { + $"Getting read request, type is {requestType}".Info(); + + return ConstructResponse( + GetStaticCount(cardId, Configs.SKIN_COUNT, Configs.SKIN_XPATH)); + } + case CardRequestType.ReadTitle: + { + $"Getting read request, type is {requestType}".Info(); + + return ConstructResponse( + GetStaticCount(cardId, Configs.TITLE_COUNT, 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)); + } + case CardRequestType.ReadNavigator: + { + $"Getting read request, type is {requestType}".Info(); + + return ConstructResponse( + GetStaticCount<Navigator>(cardId, Configs.NAVIGATOR_COUNT, 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)); + } + 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)); + } + case CardRequestType.ReadGetMessage: + { + $"Getting read request, type is {requestType}".Info(); + + return ConstructResponse(GenerateEmptyXML(Configs.GET_MESSAGE)); + } + case CardRequestType.ReadCond: + { + $"Getting read request, type is {requestType}".Info(); + + 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: + { + $"Getting read request, type is {requestType}".Info(); + + return ConstructResponse(GetSession(cardId, mac)); + } + + #endregion + + #region WriteRequests + + case CardRequestType.WriteCardDetail: + { + $"Getting write request, type is {requestType}\n Data is {xmlData}".Info(); + Write<CardDetail>(cardId, xmlData); + + return ConstructResponse(xmlData); + } + case CardRequestType.WriteCardBData: + { + $"Getting write request, type is {requestType}\n 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); + + return ConstructResponse(xmlData); + } + case CardRequestType.WriteAvatar: + case CardRequestType.WriteItem: + case CardRequestType.WriteTitle: + case CardRequestType.WriteMusicDetail: + case CardRequestType.WriteNavigator: + case CardRequestType.WriteCoin: + case CardRequestType.WriteSkin: + case CardRequestType.WriteUnlockKeynum: + case CardRequestType.WriteSoundEffect: + { + $"Getting write request, type is {requestType}\n Data is {xmlData}".Info(); + + return ConstructResponse(xmlData); + } + + #endregion + + default: +#pragma warning disable CA2208 + throw new ArgumentOutOfRangeException(nameof(requestType)); +#pragma warning restore CA2208 + } + } + + #region ReadImplementation + + private string Card(long cardId) + { + var result = cardSqLiteConnection.Table<Card>().Where(card => card.CardId == cardId); + + if (!result.Any()) + { + return GenerateEmptyXML(Configs.CARD); + } + + var card = result.First(); + + return GenerateSingleXml(card, Configs.CARD_XPATH); + } + + private string CardDetail(long cardId, string xmlData) + { + var reader = new ChoXmlReader<CardDetailReadData>(new StringReader(xmlData)); + var data = reader.Read(); + + var result = cardSqLiteConnection.Table<CardDetail>() + .Where(detail => detail.CardId == cardId && + detail.Pcol1 == data.Pcol1 && detail.Pcol2 == data.Pcol2 && + detail.Pcol3 == data.Pcol3); + + if (!result.Any()) + { + return GenerateEmptyXML(Configs.CARD_DETAIL); + } + + var cardDetail = result.First(); + + return GenerateSingleXml(cardDetail, Configs.CARD_DETAIL_RECORD_XPATH); + } + + private string CardDetails(long cardId) + { + var result = cardSqLiteConnection.Table<CardDetail>() + .Where(detail => detail.CardId == cardId); + + if (!result.Any()) + { + return GenerateEmptyXML(Configs.CARD_DETAIL); + } + + var cardDetails = result.ToList(); + + return GenerateRecordsXml(cardDetails, Configs.CARD_DETAIL_XPATH); + } + + private string CardBData(long cardId) + { + var result = cardSqLiteConnection.Table<CardBData>() + .Where(detail => detail.CardId == cardId); + + if (!result.Any()) + { + return GenerateEmptyXML(Configs.CARD_BDATA); + } + var cardBData = result.First(); + + return GenerateSingleXml(cardBData, Configs.CARD_BDATA_XPATH); + } + + private static string GetStaticCount<T>(long cardId, int count, string xpath) + where T : Record, IIdModel, ICardIdModel, new() + { + var models = new List<T>(); + + for (var id = 1; id <= count; id++) + { + var model = new T(); + model.SetId(id); + model.SetCardId(cardId); + models.Add(model); + } + + return GenerateRecordsXml(models, xpath); + } + + private static string GetSession(long cardId, string mac) + { + var session = new Session + { + CardId = cardId, + Mac = mac, + SessionId = "12345678901234567890123456789012", + Expires = 9999, + PlayerId = 1 + }; + + return GenerateSingleXml(session, Configs.SESSION_XPATH); + } + + private static string TotalTrophy(long cardId) + { + var trophy = new TotalTrophy + { + CardId = cardId, + TrophyNum = 9 + }; + + return GenerateSingleXml(trophy, Configs.TOTAL_TROPHY_XPATH); + } + + private static string Coin(long cardId) + { + var coin = new Coin + { + CardId = cardId, + CurrentCoins = 999999, + TotalCoins = 999999, + MonthlyCoins = 999999 + }; + + return GenerateSingleXml(coin, Configs.COIN_XPATH); + } + + private static string UnlockReward(long cardId) + { + var unlockRewards = new List<UnlockReward> + { + new() + { + CardId = cardId, + RewardType = 1, + RewardId = 1, + TargetId = 1, + TargetNum = 1, + KeyNum = 3 + } + }; + + return GenerateRecordsXml(unlockRewards, Configs.UNLOCK_REWARD_XPATH); + } + + private static string UnlockKeynum(long cardId) + { + var unlockKeynums = new List<UnlockKeynum> + { + new() + { + CardId = cardId, + RewardId = 1, + KeyNum = 0, + RewardCount = 1 + } + }; + + return GenerateRecordsXml(unlockKeynums, Configs.UNLOCK_KEYNUM_XPATH); + } + + private string MusicUnlock() + { + var result = musicSqLiteConnection.Table<Music>().ToList(); + + return GenerateRecordsXml(result, Configs.MUSIC_XPATH); + } + + private string MusicExtra() + { + var result = musicSqLiteConnection.Table<MusicExtra>().ToList(); + + return GenerateRecordsXml(result, Configs.MUSIC_EXTRA_XPATH); + } + + #endregion + + #region HelperMethods + + private static string ConstructResponse(string xml) + { + return "1\n" + + "10,10\n" + + xml; + } + + private static string GenerateEmptyXML(string fieldName) + { + var xml = new XDocument(new XElement("root", + 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 + { + var sb = new StringBuilder(); + + using (var writer = new ChoXmlWriter<T>(sb)) + { + writer.Configuration.OmitXmlDeclaration = false; + writer.Configuration.UseXmlSerialization = true; + writer.WithXPath(xpath); + + writer.Write(obj); + } + + return sb.ToString(); + } + + private static string GenerateRecordsXml<T>(IReadOnlyList<T> list, string xpath) where T : Record + { + var stringBuilder = new StringBuilder(); + + using (var writer = new ChoXmlWriter<T>(stringBuilder)) + { + writer.Configuration.OmitXmlDeclaration = false; + writer.Configuration.UseXmlSerialization = true; + writer.WithXPath(xpath); + + for (var i = 0; i < list.Count; i++) + { + var obj = list[i]; + obj.RecordId = i + 1; + writer.Write(obj); + } + } + + return stringBuilder.ToString(); + } + + #endregion + + #region WriteImplementation + + private void Write<T>(long cardId, string xmlData) where T : class, ICardIdModel + { + var reader = new ChoXmlReader<T>(new StringReader(xmlData)).WithXPath(Configs.DATA_XPATH); + var writeObject = reader.Read(); + + if (writeObject == null) + { + throw new HttpRequestException(); + } + + writeObject.SetCardId(cardId); + var rowsAffected = cardSqLiteConnection.InsertOrReplace(writeObject, typeof(T)); + + if (rowsAffected == 0) + { + throw new ApplicationException("Update database failed!"); + } + + $"Updated {typeof(T)}".Info(); + } + + #endregion + + private enum CardRequestType + { + // Read data + ReadCard = 259, + ReadCardDetail = 260, + ReadCardDetails = 261, + ReadCardBData = 264, + ReadAvatar = 418, + ReadItem = 420, + ReadSkin = 422, + ReadTitle = 424, + ReadMusic = 428, + ReadEventReward = 441, + ReadNavigator = 443, + ReadMusicExtra = 465, + ReadMusicAou = 467, + ReadCoin = 468, + ReadUnlockReward = 507, + ReadUnlockKeynum = 509, + ReadSoundEffect = 8458, + ReadGetMessage = 8461, + ReadCond = 8465, + ReadTotalTrophy = 8468, + + // Sessions + SessionGet = 401, + SessionStart = 402, + + // Write data + WriteCard = 771, + WriteCardDetail = 772, + WriteCardBData = 776, + WriteAvatar = 929, + WriteItem = 931, + WriteTitle = 935, + WriteMusicDetail = 94, + WriteNavigator = 954, + WriteCoin = 980, + WriteSkin = 933, + WriteUnlockKeynum = 1020, + WriteSoundEffect = 8969 + } +} \ No newline at end of file diff --git a/GC-local-server-rewrite/controllers/IncomServiceController.cs b/GC-local-server-rewrite/controllers/IncomServiceController.cs new file mode 100644 index 0000000..4f17fd0 --- /dev/null +++ b/GC-local-server-rewrite/controllers/IncomServiceController.cs @@ -0,0 +1,32 @@ +using System.Net.Mime; +using System.Text; +using EmbedIO; +using EmbedIO.Routing; +using EmbedIO.WebApi; + +namespace GCLocalServerRewrite.controllers; + +public class IncomServiceController : WebApiController +{ + [Route(HttpVerbs.Post, "/incom.php")] + // ReSharper disable once UnusedMember.Global + public string IncomService() + { + HttpContext.Response.ContentType = MediaTypeNames.Text.Plain; + HttpContext.Response.ContentEncoding = Encoding.Default; + HttpContext.Response.KeepAlive = true; + + return "1+1"; + } + + [Route(HttpVerbs.Post, "/incomALL.php")] + // ReSharper disable once UnusedMember.Global + public string IncomAllService() + { + HttpContext.Response.ContentType = MediaTypeNames.Text.Plain; + HttpContext.Response.ContentEncoding = Encoding.Default; + HttpContext.Response.KeepAlive = true; + + return "1+1"; + } +} \ No newline at end of file diff --git a/GC-local-server-rewrite/controllers/RankController.cs b/GC-local-server-rewrite/controllers/RankController.cs new file mode 100644 index 0000000..85883fc --- /dev/null +++ b/GC-local-server-rewrite/controllers/RankController.cs @@ -0,0 +1,109 @@ +using System.Net.Mime; +using System.Text; +using System.Xml; +using ChoETL; +using EmbedIO; +using EmbedIO.Routing; +using EmbedIO.WebApi; +using GCLocalServerRewrite.common; +using GCLocalServerRewrite.models; +using Swan; +using Swan.Logging; +// ReSharper disable UnusedMember.Global + +namespace GCLocalServerRewrite.controllers; + +public class RankController : WebApiController +{ + [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.KeepAlive = true; + + if (!Enum.IsDefined(typeof(RankType), type)) + { + throw new ArgumentOutOfRangeException(nameof(type)); + } + + var requestType = (RankType)type; + + switch (requestType) + { + case RankType.GlobalRank: + case RankType.UnknownRank1: + case RankType.UnknownRank2: + case RankType.UnknownRank3: + $"Getting rank request, type is {requestType}".Info(); + + return ConstructResponse(RankTemp("score_rank")); + case RankType.PlayNumRank: + $"Getting rank request, type is {requestType}".Info(); + + return ConstructResponse(RankTemp("play_num_rank")); + case RankType.EventRank: + $"Getting rank request, type is {requestType}".Info(); + + return ConstructResponse(RankTemp("event_rank")); + default: +#pragma warning disable CA2208 + throw new ArgumentOutOfRangeException(nameof(requestType)); +#pragma warning restore CA2208 + } + } + + private static string ConstructResponse(string xml) + { + return "1\n" + + xml; + } + + // TODO: Add proper rank support + private static string RankTemp(string rankType) + { + var rankStatus = new RankStatus + { + Rows = 0, + Status = 0 + }; + + var sb = new StringBuilder(); + + using (var writer = new ChoXmlWriter<RankStatus>(sb)) + { + writer.Configuration.OmitXmlDeclaration = false; + writer.Configuration.UseXmlSerialization = true; + writer.WithXPath(Configs.RANK_STATUS_XPATH); + + writer.Write(rankStatus); + } + + var document = new XmlDocument(); + document.LoadXml(sb.ToString()); + var root = document.DocumentElement; + + if (root == null) + { + throw SelfCheck.Failure("Internal XML error!"); + } + + root.AppendChild(document.CreateElement(rankType)); + + var stringWriter = new StringWriter(); + var xmlTextWriter = new XmlTextWriter(stringWriter); + document.WriteTo(xmlTextWriter); + + return stringWriter.ToString(); + } + + private enum RankType + { + GlobalRank = 4119, + PlayNumRank = 6657, + EventRank = 6661, + UnknownRank1 = 6666, + UnknownRank2 = 6667, + UnknownRank3 = 4098 + } +} \ No newline at end of file diff --git a/GC-local-server-rewrite/controllers/ResponeServiceController.cs b/GC-local-server-rewrite/controllers/ResponeServiceController.cs new file mode 100644 index 0000000..edfedc7 --- /dev/null +++ b/GC-local-server-rewrite/controllers/ResponeServiceController.cs @@ -0,0 +1,21 @@ +using System.Net.Mime; +using System.Text; +using EmbedIO; +using EmbedIO.Routing; +using EmbedIO.WebApi; + +namespace GCLocalServerRewrite.controllers; + +public class ResponeServiceController : WebApiController +{ + [Route(HttpVerbs.Post, "/respone.php")] + // ReSharper disable once UnusedMember.Global + public string ResponeService() + { + HttpContext.Response.ContentType = MediaTypeNames.Text.Plain; + HttpContext.Response.ContentEncoding = Encoding.Default; + HttpContext.Response.KeepAlive = true; + + return "1"; + } +} \ No newline at end of file diff --git a/GC-local-server-rewrite/controllers/ServerController.cs b/GC-local-server-rewrite/controllers/ServerController.cs new file mode 100644 index 0000000..a5a4eba --- /dev/null +++ b/GC-local-server-rewrite/controllers/ServerController.cs @@ -0,0 +1,138 @@ +using System.Collections.Specialized; +using System.Net.Mime; +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; +using EmbedIO; +using EmbedIO.Routing; +using EmbedIO.WebApi; +using GCLocalServerRewrite.common; +using Swan.Logging; + +// ReSharper disable UnusedMember.Global + +namespace GCLocalServerRewrite.controllers; + +public class ServerController : WebApiController +{ + [Route(HttpVerbs.Get, "/certify.php")] + public string Certify([QueryData] NameValueCollection parameters) + { + HttpContext.Response.ContentType = MediaTypeNames.Text.Plain; + HttpContext.Response.ContentEncoding = Encoding.Default; + HttpContext.Response.KeepAlive = true; + + var gid = parameters["gid"]; + var mac = parameters["mac"]; + var random = parameters["r"]; + var hash = parameters["md"]; + + if (gid == null) + { + return QuitWithError(ErrorCode.ErrorNoGid); + } + + if (mac == null) + { + return QuitWithError(ErrorCode.ErrorNoMac); + } + + if (random == null) + { + return QuitWithError(ErrorCode.ErrorNoRandom); + } + + if (hash == null) + { + return QuitWithError(ErrorCode.ErrorNoHash); + } + + if (!MacValid(mac)) + { + return QuitWithError(ErrorCode.ErrorInvalidMac); + } + + if (!Md5Valid(hash)) + { + return QuitWithError(ErrorCode.ErrorInvalidHash); + } + + using var md5 = MD5.Create(); + + var ticket = string.Join(string.Empty, + md5.ComputeHash(Encoding.UTF8.GetBytes(gid)).Select(b => b.ToString("x2"))); + + + var response = "host=card_id=7020392000147361\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" + + $"ticket={ticket}"; + + return response; + } + + [Route(HttpVerbs.Get, "/cursel.php")] + public string Cursel() + { + HttpContext.Response.ContentType = MediaTypeNames.Text.Plain; + HttpContext.Response.ContentEncoding = Encoding.Default; + HttpContext.Response.KeepAlive = true; + + return "1\n"; + } + + [Route(HttpVerbs.Get, "/data.php")] + public string Data() + { + HttpContext.Response.ContentType = MediaTypeNames.Text.Plain; + HttpContext.Response.ContentEncoding = Encoding.Default; + HttpContext.Response.KeepAlive = true; + + return "count=1\n" + + "nexttime=0\n"; + } + + [Route(HttpVerbs.Get, "/gameinfo.php")] + public string GameInfo() + { + HttpContext.Response.ContentType = MediaTypeNames.Text.Plain; + HttpContext.Response.ContentEncoding = Encoding.Default; + HttpContext.Response.KeepAlive = true; + + return "0\n" + + "3\n" + + "301000,test1\n" + + "302000,test2\n" + + "303000,test3"; + } + + private static bool MacValid(string mac) + { + return Regex.IsMatch(mac, "^[a-fA-F0-9]{12}$"); + } + + private static bool Md5Valid(string md5) + { + return Regex.IsMatch(md5, "^[a-fA-F0-9]{32}$"); + } + + private static string QuitWithError(ErrorCode errorCode) + { + return $"error={(int)errorCode}"; + } + + private enum ErrorCode + { + ErrorNoGid, + ErrorNoMac, + ErrorNoRandom, + ErrorNoHash, + ErrorInvalidMac, + ErrorInvalidHash + } +} \ No newline at end of file diff --git a/GC-local-server-rewrite/controllers/UploadServiceController.cs b/GC-local-server-rewrite/controllers/UploadServiceController.cs new file mode 100644 index 0000000..acaf1dc --- /dev/null +++ b/GC-local-server-rewrite/controllers/UploadServiceController.cs @@ -0,0 +1,22 @@ +using System.Net.Mime; +using System.Text; +using EmbedIO; +using EmbedIO.Routing; +using EmbedIO.WebApi; + +namespace GCLocalServerRewrite.controllers; + +public class UploadServiceController : WebApiController +{ + [Route(HttpVerbs.Post, "/upload.php")] + // ReSharper disable once UnusedMember.Global + public string UploadService() + { + HttpContext.Response.ContentType = MediaTypeNames.Text.Plain; + HttpContext.Response.ContentEncoding = Encoding.Default; + HttpContext.Response.KeepAlive = true; + + return "1\n" + + "OK"; + } +} \ No newline at end of file diff --git a/GC-local-server-rewrite/db/card.db3 b/GC-local-server-rewrite/db/card.db3 new file mode 100644 index 0000000..cf62926 Binary files /dev/null and b/GC-local-server-rewrite/db/card.db3 differ diff --git a/GC-local-server-rewrite/db/music.db3 b/GC-local-server-rewrite/db/music.db3 new file mode 100644 index 0000000..2067cc6 Binary files /dev/null and b/GC-local-server-rewrite/db/music.db3 differ diff --git a/GC-local-server-rewrite/models/Avatar.cs b/GC-local-server-rewrite/models/Avatar.cs new file mode 100644 index 0000000..75de876 --- /dev/null +++ b/GC-local-server-rewrite/models/Avatar.cs @@ -0,0 +1,34 @@ +using System.Xml.Serialization; + +namespace GCLocalServerRewrite.models; + +public class Avatar : Record , IIdModel, ICardIdModel +{ + [XmlElement(ElementName = "card_id")] + public long CardId { get; set; } + + [XmlElement(ElementName = "avatar_id")] + public int AvatarId { get; set; } + + [XmlElement("created")] + public string? Created { get; set; } = "1"; + + [XmlElement("modified")] + public string? Modified { get; set; } = "1"; + + [XmlElement("new_flag")] + public int NewFlag { get; set; } = 1; + + [XmlElement("use_flag")] + public int UseFlag { get; set; } = 1; + + public void SetId(int id) + { + AvatarId = id; + } + + public void SetCardId(long cardId) + { + CardId = cardId; + } +} \ No newline at end of file diff --git a/GC-local-server-rewrite/models/Card.cs b/GC-local-server-rewrite/models/Card.cs new file mode 100644 index 0000000..4faafc2 --- /dev/null +++ b/GC-local-server-rewrite/models/Card.cs @@ -0,0 +1,60 @@ +using System.Xml.Serialization; +using ChoETL; +using SQLite.Net2; + +namespace GCLocalServerRewrite.models; + +[Table("card_main")] +public class Card : ICardIdModel +{ + [PrimaryKey] + [Column("card_id")] + [ChoXmlElementRecordField(FieldName = "card_id")] + [XmlElement(ElementName = "card_id")] + public long? CardId { get; set; } + + [Column("player_name")] + [ChoXmlElementRecordField(FieldName = "player_name")] + [XmlElement(ElementName = "player_name")] + public string? PlayerName { get; set; } + + [Column("score_i1")] + [ChoXmlElementRecordField(FieldName = "score_i1")] + [XmlElement("score_i1")] + public int Score { get; set; } + + [Column("fcol1")] + [ChoXmlElementRecordField(FieldName = "fcol1")] + [XmlElement("fcol1")] + public int Fcol1 { get; set; } + + [Column("fcol2")] + [ChoXmlElementRecordField(FieldName = "fcol2")] + [XmlElement("fcol2")] + public int Fcol2 { get; set; } + + [Column("fcol3")] + [ChoXmlElementRecordField(FieldName = "fcol3")] + [XmlElement("fcol3")] + public int Fcol3 { get; set; } + + [Column("achieve_status")] + [ChoXmlElementRecordField(FieldName = "achieve_status")] + [XmlElement("achieve_status")] + public string? AchieveStatus { get; set; } = string.Empty; + + [Column("created")] + [ChoXmlElementRecordField(FieldName = "created")] + [XmlElement("created")] + public string Created { get; set; } = "1"; + + [Column("updated")] + [ChoXmlElementRecordField(FieldName = "updated")] + [XmlElement("updated")] + public string Updated { get; set; } = "1"; + + public void SetCardId(long cardId) + { + CardId = cardId; + } +} \ No newline at end of file diff --git a/GC-local-server-rewrite/models/CardBData.cs b/GC-local-server-rewrite/models/CardBData.cs new file mode 100644 index 0000000..e7b098a --- /dev/null +++ b/GC-local-server-rewrite/models/CardBData.cs @@ -0,0 +1,30 @@ +using System.Xml.Serialization; +using ChoETL; +using SQLite.Net2; + +namespace GCLocalServerRewrite.models; + +[Table("card_bdata")] +public class CardBData : ICardIdModel +{ + [PrimaryKey] + [Column("card_id")] + [ChoXmlElementRecordField(FieldName = "card_id")] + [XmlElement(ElementName = "card_id")] + public long? CardId { get; set; } + + [Column("bdata")] + [ChoXmlElementRecordField(FieldName = "bdata")] + [XmlElement(ElementName = "bdata")] + public string BData { get; set; } = string.Empty; + + [Column("bdata_size")] + [ChoXmlElementRecordField(FieldName = "bdata_size")] + [XmlElement(ElementName = "bdata_size")] + public int BDataSize { get; set; } + + public void SetCardId(long cardId) + { + CardId = cardId; + } +} \ No newline at end of file diff --git a/GC-local-server-rewrite/models/CardDetail.cs b/GC-local-server-rewrite/models/CardDetail.cs new file mode 100644 index 0000000..ad2cd56 --- /dev/null +++ b/GC-local-server-rewrite/models/CardDetail.cs @@ -0,0 +1,98 @@ +using System.Xml.Serialization; +using ChoETL; +using SQLite.Net2; + +namespace GCLocalServerRewrite.models; + +[Table("card_detail")] +public class CardDetail : Record, ICardIdModel +{ + [PrimaryKey] + [Column("card_id")] + [ChoXmlElementRecordField(FieldName = "card_id")] + [XmlElement(ElementName = "card_id")] + public long CardId { get; set; } + + [PrimaryKey] + [Column("pcol1")] + [ChoXmlElementRecordField(FieldName = "pcol1")] + [XmlElement(ElementName = "pcol1")] + public int Pcol1 { get; set; } + + [PrimaryKey] + [Column("pcol2")] + [ChoXmlElementRecordField(FieldName = "pcol2")] + [XmlElement(ElementName = "pcol2")] + public int Pcol2 { get; set; } + + [PrimaryKey] + [Column("pcol3")] + [ChoXmlElementRecordField(FieldName = "pcol3")] + [XmlElement(ElementName = "pcol3")] + public int Pcol3 { get; set; } + + [Column("score_i1")] + [ChoXmlElementRecordField(FieldName = "score_i1")] + [XmlElement(ElementName = "score_i1")] + public long ScoreI1 { get; set; } + + [Column("score_ui1")] + [ChoXmlElementRecordField(FieldName = "score_ui1")] + [XmlElement(ElementName = "score_ui1")] + public long ScoreUi1 { get; set; } + + [Column("score_ui2")] + [ChoXmlElementRecordField(FieldName = "score_ui2")] + [XmlElement(ElementName = "score_ui2")] + public long ScoreUi2 { get; set; } + + [Column("score_ui3")] + [ChoXmlElementRecordField(FieldName = "score_ui3")] + [XmlElement(ElementName = "score_ui3")] + public long ScoreUi3 { get; set; } + + [Column("score_ui4")] + [ChoXmlElementRecordField(FieldName = "score_ui4")] + [XmlElement(ElementName = "score_ui4")] + public long ScoreUi4 { get; set; } + + [Column("score_ui5")] + [ChoXmlElementRecordField(FieldName = "score_ui5")] + [XmlElement(ElementName = "score_ui5")] + public long ScoreUi5 { get; set; } + + [Column("score_ui6")] + [ChoXmlElementRecordField(FieldName = "score_ui6")] + [XmlElement(ElementName = "score_ui6")] + public long ScoreUi6 { get; set; } + + [Column("score_bi1")] + [ChoXmlElementRecordField(FieldName = "score_bi1")] + [XmlElement(ElementName = "score_bi1")] + public long ScoreBi1 { get; set; } + + [Column("last_play_tenpo_id")] + [ChoXmlElementRecordField(FieldName = "last_play_tenpo_id")] + [XmlElement(ElementName = "last_play_tenpo_id")] + public string LastPlayShopId { get; set; } = "1337"; + + [Column("fcol1")] + [ChoXmlElementRecordField(FieldName = "fcol1")] + [XmlElement("fcol1")] + public int Fcol1 { get; set; } + + [Column("fcol2")] + [ChoXmlElementRecordField(FieldName = "fcol2")] + [XmlElement("fcol2")] + public int Fcol2 { get; set; } + + [Column("fcol3")] + [ChoXmlElementRecordField(FieldName = "fcol3")] + [XmlElement("fcol3")] + public int Fcol3 { get; set; } + + public void SetCardId(long cardId) + { + CardId = cardId; + } +} \ No newline at end of file diff --git a/GC-local-server-rewrite/models/CardDetailReadData.cs b/GC-local-server-rewrite/models/CardDetailReadData.cs new file mode 100644 index 0000000..395f6bb --- /dev/null +++ b/GC-local-server-rewrite/models/CardDetailReadData.cs @@ -0,0 +1,20 @@ +using System.Xml.Serialization; +using ChoETL; + +namespace GCLocalServerRewrite.models; + +[ChoXmlRecordObject(XPath = "/root/data")] +public class CardDetailReadData +{ + [XmlElement(ElementName = "pcol1")] + [ChoXmlElementRecordField(FieldName = "pcol1")] + public int Pcol1 { get; set; } + + [XmlElement(ElementName = "pcol2")] + [ChoXmlElementRecordField(FieldName = "pcol2")] + public int Pcol2 { get; set; } + + [XmlElement(ElementName = "pcol3")] + [ChoXmlElementRecordField(FieldName = "pcol3")] + public int Pcol3 { get; set; } +} \ No newline at end of file diff --git a/GC-local-server-rewrite/models/Coin.cs b/GC-local-server-rewrite/models/Coin.cs new file mode 100644 index 0000000..c501202 --- /dev/null +++ b/GC-local-server-rewrite/models/Coin.cs @@ -0,0 +1,24 @@ +using System.Xml.Serialization; + +namespace GCLocalServerRewrite.models; + +public class Coin +{ + [XmlElement(ElementName = "card_id")] + public long CardId { get; set; } + + [XmlElement(ElementName = "current")] + public int CurrentCoins { get; set; } + + [XmlElement(ElementName = "total")] + public int TotalCoins { get; set; } + + [XmlElement("monthly")] + public int MonthlyCoins { get; set; } + + [XmlElement("created")] + public string? Created { get; set; } = "1"; + + [XmlElement("modified")] + public string? Modified { get; set; } = "1"; +} \ No newline at end of file diff --git a/GC-local-server-rewrite/models/ICardIdModel.cs b/GC-local-server-rewrite/models/ICardIdModel.cs new file mode 100644 index 0000000..a8080b7 --- /dev/null +++ b/GC-local-server-rewrite/models/ICardIdModel.cs @@ -0,0 +1,6 @@ +namespace GCLocalServerRewrite.models; + +public interface ICardIdModel +{ + public void SetCardId(long cardId); +} \ No newline at end of file diff --git a/GC-local-server-rewrite/models/IIdModel.cs b/GC-local-server-rewrite/models/IIdModel.cs new file mode 100644 index 0000000..4e065b6 --- /dev/null +++ b/GC-local-server-rewrite/models/IIdModel.cs @@ -0,0 +1,6 @@ +namespace GCLocalServerRewrite.models; + +public interface IIdModel +{ + public void SetId(int id); +} \ No newline at end of file diff --git a/GC-local-server-rewrite/models/Item.cs b/GC-local-server-rewrite/models/Item.cs new file mode 100644 index 0000000..7eac5f1 --- /dev/null +++ b/GC-local-server-rewrite/models/Item.cs @@ -0,0 +1,37 @@ +using System.Xml.Serialization; + +namespace GCLocalServerRewrite.models; + +public class Item : Record, IIdModel, ICardIdModel +{ + [XmlElement(ElementName = "card_id")] + public long CardId { get; set; } + + [XmlElement(ElementName = "item_id")] + public int ItemId { get; set; } + + [XmlElement(ElementName = "item_num")] + public int ItemNum { get; set; } = 90; + + [XmlElement("created")] + public string? Created { get; set; } = "1"; + + [XmlElement("modified")] + public string? Modified { get; set; } = "1"; + + [XmlElement("new_flag")] + public int NewFlag { get; set; } = 1; + + [XmlElement("use_flag")] + public int UseFlag { get; set; } = 1; + + public void SetId(int id) + { + ItemId = id; + } + + public void SetCardId(long cardId) + { + CardId = cardId; + } +} \ No newline at end of file diff --git a/GC-local-server-rewrite/models/Music.cs b/GC-local-server-rewrite/models/Music.cs new file mode 100644 index 0000000..1eb2c9e --- /dev/null +++ b/GC-local-server-rewrite/models/Music.cs @@ -0,0 +1,37 @@ +using System.Xml.Serialization; +using SQLite.Net2; + +namespace GCLocalServerRewrite.models; + +[Table("music_unlock")] +public class Music : Record +{ + [PrimaryKey] + [Column("music_id")] + [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; } +} \ No newline at end of file diff --git a/GC-local-server-rewrite/models/MusicAou.cs b/GC-local-server-rewrite/models/MusicAou.cs new file mode 100644 index 0000000..d54e7dc --- /dev/null +++ b/GC-local-server-rewrite/models/MusicAou.cs @@ -0,0 +1,37 @@ +using System.Xml.Serialization; +using SQLite.Net2; + +namespace GCLocalServerRewrite.models; + +[Table("music_aou")] +public class MusicAou : Record +{ + [PrimaryKey] + [Column("music_id")] + [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; } +} \ No newline at end of file diff --git a/GC-local-server-rewrite/models/MusicExtra.cs b/GC-local-server-rewrite/models/MusicExtra.cs new file mode 100644 index 0000000..c4e1d1e --- /dev/null +++ b/GC-local-server-rewrite/models/MusicExtra.cs @@ -0,0 +1,17 @@ +using System.Xml.Serialization; +using SQLite.Net2; + +namespace GCLocalServerRewrite.models; + +[Table("music_extra")] +public class MusicExtra : Record +{ + [PrimaryKey] + [Column("music_id")] + [XmlElement("music_id")] + public int MusicId { get; set; } + + [Column("use_flag")] + [XmlElement("use_flag")] + public int UseFlag { get; set; } +} \ No newline at end of file diff --git a/GC-local-server-rewrite/models/Navigator.cs b/GC-local-server-rewrite/models/Navigator.cs new file mode 100644 index 0000000..f1f933c --- /dev/null +++ b/GC-local-server-rewrite/models/Navigator.cs @@ -0,0 +1,34 @@ +using System.Xml.Serialization; + +namespace GCLocalServerRewrite.models; + +public class Navigator : Record, IIdModel, ICardIdModel +{ + [XmlElement(ElementName = "card_id")] + public long CardId { get; set; } + + [XmlElement(ElementName = "navigator_id")] + public int NavigatorId { get; set; } + + [XmlElement("created")] + public string? Created { get; set; } = "1"; + + [XmlElement("modified")] + public string? Modified { get; set; } = "1"; + + [XmlElement("new_flag")] + public int NewFlag { get; set; } = 1; + + [XmlElement("use_flag")] + public int UseFlag { get; set; } = 1; + + public void SetId(int id) + { + NavigatorId = id; + } + + public void SetCardId(long cardId) + { + CardId = cardId; + } +} \ No newline at end of file diff --git a/GC-local-server-rewrite/models/RankStatus.cs b/GC-local-server-rewrite/models/RankStatus.cs new file mode 100644 index 0000000..8b0fd67 --- /dev/null +++ b/GC-local-server-rewrite/models/RankStatus.cs @@ -0,0 +1,21 @@ +using System.Xml.Serialization; + +namespace GCLocalServerRewrite.models; + +public class RankStatus +{ + [XmlElement("table_name")] + public string? TableName { get; set; } + + [XmlElement("start_date")] + public string StartDate { get; set; } = "2017-05-30"; + + [XmlElement("end_date")] + public string EndDate { get; set; } = "2018-05-30"; + + [XmlElement("status")] + public int Status { get; set; } + + [XmlElement("rows")] + public int Rows { get; set; } +} \ No newline at end of file diff --git a/GC-local-server-rewrite/models/Record.cs b/GC-local-server-rewrite/models/Record.cs new file mode 100644 index 0000000..0ea53ef --- /dev/null +++ b/GC-local-server-rewrite/models/Record.cs @@ -0,0 +1,10 @@ +using System.Xml.Serialization; +using ChoETL; + +namespace GCLocalServerRewrite.models; + +public class Record +{ + [XmlAttribute(AttributeName = "id")] + public int RecordId { get; set; } +} \ No newline at end of file diff --git a/GC-local-server-rewrite/models/Session.cs b/GC-local-server-rewrite/models/Session.cs new file mode 100644 index 0000000..f0d9b41 --- /dev/null +++ b/GC-local-server-rewrite/models/Session.cs @@ -0,0 +1,21 @@ +using System.Xml.Serialization; + +namespace GCLocalServerRewrite.models; + +public class Session +{ + [XmlElement(ElementName = "card_id")] + public long CardId { get; set; } + + [XmlElement(ElementName = "mac_addr")] + public string? Mac { get; set; } + + [XmlElement(ElementName = "session_id")] + public string? SessionId { get; set; } + + [XmlElement(ElementName = "expires")] + public int Expires { get; set; } + + [XmlElement(ElementName = "player_id")] + public int PlayerId { get; set; } +} \ No newline at end of file diff --git a/GC-local-server-rewrite/models/Skin.cs b/GC-local-server-rewrite/models/Skin.cs new file mode 100644 index 0000000..84351d5 --- /dev/null +++ b/GC-local-server-rewrite/models/Skin.cs @@ -0,0 +1,34 @@ +using System.Xml.Serialization; + +namespace GCLocalServerRewrite.models; + +public class Skin : Record, IIdModel, ICardIdModel +{ + [XmlElement(ElementName = "card_id")] + public long CardId { get; set; } + + [XmlElement(ElementName = "skin_id")] + public int SkinId { get; set; } + + [XmlElement("created")] + public string? Created { get; set; } = "1"; + + [XmlElement("modified")] + public string? Modified { get; set; } = "1"; + + [XmlElement("new_flag")] + public int NewFlag { get; set; } = 1; + + [XmlElement("use_flag")] + public int UseFlag { get; set; } = 1; + + public void SetId(int id) + { + SkinId = id; + } + + public void SetCardId(long cardId) + { + CardId = cardId; + } +} \ No newline at end of file diff --git a/GC-local-server-rewrite/models/SoundEffect.cs b/GC-local-server-rewrite/models/SoundEffect.cs new file mode 100644 index 0000000..e673953 --- /dev/null +++ b/GC-local-server-rewrite/models/SoundEffect.cs @@ -0,0 +1,34 @@ +using System.Xml.Serialization; + +namespace GCLocalServerRewrite.models; + +public class SoundEffect : Record, IIdModel, ICardIdModel +{ + [XmlElement(ElementName = "card_id")] + public long CardId { get; set; } + + [XmlElement(ElementName = "sound_effect_id")] + public int SeId { get; set; } + + [XmlElement("created")] + public string? Created { get; set; } = "1"; + + [XmlElement("modified")] + public string? Modified { get; set; } = "1"; + + [XmlElement("new_flag")] + public int NewFlag { get; set; } = 1; + + [XmlElement("use_flag")] + public int UseFlag { get; set; } = 1; + + public void SetId(int id) + { + SeId = id; + } + + public void SetCardId(long cardId) + { + CardId = cardId; + } +} \ No newline at end of file diff --git a/GC-local-server-rewrite/models/Title.cs b/GC-local-server-rewrite/models/Title.cs new file mode 100644 index 0000000..6a0cd0a --- /dev/null +++ b/GC-local-server-rewrite/models/Title.cs @@ -0,0 +1,34 @@ +using System.Xml.Serialization; + +namespace GCLocalServerRewrite.models; + +public class Title : Record, IIdModel, ICardIdModel +{ + [XmlElement(ElementName = "card_id")] + public long CardId { get; set; } + + [XmlElement(ElementName = "title_id")] + public int TitleId { get; set; } + + [XmlElement("created")] + public string? Created { get; set; } = "1"; + + [XmlElement("modified")] + public string? Modified { get; set; } = "1"; + + [XmlElement("new_flag")] + public int NewFlag { get; set; } = 1; + + [XmlElement("use_flag")] + public int UseFlag { get; set; } = 1; + + public void SetId(int id) + { + TitleId = id; + } + + public void SetCardId(long cardId) + { + CardId = cardId; + } +} \ No newline at end of file diff --git a/GC-local-server-rewrite/models/TotalTrophy.cs b/GC-local-server-rewrite/models/TotalTrophy.cs new file mode 100644 index 0000000..8ecc5aa --- /dev/null +++ b/GC-local-server-rewrite/models/TotalTrophy.cs @@ -0,0 +1,12 @@ +using System.Xml.Serialization; + +namespace GCLocalServerRewrite.models; + +public class TotalTrophy +{ + [XmlElement(ElementName = "card_id")] + public long CardId { get; set; } + + [XmlElement(ElementName = "total_trophy_num")] + public int TrophyNum { get; set; } +} \ No newline at end of file diff --git a/GC-local-server-rewrite/models/UnlockKeynum.cs b/GC-local-server-rewrite/models/UnlockKeynum.cs new file mode 100644 index 0000000..8510f66 --- /dev/null +++ b/GC-local-server-rewrite/models/UnlockKeynum.cs @@ -0,0 +1,33 @@ +using System.Xml.Serialization; + +namespace GCLocalServerRewrite.models; + +public class UnlockKeynum : Record +{ + [XmlElement(ElementName = "card_id")] + public long CardId { get; set; } + + [XmlElement(ElementName = "reward_id")] + public int RewardId { get; set; } + + [XmlElement(ElementName = "key_num")] + public int KeyNum { get; set; } + + [XmlElement(ElementName = "reward_count")] + public int RewardCount { get; set; } + + [XmlElement("expired_flag")] + public int ExpiredFlag { get; set; } + + [XmlElement("use_flag")] + public int UseFlag { get; set; } = 1; + + [XmlElement("cash_flag")] + public int CashFlag { get; set; } + + [XmlElement("created")] + public string? Created { get; set; } = "1"; + + [XmlElement("modified")] + public string? Modified { get; set; } = "1"; +} \ No newline at end of file diff --git a/GC-local-server-rewrite/models/UnlockReward.cs b/GC-local-server-rewrite/models/UnlockReward.cs new file mode 100644 index 0000000..226b61e --- /dev/null +++ b/GC-local-server-rewrite/models/UnlockReward.cs @@ -0,0 +1,57 @@ +using System.Xml.Serialization; + +namespace GCLocalServerRewrite.models; + +public class UnlockReward : Record +{ + [XmlElement(ElementName = "card_id")] + public long CardId { get; set; } + + [XmlElement(ElementName = "reward_id")] + public int RewardId { get; set; } + + [XmlElement(ElementName = "reward_type")] + public int RewardType { get; set; } + + [XmlElement(ElementName = "open_date")] + public string? OpenDate { get; set; } = "2021-05-30"; + + [XmlElement(ElementName = "close_date")] + public string? CloseDate { get; set; } = "2030-05-30"; + + [XmlElement(ElementName = "open_time")] + public string? OpenTime { get; set; } = "00:00:01"; + + [XmlElement(ElementName = "close_time")] + public string? CloseTime { get; set; } = "23:59:59"; + + [XmlElement(ElementName = "target_id")] + public int TargetId { get; set; } + + [XmlElement(ElementName = "target_num")] + public int TargetNum { get; set; } + + [XmlElement(ElementName = "key_num")] + public int KeyNum { get; set; } + + [XmlElement("display_flag")] + public int DisplayFlag { get; set; } = 1; + + [XmlElement("use_flag")] + public int UseFlag { get; set; } = 1; + + [XmlElement("limited_flag")] + public int LimitedFlag { get; set; } + + [XmlElement("open_unixtime")] + public long OpenUnixTime { get; set; } = 1622304001; + + [XmlElement("close_unixtime")] + public long CloseUnixTime { get; set; } = 1906387199; + + [XmlElement("created")] + public string? Created { get; set; } = "1"; + + [XmlElement("modified")] + public string? Modified { get; set; } = "1"; +} \ No newline at end of file diff --git a/GC-local-server-rewrite/server/Server.cs b/GC-local-server-rewrite/server/Server.cs new file mode 100644 index 0000000..46889e3 --- /dev/null +++ b/GC-local-server-rewrite/server/Server.cs @@ -0,0 +1,65 @@ +using EmbedIO; +using EmbedIO.Actions; +using EmbedIO.Files; +using EmbedIO.WebApi; +using GCLocalServerRewrite.backports; +using GCLocalServerRewrite.common; +using GCLocalServerRewrite.controllers; +using GCLocalServerRewrite.models; +using Swan.Logging; + +namespace GCLocalServerRewrite.server; + +public class Server +{ + public static WebServer CreateWebServer(string[] urlPrefixes) + { + InitializeDatabase(); + var cert = CertificateHelper.InitializeCertificate(); + + var server = new WebServer(webServerOptions => webServerOptions + .WithUrlPrefixes(urlPrefixes) + .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), + module => module.WithController<CardServiceController>()) + .WithWebApi(Configs.UPLOAD_SERVICE_BASE_ROUTE, CustomResponseSerializer.None(true), + module => module.WithController<UploadServiceController>()) + .WithWebApi(Configs.RESPONE_SERVICE_BASE_ROUTE, CustomResponseSerializer.None(true), + module => module.WithController<ResponeServiceController>()) + .WithWebApi(Configs.INCOM_SERVICE_BASE_ROUTE, CustomResponseSerializer.None(true), + module => module.WithController<IncomServiceController>()) + .WithWebApi(Configs.ALIVE_BASE_ROUTE, CustomResponseSerializer.None(true), + module => module.WithController<AliveController>()) + .WithWebApi(Configs.SERVER_BASE_ROUTE, CustomResponseSerializer.None(true), + module => module.WithController<ServerController>()) + .WithWebApi(Configs.RANK_BASE_ROUTE, CustomResponseSerializer.None(true), + module => module.WithController<RankController>()) + .WithStaticFolder(Configs.STATIC_BASE_ROUTE, PathHelper.HtmlRootPath, true, m => m + .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)) + .WithModule(new ActionModule("/", HttpVerbs.Any, + ctx => ctx.SendDataAsync(new { Message = "Error" }))); + + // Listen for state changes. + server.StateChanged += (_, e) => $"WebServer New State - {e.NewState}".Info(); + + return server; + } + + private static void InitializeDatabase() + { + DatabaseHelper.InitializeLocalDatabase(Configs.CARD_DB_NAME, + typeof(Card), typeof(CardBData), typeof(CardDetail)); + DatabaseHelper.InitializeLocalDatabase(Configs.MUSIC_DB_NAME, + typeof(Music), typeof(MusicAou), typeof(MusicExtra)); + } +} \ No newline at end of file diff --git a/GC-local-server-rewrite/static/favicon.ico b/GC-local-server-rewrite/static/favicon.ico new file mode 100644 index 0000000..993e925 Binary files /dev/null and b/GC-local-server-rewrite/static/favicon.ico differ diff --git a/GC-local-server-rewrite/static/index.html b/GC-local-server-rewrite/static/index.html new file mode 100644 index 0000000..5ece4b5 --- /dev/null +++ b/GC-local-server-rewrite/static/index.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <title>Title + + +Hello world! + + \ No newline at end of file diff --git a/GC-local-server-rewrite/static/news.png b/GC-local-server-rewrite/static/news.png new file mode 100644 index 0000000..f720859 Binary files /dev/null and b/GC-local-server-rewrite/static/news.png differ