This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
A private server emulator for Taiko no Tatsujin Nijiiro (CHN v12r00_cn and 39.06 v12r08_ww). The same ASP.NET Core 10 process serves the game's protobuf endpoints and hosts the Blazor WebAssembly admin UI on the same Kestrel server, so there is no separate frontend deployment. The codebase follows a hexagonal/ports-and-adapters layout: a pure Domain core, an Application orchestration layer that depends only on ports, an Infrastructure project that owns the real EF Core context + filesystem catalog + JWT issuer + clock, and one adapter project per inbound surface (admin REST, AllNet/Mucha lifecycle, and one per game protocol version).
12 projects, all net10.0 unless noted:
- Domain — pure entities, value objects, enums, domain constants. No package or project references. Namespace
TaikoLocalServer.Domain.*. - Contracts.AdminApi — DTO-only project: ViewModels, Requests, Responses, the WebUI-shared ServerData JSON shapes (DanData, MusicDetail, IVerupNo), and the
PlaySettingConverterthat the admin REST API and TaikoWebUI both serialize/deserialize. References Domain only (for enums). No business logic. - Application — orchestration. Mediator request/handler types, port interfaces (
ITaikoDbContext,IGameDataCatalog,IJwtTokenService,IClock), Common* DTOs, server-only ServerData JSON shapes (EventFolderData, MovieData, QRCodeData, ShopFolderData, SongIntroductionData), application-only utilities, settings (ServerSettings,DataSettings). References Domain + Contracts.AdminApi (the latter brings in DanData/MusicDetail/IVerupNo for catalog signatures and handlers). - Infrastructure — EF Core 10 + SQLite (
TaikoDbContextimplementingITaikoDbContext), filesystem catalog (FileGameDataCatalog),JwtTokenService,SystemClock. Owns all migrations (underInfrastructure/Persistence/Migrations/). Migrations are applied automatically on server startup. - Adapters.AdminApi — admin REST controllers (
/api/...) consumed by TaikoWebUI. JWT bearer auth scheme registered here. Apply[AuthorizeIfRequired]on these. - Adapters.AllnetMucha — AmAuth/AmUpdater/Garmc/MuchaActivation controllers, the
AllNetRequestMiddleware(zlib-decompresses base64 form bodies for/sys/servlet/PowerOn), and the Mucha + Garm wire types. - Adapters.GameProtocol.Shared —
BaseProtocolController, gzip/header-strip helpers shared by both game-protocol adapters. - Adapters.GameProtocol.WwR08 — protobuf controllers + wire types + Mapperly mappers for the 39.06 WW client. Routes
/v12r08_ww/.... - Adapters.GameProtocol.CnR00 — protobuf controllers + wire types + Mapperly mappers for the CHN client. Routes
/v12r00_cn/.... - Host — ASP.NET Core 10 entry point. Owns
Program.cs,Configurations/,Certificates/,wwwroot/,Logging/CsvFormatter.cs. Hosts the Blazor UI viaUseBlazorFrameworkFiles()+MapFallbackToFile("index.html").<AssemblyName>TaikoLocalServer</AssemblyName>preserves the published exe name (TaikoLocalServer.exe). - TaikoWebUI — Blazor WebAssembly admin UI (MudBlazor 9). References Contracts.AdminApi only. Referenced as a
<ProjectReference>by Host so the WASM build artifacts are bundled into Host's publish output (then served viaUseBlazorFrameworkFiles()). - LocalSaveModScoreMigrator — standalone CLI (System.CommandLine) for importing local-save-mod JSON dumps into
taiko.db3. References Infrastructure to reuse the EF Core context. Not part of the runtime.
The dependency direction is strictly: Domain ← Contracts.AdminApi ← Application ← Infrastructure ← Host, with the adapters slotting in at the right layer (Adapters.* → Application for game/admin/AllNet logic; Host references all adapters and Infrastructure to compose the runtime). Domain has zero outbound references.
Every adapter project ships a DependencyInjection.cs that defines exactly one extension method on IServiceCollection (or IApplicationBuilder if it needs middleware wiring), e.g. AddAdminApi(IConfiguration), AddAllnetMucha() / UseAllnetMucha(), AddGameProtocolWwR08(), AddGameProtocolCnR00(). Adding a new game version means: create Adapters.GameProtocol.<Version>/, add one <ProjectReference> to Host, add one Add...() call in Program.cs. Adapters never reference each other (except GameProtocol.WwR08 and GameProtocol.CnR00 may both reference GameProtocol.Shared). Cross-adapter coordination flows through the Application layer.
Run from the repo root:
# Build the whole solution
dotnet build
# Publish a self-contained single-file Windows exe (Release config sets PublishSingleFile/SelfContained)
dotnet publish Host/Host.csproj
# Output: Host/bin/Release/net10.0/win-x64/publish/TaikoLocalServer.exe
# Run the server in dev (auto-applies migrations)
dotnet run --project Host
# Add an EF Core migration — note the split: Infrastructure owns the context, Host is the startup project
dotnet ef migrations add <Name> --project Infrastructure --startup-project Host
# Update database manually (normally not needed — server migrates on startup)
dotnet ef database update --project Infrastructure --startup-project Host
# Run the score migrator
dotnet run --project LocalSaveModScoreMigrator -- --save-file-path <path> --baid <id>There are no automated tests in this repository.
Host/appsettings.json is intentionally near-empty. Real configuration lives in Host/Configurations/ (loaded explicitly in Program.cs):
Kestrel.json— multi-port binding. The server listens on multiple ports simultaneously (5000 base, 80 AmAuth, 10122 Mucha, 54430/54431 game WW, 57402 game CN, 443 Garmc) using one cert atHost/Certificates/cert.pfx.Database.json— only setsDbFileName. The DB lives at<exe-dir>/wwwroot/<DbFileName>(defaulttaiko.db3).ServerSettings.json—EnableMoreSongsraisesDomainConstants.MusicIdMaxfrom 1600 to 9000 (use with caution per the warning inProgram.cs).DataSettings.json— filenames for the server-owned JSON datatables underwwwroot/data/.AuthSettings.json— JWT issuer/audience/key.AuthenticationRequired: falsedisables auth on all admin API endpoints (theAuthorizeIfRequiredAttributefilter short-circuits).Logging.json— Serilog. There is also a side-channel CSV sink inProgram.cs(HeadClerkLog-*.csv) that captures only log messages starting with"CSV WRITE:"from the HeadClerk2 controller inAdapters.GameProtocol.{WwR08,CnR00}.
These files are copied to the publish output via <None Include=... CopyToOutputDirectory="PreserveNewest"/> in Host/Host.csproj. WebUI configuration is fetched at runtime from wwwroot/appsettings.json over HTTP (see TaikoWebUI/Program.cs).
Infrastructure.GameDataCatalog.PathHelper.GetRootPath() returns the wwwroot directory next to the running executable — not the project source's wwwroot. Anything under Host/wwwroot/data/ is copied to that location on build/publish:
wwwroot/data/*.json— server-owned datatables that operators edit (dan_data.json,event_folder_data.json,gaiden_data.json,intro_data.json,locked_*_data.json,movie_data.json,qrcode_data.json,shop_folder_data.json,token_data.json,special_songs_data.json). Documented inHost/README.md.wwwroot/data/datatable/*.bin— game-owned binary datatables (musicinfo.bin,music_order.bin,wordlist.bin,don_cos_reward.bin,shougou.bin,neiro.bin). These ship with the game install and must be copied in by the operator before first run (see top-levelREADME.md). Decoded*.jsonsiblings may also be present.wwwroot/taiko.db3— SQLite DB.
IGameDataCatalog (singleton, implemented in Infrastructure as FileGameDataCatalog) loads all of the above once at startup via await catalog.InitializeAsync() in Program.cs. Treat its in-memory dictionaries as immutable for the lifetime of the process.
The game protocol uses protobuf-net over HTTP. Requests are gzip-compressed and (for some endpoints) prefixed with a 32-byte header that controllers strip before deserializing.
Two game versions are supported in parallel and cleanly separated:
Adapters.GameProtocol.CnR00/Wire/— protobuf wire types for the CHN client. Controller routes use/v12r00_cn/....Adapters.GameProtocol.WwR08/Wire/— protobuf wire types for the 39.06 WW client. Controller routes use/v12r08_ww/....Application/Dtos/— version-agnostic Common* DTOs.
The flow: adapter controller deserializes the version-specific request → Riok.Mapperly source-generated mapper (in the same adapter project) converts it to a Common* DTO in Application → Mediator (martinothamar) Handlers/*Query/*Command in Application operates only on Common* types → mapper converts the response back to the requested version's protobuf type. Adding a new game version means adding a new Adapters.GameProtocol.<Version>/ project (Wire types + mappers + controllers + a DependencyInjection.cs); handlers and DB code do not change.
Controllers in Adapters.GameProtocol.* inherit BaseProtocolController (in Adapters.GameProtocol.Shared). Controllers in Adapters.AdminApi inherit BaseAdminController. Both lazily resolve IMediator and Logger from HttpContext.RequestServices. Don't inject these via constructor — match the existing pattern.
- Don't add config to
Host/appsettings.json. Add a new section to one of the files inHost/Configurations/, register it inProgram.cs(or in the relevant adapter'sDependencyInjection.cs), and add a<None Include="Configurations/Foo.json"><CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory></None>block toHost/Host.csproj. - New persisted field? Add it to the entity in
Domain/Entities/, thendotnet ef migrations add ...againstInfrastructurewithHostas the startup project. Migrations live underInfrastructure/Persistence/Migrations/. Never edit existing migration files. - New game endpoint? Implement both
_cnand_wwroute variants in their respective adapter projects (Adapters.GameProtocol.CnR00andAdapters.GameProtocol.WwR08), each mapping into the sameCommon*request via Mapperly. Send through Mediator; never queryITaikoDbContextdirectly from a controller — that's a handler's job. - New admin API endpoint? Add it to
Adapters.AdminApi/Controllers/. The DTO types it accepts/returns live inContracts.AdminApi/{Requests,Responses,ViewModels}so the WebUI can deserialize them. Apply[AuthorizeIfRequired]. - New port? Define the interface in
Application/Abstractions/, implement it inInfrastructure/, and register both inInfrastructure/DependencyInjection.cs'sAddInfrastructure(IConfiguration). Handlers depend on the port, never on the implementation. - New ServerData JSON shape? If TaikoWebUI consumes it, put it in
Contracts.AdminApi/ServerData/. If it's server-only (loaded byFileGameDataCatalog, consumed by handlers/mappers), put it inApplication/ServerData/. - GlobalUsings. Each project has its own
GlobalUsings.cs. The Host's globals re-export Mediator, ProtoBuf where relevant. The Contracts.AdminApi globals re-export Domain.Enums and the project's own ViewModels. Read the relevant project'sGlobalUsings.csbefore adding explicitusingstatements that might already be global. - Auth. Use
[AuthorizeIfRequired](inAdapters.AdminApi/Filters/) on admin API endpoints so the operator-controlledAuthSettings.AuthenticationRequiredtoggle works. - Mediator handler conventions (martinothamar's library, registered via
AddApplication()inApplication/DependencyInjection.cs):- Request types are
readonly record struct, notrecord class. IRequestHandler<TRequest, TResponse>.HandlereturnsValueTask<TResponse>, notTask<TResponse>. For void requests useIRequestHandler<TRequest>and returnValueTask<Unit>ending withreturn Unit.Value;.- Service lifetime is scoped (set by
opt.ServiceLifetime = ServiceLifetime.Scoped) because handlers injectITaikoDbContext. Don't change this without also switching toIDbContextFactory<TaikoDbContext>and reworking the handlers. - Controllers always pass
HttpContext.RequestAbortedas the second argument toMediator.Send(...)for cancellation hygiene.
- Request types are