The remote client exist to help users without a FullClient on their computer to play by downloading resources from an external server. Because pushing directly the fullclient on a server/ftp can provoke some errors, this tool allow to :
- Get the files from a client used in another domain (Cross-origin resource sharing).
- Extracting files directly from GRF archive (versions 0x200 and 0x300 supported).
- Converting BMP files to PNG to speed up the transfer.
- Optimized to don't call any script if files are already extracted/converted (resource friendly).
- File Index for O(1) lookups: Files are indexed at startup for instant lookups instead of sequential search through GRFs.
- Gzip/Deflate Compression: Automatically compresses text-based responses (XML, TXT, LUA, etc.) to reduce bandwidth.
- HTTP Cache Headers (ETag, Cache-Control, 304 Not Modified) for browser caching.
- LRU Cache for fast repeated file access (in-memory caching).
- Missing Files Log for tracking and debugging missing game assets.
- Health Check API (
/api/health) for monitoring and diagnostics. - Korean Path Mapping for CP949/EUC-KR filename encoding support.
- Warm Cache for pre-loading frequently accessed files at startup.
###Add your fullclient###
Just put your GRFs files and DATA.INI file in the resources/ directory.
Overwrite the BGM/, data/ and System/ directories with your own folders.
Note: to be sure to use a compatible version of your GRFs, download GRF Builder and repack them manually (Option > Repack type > Repack), it will ensure the GRFs files are converted in the proper version
The server implements proper HTTP cache headers for browser caching:
- ETag: Content-based validation for conditional requests
- 304 Not Modified: Reduces bandwidth by validating client cache
- Cache-Control: Optimized per file type
- Game assets (sprites, maps, etc.): 1 year with
immutable - Other files: 30 days
- Game assets (sprites, maps, etc.): 1 year with
- Expires: HTTP/1.0 compatibility
This significantly reduces bandwidth and speeds up repeated requests, as unchanged files are served from browser cache.
The server implements an in-memory LRU (Least Recently Used) cache for file content:
- Default: 100 files, 256MB max memory
- O(1) get/set operations
- Automatic eviction of least recently used files
- Configurable via environment variables
CACHE_ENABLED=true
CACHE_MAX_FILES=100
CACHE_MAX_MEMORY_MB=256| Setting | Description | Default |
|---|---|---|
CACHE_ENABLED |
Enable/disable cache | true |
CACHE_MAX_FILES |
Max files in cache | 100 |
CACHE_MAX_MEMORY_MB |
Max memory usage | 256 MB |
Many Ragnarok GRF files contain Korean filenames encoded in CP949/EUC-KR. When these are read on non-Korean systems, they appear as mojibake (garbled characters).
The Problem:
Client requests: /data/texture/유저인터페이스/t_배경3-3.tga
GRF contains: /data/texture/À¯ÀúÀÎÅÍÆäÀ̽º/t_¹è°æ3-3.tga
The Solution:
The server uses a path-mapping.json file to map Korean UTF-8 paths to their GRF equivalents:
PATH_MAPPING_ENABLED=true
PATH_MAPPING_FILE=path-mapping.json| Setting | Description | Default |
|---|---|---|
PATH_MAPPING_ENABLED |
Enable/disable path mapping | true |
PATH_MAPPING_FILE |
Path to mapping file | path-mapping.json |
Creating a path-mapping.json file:
{
"generatedAt": "2026-01-18T12:00:00Z",
"paths": {
"data/texture/유저인터페이스/file.tga": "data/texture/À¯ÀúÀÎÅÍÆäÀ̽º/file.tga"
}
}The server can pre-load frequently accessed files into the LRU cache at startup or on-demand:
- Essential files pre-loaded for faster initial requests
- Pattern matching with wildcards for file groups
- Memory limit aware - stops warming when limits reached
- Statistics tracking for monitoring
- API endpoint for on-demand warming
- Configurable via environment variables
WARM_CACHE_ENABLED=true
WARM_CACHE_MAX_FILES=50
WARM_CACHE_MAX_MEMORY_MB=50| Setting | Description | Default |
|---|---|---|
WARM_CACHE_ENABLED |
Enable/disable warm cache | true |
WARM_CACHE_MAX_FILES |
Max files to warm | 50 |
WARM_CACHE_MAX_MEMORY_MB |
Max memory for warming | 50 MB |
Essential files warmed by default:
data\clientinfo.xml- Client configurationdata\lua files\*.lub- Lua scriptsdata\sprite\*.spr- Character sprites (basic)data\texture\*.bmp- UI textures
API Endpoints:
| Endpoint | Method | Description |
|---|---|---|
/api/warm-cache |
GET | Get warm cache status and stats |
/api/warm-cache/run |
POST | Trigger cache warming manually |
| Version | Status | Notes |
|---|---|---|
| 0x200 | ✅ Supported | 32-bit file offsets, no DES encryption |
| 0x300 | ✅ Supported | 64-bit file offsets (files > 4GB), no DES encryption |
| DES Encrypted | ✅ Supported |
The remote client provides several API endpoints for monitoring and diagnostics:
| Endpoint | Method | Description |
|---|---|---|
GET /api/health |
GET | Complete system health status |
GET /api/health/simple |
GET | Simple status check (fast) |
GET /api/cache-stats |
GET | Cache and index statistics |
GET /api/missing-files |
GET | Missing files log summary |
POST /api/missing-files/clear |
POST | Clear missing files log |
GET /api/warm-cache |
GET | Warm cache status and stats |
POST /api/warm-cache/run |
POST | Trigger cache warming |
GET /api/path-mapping |
Path mapping statistics |
Example response for /api/health:
{
"status": "ok",
"timestamp": "2026-01-18T12:00:00+00:00",
"grfs": {
"total": 2,
"valid": 2
},
"cache": {
"enabled": true,
"items": 45,
"hitRate": "96.5%"
},
"index": {
"totalFiles": 450000
},
"hasWarnings": false
}You can use this setup with the container to run the remote client API. Using docker container does not handle/copy the game files. You need to set up them first on the directory.
Copy the file .env.example to the same directory as .env
After copying the game files and modifiying your .env file, you can start the container services.
You can start using the remote client with NGINX using the following command:
docker compose --profile nginx up --buildThe webserver will be answering requests on the 80 port.
docker compose --profile apache up --buildThe vhosts configuration for both Apache and NGINX are baked into the container recipe. If you need to change this, you have to modify the respective Apache Dockerfile or NGINX dockerfile and re-run the docker compose command.
You can also override vhost configuration on /etc/apache2/sites-available/000-default.conf for Apache or /etc/nginx/conf.d/default.conf for NGINX using docker compose specification adding a bind mount, example:
volumes:
- ./:/var/www/html:ro
- ./my-own-nginx.conf:/etc/nginx/conf.d/default.confIf you set up all the required files correctly, you can run the python script tester.py. This script reads a file list in the same directory (list.txt) that contains multiple filenames that should be in a GRF. The script will request each file to the target webserver defined in TARGET_SERVER_ADDRESS variable, checking if the server answers with an HTTP code 200, if not, a new log file is created with the HTTP code and the file url requested, so you can debug it afterward.
Example, running the list against a working server (localhost, apache and nginx):
Apache
=== Final result ===
Total: 2784 | OK: 2784 | FAIL: 0
Elapsed time: 110.10 seconds
NGINX
=== Final result ===
Total: 2784 | OK: 2784 | FAIL: 0
Elapsed time: 1.86 seconds
