An open-source, rsync-like patcher for game clients and desktop applications.
Hina delivers fast, bandwidth-efficient updates by computing rolling checksums against local files and transferring only the chunks that differ. It supports Ed25519 manifest signing, Brotli-compressed chunk storage, content-defined chunking, automatic retry with exponential backoff, and structured logging -- all available as a standalone CLI or as a library you embed in your own application.
- rsync-like delta patching -- rolling checksum matching transfers only changed blocks.
- Content-defined chunking (CDC) -- FastCDC-inspired algorithm for better deduplication when files have insertions or deletions.
- Ed25519 manifest signing -- cryptographic verification of every manifest before patching.
- Per-chunk and per-file hash verification -- integrity checks at every stage.
- Brotli-compressed chunk storage -- reduces server bandwidth and storage.
- Retry with exponential backoff -- automatic recovery from transient 5xx and network errors.
- Backup and rollback -- automatic backups enable instant rollback on failure.
- Concurrent downloads -- configurable parallelism for faster patching.
- Structured logging -- built on
Microsoft.Extensions.Loggingwith--verbosedebug output. - Static hosting -- included ASP.NET Core host, or deploy to any CDN / Nginx.
- 102 unit and integration tests -- comprehensive test coverage across the core library.
- Open source end-to-end -- no proprietary dependencies or services.
For detailed guides beyond this README, see the docs/ directory:
- Architecture -- project structure, class diagrams, design decisions
- Package Manager Guide --
hina install/update/uninstall, descriptor schema, hooks, signature chain - Configuration -- full reference for all config properties
- Builder Guide -- manifest generation, CDC vs fixed chunking, CI/CD
- CLI Guide -- all commands, flags, exit codes, common workflows
- Host Guide -- deployment, Nginx, Docker, CDN, CORS
- Security -- Ed25519 signing, threat model, best practices
- Integration Guide -- embedding Hina.Core in your app
- Troubleshooting -- common errors and solutions
- Changelog -- feature history
Hina produces a manifest and a chunk store from a build directory. Clients download only the chunks they are missing, rebuild files locally, and verify integrity.
Build artifacts (your game / app files)
|
v
Hina.Builder
|
+--> manifest.json (file list, hashes, chunk map, optional signature)
|
+--> chunks/ (Brotli-compressed blocks, stored by content hash)
|
v
Static host or CDN (Hina.Host, Nginx, S3, etc.)
Client startup
|
v
Fetch manifest -----> Verify Ed25519 signature (if configured)
|
v
Rolling checksum scan of local files
|
v
Download missing chunks (concurrent, with retry + exponential backoff)
|
v
Rebuild file --> Verify hash --> Swap in place
|
v
Success or Rollback from backup
Quick install (Linux / macOS):
curl -fsSL https://raw.githubusercontent.com/Arutosio/Hina/master/scripts/install.sh | bashQuick install (Windows PowerShell):
iwr -useb https://raw.githubusercontent.com/Arutosio/Hina/master/scripts/install.ps1 | iexPin a specific version with HINA_VERSION=v1.2.3 curl ... | bash (or
$env:HINA_VERSION = 'v1.2.3' before piping into iex). Override the
install directory with HINA_INSTALL_DIR, and skip the PATH edit with
HINA_NO_MODIFY_PATH=1.
Re-running the one-liner on an existing install drops you into an
interactive menu: reinstall, clean reinstall (wipes registry +
apps + keys), integrity check (verifies the installed binary's
SHA-256 against the published release), uninstall (full / configs /
binary-only), or exit. The script is power-loss safe (atomic rename),
network-drop safe (curl -C - resume + 5x retry), and
corruption-detecting (per-archive .sha256 companion). See
docs/Install-Script.md for full capability
reference and a flow diagram.
For manual install via .deb / .pkg / .msi / Scoop, each GitHub
release also ships:
| OS | Recommended | Fallback |
|---|---|---|
| macOS (Apple Silicon / Intel) | Hina-macos-arm64.pkg / Hina-macos-x64.pkg — double-click |
Hina-macos-*.tar.gz + ./install.sh |
| Debian / Ubuntu / derivatives | Hina-linux-x64.deb — sudo dpkg -i Hina-linux-x64.deb |
Hina-linux-x64.tar.gz + ./install.sh |
| Arch / Fedora / openSUSE / any other Linux | Hina-linux-x64.tar.gz + ./install.sh (no root needed; lands in ~/.local/bin) |
— |
| Windows (x64 / arm64) | Hina-windows-x64.msi (or arm64.msi) — double-click |
scoop install https://github.com/Arutosio/Hina/releases/latest/download/hina.json, or Hina-windows-*.zip + install.bat |
On Arch Linux specifically there isn't (yet) a PKGBUILD on AUR; the
.tar.gz + install.shflow works the same way as on every other distro and requires no root.
After install, verify with:
hina --help
hina install <url-to-hina.app.json>See the Package Manager Guide for the full end-user workflow and the CLI Guide for every command + flag.
# 1. Build the solution
dotnet build Hina.sln
# 2. Generate signing keys
dotnet run --project Hina.Builder -- keygen --out ./keys --name hina
# 3. Build a patch from your game directory
dotnet run --project Hina.Builder -- build \
--input ./build \
--out ./patch \
--base https://patch.example.com/ \
--version 1.0.0 \
--sign-key ./keys/hina.key.b64
# 4. Serve the patch
dotnet run --project Hina.Host
# 5. Patch a client
dotnet run --project Hina.CLI -- patch \
--dir ./client \
--base https://patch.example.com/ \
--pubkey ./keys/hina.pub.b64| Project | Description |
|---|---|
Hina.Core |
Core library: patching engine, rsync matching, manifest handling, hashing, signing, compression, CDC |
Hina.CLI |
Command-line client patcher |
Hina.Builder |
Manifest and chunk store generator |
Hina.Host |
ASP.NET Core static HTTP server for serving patches |
Requirements: .NET SDK 10.x
Build everything:
dotnet build Hina.slnPublish the CLI for Windows and Linux:
pwsh ./scripts/publish-cli.ps1Run all tests (102 unit and integration tests):
dotnet test Hina.slnThe builder scans a directory of build artifacts and produces a manifest plus a chunk store.
dotnet run --project Hina.Builder -- keygen --out ./keys --name hinaThis creates two files:
./keys/hina.key.b64-- Ed25519 private key (keep secret)./keys/hina.pub.b64-- Ed25519 public key (distribute to clients)
dotnet run --project Hina.Builder -- build \
--input ./build \
--out ./patch \
--base https://patch.example.com/ \
--version 1.0.0 \
--chunk 65536 \
--sign-key ./keys/hina.key.b64CDC produces variable-size chunks based on content boundaries. This provides significantly better deduplication when files change through insertions or deletions rather than in-place modifications.
dotnet run --project Hina.Builder -- build \
--input ./build \
--out ./patch \
--base https://patch.example.com/ \
--version 1.0.0 \
--chunking cdc \
--min-chunk 2048 \
--max-chunk 65536 \
--avg-chunk 8192 \
--sign-key ./keys/hina.key.b64| Command | Description |
|---|---|
build |
Generate manifest and chunk store from a build directory |
keygen |
Generate an Ed25519 key pair for manifest signing |
Build flags:
| Flag | Description |
|---|---|
--input |
Path to the build artifacts directory |
--out |
Output directory for manifest and chunks |
--base |
Base URL of the patch server |
--version |
Version string for this build |
--chunk |
Fixed chunk size in bytes (default: 65536) |
--chunking |
Chunking mode: fixed or cdc (default: fixed) |
--min-chunk |
Minimum CDC chunk size (default: 2048) |
--max-chunk |
Maximum CDC chunk size (default: 65536) |
--avg-chunk |
Average CDC chunk size (default: 8192) |
--sign-key |
Path to Ed25519 private key file |
-v, --verbose |
Enable debug logging output |
Keygen flags:
| Flag | Description |
|---|---|
--out |
Output directory for key files |
--name |
Base name for key files |
Hina.Host is a lightweight ASP.NET Core static file server purpose-built for serving patch manifests and chunks. It can serve one program or several programs from a single process, with built-in rate limiting, per-IP abuse detection, and structured request logging.
Start the host:
dotnet run --project Hina.HostOn first run, if no hina.host.json exists and stdin is a terminal, an interactive setup wizard launches automatically to configure port, single/multi-app mode, rate limits, and CORS. Re-run it any time with --setup.
Hina.Host binds to http://0.0.0.0:49876 by default. The port is in the IANA dynamic/private range and is not assigned to any common service. Override it with --port, --urls, the urls config key, or the ASPNETCORE_URLS environment variable.
hina.host.json (full schema, see Hina.Host/hina.host.example.json):
{
"urls": "http://0.0.0.0:49876",
"root": "patch",
"requestsPerMinutePerIp": 600,
"abuseThresholdPerMinute": 300,
"summaryIntervalSeconds": 60,
"statsEnabled": true,
"cors": []
}| Flag | Description |
|---|---|
--root <path> |
Patch directory (single-app mode) |
--port <n> |
Shortcut for --urls http://0.0.0.0:<n> |
--urls <list> |
Bind URLs (semicolon-separated) |
--config <path> |
Use a specific JSON config |
--rate-limit <n> |
Max requests/minute per (IP, App). 0 disables. |
--abuse-threshold <n> |
Log Warning when (IP, App) exceeds N req/min |
--cors <origins> |
Comma-separated allowed origins |
--no-stats |
Disable the /stats endpoint |
--setup |
Force the interactive setup wizard |
-h, --help |
Show help |
A single host can serve patches for several independent programs. Define apps in the config:
{
"urls": "http://0.0.0.0:49876",
"apps": {
"gameA": "/var/patches/gameA",
"gameB": "/srv/gameB/release"
}
}Each app is served under /<appName>/..., gets its own rate-limit bucket per IP, and is tracked separately in logs and /stats. Requests to unknown prefixes return 404 without touching the filesystem. Build each app with --base https://host/<appName>/ so client baseUrl resolves correctly. When apps is set, root is ignored.
| Endpoint | Description |
|---|---|
GET /health |
Liveness probe, returns "ok". |
GET /stats |
Loopback-only JSON snapshot: totals, top IPs / apps / paths in the last minute, rejections per app. |
Out of the box the host logs:
Informationon every manifest GET (Update check: app={App} ip={Ip} path={Path} ua={UserAgent}) - useful to track which clients are polling for updates.Warningwhen the per-(IP, App) rate limiter rejects a request.Warningwhen an IP exceeds the configured abuse threshold (throttled to once per minute per IP).Informationperiodic traffic summary.
A Dockerfile and docker-compose.yml are included:
docker compose -f Hina.Host/docker-compose.yml up -dSee docs/Host-Guide.md for Nginx/Apache/CDN deployment, CORS, and performance tuning.
The CLI is the client-side patcher. It reads a configuration file or accepts flags directly.
patch -- Download and apply all missing or changed files:
dotnet run --project Hina.CLI -- patch \
--dir ./client \
--base https://patch.example.com/check -- Compare local files against the manifest without downloading:
dotnet run --project Hina.CLI -- check \
--dir ./client \
--base https://patch.example.com/verify -- Verify integrity of all local files against manifest hashes:
dotnet run --project Hina.CLI -- verify \
--dir ./client \
--base https://patch.example.com/rollback -- Restore files from backups if a patch failed:
dotnet run --project Hina.CLI -- rollback \
--dir ./client \
--base https://patch.example.com/cleanup -- Remove leftover temporary and backup files:
dotnet run --project Hina.CLI -- cleanup \
--dir ./client \
--base https://patch.example.com/| Flag | Description |
|---|---|
--dir |
Target directory to patch (required) |
--base |
Patch server base URL |
--channel |
Release channel (default: stable) |
--config |
Path to configuration file |
--pubkey |
Path to Ed25519 public key for manifest verification |
-v, --verbose |
Enable debug logging output |
--help |
Show help information |
The CLI reads hina.config.json from the working directory, or from a path specified with --config. All properties are also available programmatically through PatcherConfig.
Example hina.config.json:
{
"baseUrl": "https://patch.example.com/",
"channel": "stable",
"concurrency": 4,
"chunkSize": 65536,
"verify": true,
"backup": true,
"trustedPublicKey": "BASE64_ED25519_PUBLIC_KEY",
"maxRetries": 3,
"retryBaseDelayMs": 1000,
"chunkingMode": "fixed",
"minChunkSize": 2048,
"maxChunkSize": 65536,
"avgChunkSize": 8192
}| Property | Type | Default | Description |
|---|---|---|---|
BaseUrl |
Uri |
http://localhost/ |
Base URL of the patch server |
Channel |
string |
"stable" |
Release channel name |
Concurrency |
int |
4 |
Number of concurrent chunk downloads |
ChunkSize |
int |
65536 |
Fixed chunk size in bytes |
Verify |
bool |
true |
Verify file hashes after patching |
Backup |
bool |
true |
Keep backups of original files for rollback |
TrustedPublicKey |
string? |
null |
Base64-encoded Ed25519 public key for manifest signature verification |
MaxRetries |
int |
3 |
Maximum retry attempts on transient errors (5xx, network failures) |
RetryBaseDelayMs |
int |
1000 |
Base delay in milliseconds for exponential backoff between retries |
ChunkingMode |
string |
"fixed" |
Chunking strategy: "fixed" for fixed-size or "cdc" for content-defined chunking |
MinChunkSize |
int |
2048 |
Minimum chunk size in bytes (CDC mode only) |
MaxChunkSize |
int |
65536 |
Maximum chunk size in bytes (CDC mode only) |
AvgChunkSize |
int |
8192 |
Target average chunk size in bytes (CDC mode only) |
Notes:
ChunkSize(fixed mode) or the CDC size parameters must match between the builder and client configuration.- Setting
TrustedPublicKeyenables mandatory signature verification. If the manifest signature does not match, the patch is rejected. - Retry with exponential backoff activates automatically on HTTP 5xx responses and network-level errors. The delay doubles on each attempt:
RetryBaseDelayMs * 2^attempt.
Reference the Hina.Core project or NuGet package and use PatchClient directly:
using Hina.Core.Configuration;
using Hina.Core.Patching;
using Microsoft.Extensions.Logging;
// Set up logging (optional -- pass null for silent operation)
using var loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddConsole();
builder.SetMinimumLevel(LogLevel.Debug);
});
var logger = loggerFactory.CreateLogger<PatchClient>();
// Configure the patcher
var config = new PatcherConfig
{
BaseUrl = new Uri("https://patch.example.com/"),
Channel = "stable",
Concurrency = 4,
Verify = true,
Backup = true,
TrustedPublicKey = "BASE64_ED25519_PUBLIC_KEY",
MaxRetries = 3,
RetryBaseDelayMs = 1000
};
// Run the patch
var client = new PatchClient(config, logger);
var result = await client.PatchAsync("./client", CancellationToken.None);You can also load configuration from a JSON file:
using Hina.Core.Configuration;
var config = PatcherConfigLoader.Load("./hina.config.json");- Manifest signing: The builder signs the manifest with an Ed25519 private key. Clients verify the signature against a trusted public key before applying any changes.
- Chunk-level integrity: Every downloaded chunk is verified against its content hash before being written to disk.
- File-level integrity: After reconstruction, each file is verified against its full-file hash from the manifest.
- Rollback safety: If verification fails at any stage, the patcher restores the original file from backup.
For production deployments, always generate a key pair with keygen, sign manifests with --sign-key, and configure TrustedPublicKey on clients.
- Rolling checksum matching reduces bandwidth by identifying blocks that already exist locally, even if the file has been partially modified.
- Brotli compression on stored chunks reduces transfer size and disk usage on the patch server.
- Concurrent downloads (configurable via
Concurrency) saturate available bandwidth on high-latency connections. - Content-defined chunking (CDC) provides superior deduplication for files that change through insertions or deletions. Unlike fixed-size chunking, CDC boundaries are determined by file content, so inserting a byte at the beginning of a file does not invalidate every chunk. This is particularly beneficial for large binary assets, archives, and database files.
- Hash-bucketed chunk storage keeps file system lookups fast even in stores with millions of chunks.
- Exponential backoff prevents thundering-herd effects when many clients retry against a temporarily degraded server.
"Manifest signature is invalid"
- Verify that the
TrustedPublicKeyin your configuration matches the public key corresponding to the private key used during the build. - Ensure the manifest was not modified after signing.
Client redownloads everything on each patch
- Confirm that
ChunkSize(or CDC size parameters) in the client configuration matches the values used by the builder. - If switching between
fixedandcdcchunking modes, a full redownload is expected on the first run.
404 errors on chunk downloads
- Confirm that the
chunks/directory is accessible from the host root URL. - If using a reverse proxy, verify it serves the full directory tree under the base URL.
Patch fails with network errors
- Hina retries transient failures automatically. Increase
MaxRetriesorRetryBaseDelayMsif your network is unreliable. - Check server logs or the
/healthendpoint onHina.Hostto confirm the server is running.
Verbose output for debugging
- Pass
-vor--verboseto the CLI for detailed debug-level log output showing each step of the patch process.
Contributions are welcome. To get started:
- Fork the repository and create a feature branch.
- Make your changes and add tests where appropriate.
- Run
dotnet test Hina.slnand confirm all tests pass. - Open a pull request against
masterwith a clear description of the change.
Please keep pull requests focused on a single concern. For larger changes, open an issue first to discuss the approach.
Hina is licensed under the Apache License 2.0.
