Skip to content

MrBIOSs/cpw_pw

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

43 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

CPW Patcher โ€” A modern patcher for game clients

Dart License

A secure and fast patcher for managing Perfect World game client updates. Fully compatible with the original protocol, but with a modern architecture and type safety.


Table of contents


Possibilities

Feature Description
RSA Signing Key generation (1024-bit), manifest signing via MD5withRSA
MySQL/PostgreSQL Abstract database layer, parameterized queries, and migrations
File Packing [4-byte LE size][deflate(data)] format for client compatibility
Incremental Patches Automated generation of v-N.inc for fast updates
Docker-ready Minimal footprint images and health checks

Roadmap

  • Integrate CPW into the Web Admin Panel
  • Upgrade RSA signature size to 2048-bit
  • Migrate from MD5withRSA to SHA-256withRSA
  • New modern launcher

Architecture

lib/
โ”œโ”€โ”€ app/                          # Entry point and dispatching
โ”‚   โ”œโ”€โ”€ cli_runner.dart           # Argument parsing and command execution
โ”‚   โ”œโ”€โ”€ command_info.dart         # Command metadata
โ”‚   โ””โ”€โ”€ command_registry.dart     # Command registry + menu output
โ”‚
โ”œโ”€โ”€ config/                       # Configuration
โ”‚   โ”œโ”€โ”€ config.dart               # Exports
โ”‚   โ”œโ”€โ”€ patcher_config.dart       # Typed immutable model
โ”‚   โ”œโ”€โ”€ config_loader.dart        # Loading .conf + env overrides
โ”‚   โ””โ”€โ”€ config_parser.dart        # Parsing .properties
โ”‚
โ”œโ”€โ”€ core/                                # Infrastructure and shared services
โ”‚   โ”œโ”€โ”€ crypto/                          # Cryptography and encoding
โ”‚   โ”‚   โ”œโ”€โ”€ crypto.dart                  # Crypto module exports
โ”‚   โ”‚   โ”œโ”€โ”€ exceptions.dart              # Typed encryption errors
โ”‚   โ”‚   โ”œโ”€โ”€ file_key_storage.dart        # Implementation keys.json
โ”‚   โ”‚   โ”œโ”€โ”€ key_storage_interface.dart   # Key storage abstraction
โ”‚   โ”‚   โ”œโ”€โ”€ models/
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ rsa_key_pair.dart        # Key pair model  (Public/Private)
โ”‚   โ”‚   โ””โ”€โ”€ utils/
โ”‚   โ”‚       โ”œโ”€โ”€ base64_path_encoder.dart # Path encoding
โ”‚   โ”‚       โ””โ”€โ”€ rsa_utils.dart           # RSA helpers
โ”‚   โ”‚
โ”‚   โ”œโ”€โ”€ database/                      # Database management
โ”‚   โ”‚   โ”œโ”€โ”€ database.dart              # DB module exports
โ”‚   โ”‚   โ”œโ”€โ”€ database_interface.dart    # IDatabase (abstraction)
โ”‚   โ”‚   โ”œโ”€โ”€ db_service.dart            # DB business logic
โ”‚   โ”‚   โ”œโ”€โ”€ exceptions.dart            # Typed DB errors
โ”‚   โ”‚   โ”œโ”€โ”€ utils/
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ sql_script_parser.dart # SQL script helper
โ”‚   โ”‚   โ””โ”€โ”€ adapters/
โ”‚   โ”‚       โ”œโ”€โ”€ mysql_adapter.dart     # MySQL implementation
โ”‚   โ”‚       โ””โ”€โ”€ postgres_adapter.dart  # PostgreSQL implementation
โ”‚   โ”‚
โ”‚   โ”œโ”€โ”€ logger/
โ”‚   โ”‚   โ””โ”€โ”€ logger_service.dart      # Logging: console + files
โ”‚   โ”‚
โ”‚   โ””โ”€โ”€ utils/
โ”‚       โ”œโ”€โ”€ ansi_colors.dart      # Terminal colors
โ”‚       โ”œโ”€โ”€ safe_path.dart        # Paths safety
โ”‚       โ””โ”€โ”€ utilities.dart        # Shared project utilities
โ”‚
โ”œโ”€โ”€ di/                           # Dependency Injection
โ”‚   โ””โ”€โ”€ service_locator.dart      # get_it locator configuration
โ”‚
โ””โ”€โ”€ features/                        # Business features (isolated modules)
    โ”œโ”€โ”€ revisions/                   # Version and revision management
    โ”‚   โ”œโ”€โ”€ manifest_service.dart    # Generation of files.md5 + v-N.inc
    โ”‚   โ”œโ”€โ”€ packer_service.dart      # Packing [size][deflate]
    โ”‚   โ”œโ”€โ”€ revisions.dart           # Revision module exports
    โ”‚   โ”œโ”€โ”€ revision_service.dart    # Revision logic: initial, new
    โ”‚   โ”œโ”€โ”€ commands/
    โ”‚   โ”‚   โ”œโ”€โ”€ initial_command.dart # ./cpw initial
    โ”‚   โ”‚   โ”œโ”€โ”€ listgen_command.dart # ./cpw listgen
    โ”‚   โ”‚   โ””โ”€โ”€ new_command.dart     # ./cpw new
    โ”‚   โ””โ”€โ”€ models/
    โ”‚       โ””โ”€โ”€ revision_state.dart  # Revision state
    โ”‚
    โ”œโ”€โ”€ security/                       # Security and binary patching
    โ”‚   โ”œโ”€โ”€ binary_patcher_service.dart # Key injection into binaries
    โ”‚   โ”œโ”€โ”€ rsa_service.dart            # Generation + signing
    โ”‚   โ”œโ”€โ”€ security.dart               # Security module exports
    โ”‚   โ””โ”€โ”€ commands/
    โ”‚       โ”œโ”€โ”€ patch_command.dart      # ./cpw x [exe]
    โ”‚       โ””โ”€โ”€ rsagen_command.dart     # ./cpw rsagen
    โ”‚
    โ””โ”€โ”€ setup/                       # Initialization and deployment
        โ”œโ”€โ”€ setup.dart               # Initialization module exports
        โ”œโ”€โ”€ setup_service.dart       # System initialization
        โ”œโ”€โ”€ validators.dart          # Environment validation
        โ””โ”€โ”€ commands/
            โ””โ”€โ”€ install_command.dart # ./cpw install

Design Principles

  • Feature-first: Code is grouped around business capabilities.
  • Dependency Inversion: Business logic depends on abstractions, not implementations.
  • Single Responsibility: Each class has a single, well-defined purpose.

Requirements

Component Version Purpose
Dart SDK โ‰ฅ 3.10.0 Records, pattern matching, async/await
MySQL โ‰ฅ 5.7 Storing file metadata and revisions
Docker โ‰ฅ 24.0 Optional: for containerization

Environment Verification

# Dart
dart --version  # Dart SDK version: 3.10.0 or later

# MySQL
mysql --version  # mysql  Ver 8.x or 5.7.x

# MariaDB
mariadb --version # mariadb (MySQL)  Ver 15.1 Distrib 10.6.25-MariaDB

Installation

1. Cloning a repository

git clone https://github.com/MrBIOSs/cpw_pw.git
cd cpw_pw

2. Installing dependencies

dart pub get

3. Building an executable file (AOT)

# For Windows
dart compile exe bin/cpw.dart -o bin/cpw.exe

# For Linux
dart compile exe bin/cpw.dart -o bin/cpw

# verification
./bin/cpw

(Optional) Download the finished binary file on Linux

curl -L -s "https://github.com/MrBIOSs/cpw_pw/releases/latest/download/cpw" -o ./cpw

After downloading the binary, create or download the files /config/patcher.conf and /config/install_mysql.sql in the same folder.


Configuration

File structure

./
โ”œโ”€โ”€ config/
โ”‚   โ”œโ”€โ”€ patcher.conf         # Main configuration settings
โ”‚   โ”œโ”€โ”€ install_mysql.sql    # Database initialization script
โ”‚   โ”œโ”€โ”€ install_postgres.sql # Database initialization script
โ”‚   โ””โ”€โ”€ keys.json            # RSA keys
โ”œโ”€โ”€ files/
โ”‚   โ”œโ”€โ”€ new/              # Input: Source files from developers
โ”‚   โ”‚   โ”œโ”€โ”€ element/
โ”‚   โ”‚   โ”œโ”€โ”€ launcher/
โ”‚   โ”‚   โ””โ”€โ”€ patcher/
โ”‚   โ””โ”€โ”€ CPW/              # Output: Files ready for distribution
โ”‚       โ”œโ”€โ”€ element/
โ”‚       โ”œโ”€โ”€ launcher/
โ”‚       โ””โ”€โ”€ patcher/
โ”œโ”€โ”€ log/                  # Application logs
โ”‚   โ”œโ”€โ”€ console.log
โ”‚   โ””โ”€โ”€ errors.log
โ””โ”€โ”€ bin/cpw               # Executable binary

config/patcher.conf - basic settings

# DB connection
db-host=localhost
db-port=3306
db-name=pw
db-user=user
db-password=password

# Paths (relative to baseDir)
patch-path=files
patch-new-dir=new
patch-cpw-dir=CPW

# Minimum client versions
min-element-ver=1
min-launcher-ver=1
min-patcher-ver=1

# Flags
remove-input-files=true
add-file-size-to-inc=true

Overriding via environment variables

# Docker / CI / Production
export DB_HOST=db.prod.internal
export DB_PASSWORD=${VAULT_DB_PASSWORD}

./bin/cpw install

Supported Variables:

Env Variable Config Key Description
DB_HOST db-host Database host
DB_PORT db-port Database port
DB_USER db-user Database user
DB_PASSWORD db-password Database password
DB_NAME db-name Database name
CPW_PATCH_PATH patch-path Base path for files

CLI Commands

Review

$ ./bin/cpw
Usage:
	./cpw install           	Install updater: database setup, RSA keys generation, paths
	./cpw rsagen            	Regenerate RSA keys
	./cpw x [executable]    	Patch executable with public RSA key
	./cpw initial           	Creates initial (base) revision state
	./cpw new               	Creates the next revision, packs files and generates manifests
	./cpw listgen           	Regenerate files.md5 and incremental patches from current DB state

Detailed description

./cpw install - System initialization

# Interactive installation
./cpw install

# Skip key generation (if already present)
./cpw install --skip-keys

# Skip database initialization (if already configured)
./cpw install --skip-db

# Preview without changes
./cpw install --help

What it does:

  1. Creates the directory structure: files/new/ and files/CPW/
  2. Initializes version files with the value set in min-*-ver
  3. (Optional) Creates database tables using install_*.sql
  4. (Optional) Generates RSA keys and saves them to keys.json

./cpw rsagen - Generating RSA keys

# Standard generation
./cpw rsagen

What it does:

  1. Generates an RSA-1024 key pair
  2. Saves it to config/keys.json (atomically, with a backup)
  3. Outputs the public key for copying

Output Format:

Generated Public Key:
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# RSA Public Key (copy-paste ready)
# Modulus (hex): a1b2c3...
# Exponent: 65537

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

Keys saved to config/keys.json
Tip: Set file permissions to 600 for security: chmod 600 config/keys.json

./cpw x [executable] - Injecting the key into the binary

# Patching an executable file
./cpw x client/Launcher.exe

# With a custom marker (if different from the default)
./cpw x client/Launcher.exe --marker="__CUSTOM_MARKER__"

# A preview of what will be done
./cpw x client/Launcher.exe --help

Client Requirements: The client source code must include a placeholder token of sufficient size.

What it does:

  1. Locates the token in the binary (via byte pattern search)
  2. Replaces it with the serialized public key (format: 4 Base64 strings)
  3. Preserves the original file size (via zero-padding)
  4. Verifies the write operation

./cpw initial - Preparing a basic audit

# Standard launch
./cpw initial

# Preview
./cpw initial --help

What it does:

  1. Creates the directory structure: files/new/{element,launcher,patcher}/
  2. Creates the files/CPW/{type}/{type}/ directory structure for packaged files
  3. Initializes version files with the value set in min-*-ver (defaults to 1)
  4. Synchronizes the state with the database
  5. Creates /info/pid with default value 101

Warning: Delete all *.sw files except version on the original client in the /config/(element, launcher, patcher) folder.


./cpw new - Creating the next revision

# Standard launch
./cpw new

# Skip manifest generation (packaging + DB only)
./cpw new --skip-manifests

# Forced overwrite (crash recovery)
./cpw new --force

# Preview
./cpw new --help

What it does:

  1. Reads files from files/new/{type}/ (recursively)
  2. Compresses each file using the format: [4-byte LE size][deflate(data, level=1)]
  3. Renames the files to Base64 (e.g., data/config.ini becomes ZGF0YQ==/Y29uZmlnLmluaQ==)
  4. Calculates the MD5 checksum of the compressed file
  5. Writes metadata to the database (via UPSERT)
  6. Automatically generates manifests (files.md5, v-N.inc, along with the RSA signature)
  7. Updates the version files
  8. Removes input files if remove-input-files flag is true.

Result:

Revision published successfully!

New revision state:
  element:   v2
  launcher:  v1
  patcher:   v1

Clients can now update to this revision.

Note: Ready-made update files can be obtained from the original Launcher if /patcher/server/updateserver.txt contains a link to an open source with files


./cpw listgen - Regeneration of manifestos

# Complete regeneration of all types
./cpw listgen

# Only for specific type
./cpw listgen --type=element

# Preview
./cpw listgen --help

What it does:

  1. Reads the current revision state from the database
  2. Generates files.md5:
    # 2
    abc123... data/Y29uZmlnLmluaQ    # first entry in the folder
    def456... data/Y29uZmlnLmlua=    # the rest are in the same folder
    
  3. Signs the manifest using MD5withRSA
  4. Generates incremental patches (v-N.inc) with the following prefixes:
    • + - File added within this revision range
    • ! - File modified within this range
  5. Updates the version files

When to use:

  • Accidentally deleted or corrupted files.md5 or v-N.inc
  • After manually modifying database records
  • For auditing purposes: to regenerate manifests from the current database state

Full detailed instructions

  1. Download CPW from Releases.
  2. Configure your database and settings in config/patcher.conf.
  3. Upload the files to any folder on your server.
  4. Run ./cpw install to set up the DB tables and generate your RSA keys.
  5. Run ./cpw initial to create the base (initial) revision state and prepare for updates.
  6. Create a symlink to make the output files downloadable via your web server:
ln -s /path/to/files/CPW /var/www/html/<NAME>
  1. Drop your updated game files into the files/new/directory. For example:
    • /element/data/tasks.data
    • For .pck changes: unpack the archive beforehand and put only the specific files you want to add to the update (e.g. /element/surfaces/<FOLDER_NAME>/<FILE_NAME>).
  2. In your original client, delete all *.sw files except version.sw inside the /config/(element, launcher, patcher) folders. Set the version to 1 inside those version.sw files. Also, set 101 in /patcher/server/pid.ini since the default value in CPW is 101.
  3. Copy Launcher.exe and patcher.exe from your client to any working directory on your server.
  4. Patch the client binaries with your generated public key using ./cpw x /path/to/Launcher.exe and ./cpw x /path/to/patcher.exe.
  5. Copy the patched executables back into your client folder (overwrite the old ones).
  6. Update the patcher URL in the client's /patcher/server/updateserver.txt file to point to your actual URL:
http://<ADDRESS>:<PORT>/<NAME>/
  1. Run the launcher and test the update.

Examples of use

Full workflow (first run)

# 1. System Initialization
./cpw install
# Prompts for database credentials, generates keys, and creates directory structure

# 2. Creating a base revision
./cpw initial

# 3. Preparing Source Files
cp -r ~/game-client/element/* files/new/element/
cp -r ~/game-client/launcher/* files/new/launcher/

# 4. Creating the First Revision
./cpw new
# Packs files, writes metadata to the DB, and generates manifests

# 5. Patching the Client Binaries (Done once during build)
./cpw x client/launcher.exe
./cpw x client/patcher.exe

# 6. Distribution: Files inside files/CPW/ are ready for client download

Adding an update

# 1. We put new/modified files
cp new_feature.data files/new/element/data/

# for .pck
cp *.pck.files/ files/new/element/name_pck/ 

# 2. Create a new revision
./cpw new
# Automatic: packaging, database, revision +1, manifests

# 3. Synchronize with the distribution server
rsync -av files/CPW/ cdn-server:/var/www/patch/

Disaster recovery

# If the disk is full or the manifests are corrupted

# 1. Checking the database status
mysql -u user -p password -e "SELECT MAX(revision) FROM files WHERE type='element';"

# 2. Regenerating manifests from the database
./cpw listgen

# 3. Checking the integrity
./cpw listgen --help

Running in Docker

Example using Docker. The following services are considered: pw-db, pw-web, and pw-server. pw-web includes CPW and the web pwadmin.

# docker-compose.yml
services:
  pw-db:
    image: mariadb:10.6
    container_name: pw-db
    restart: unless-stopped
    env_file: .env
    environment:
      MARIADB_DATABASE: ${DB_NAME}
      MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
      MARIADB_USER: ${DB_USER}
      MARIADB_PASSWORD: ${DB_PASSWORD}
    command: [
      "--sql-mode=", 
      "--lower-case-table-names=1",
      "--character-set-server=latin1",
      "--collation-server=latin1_swedish_ci",
      "--innodb-buffer-pool-size=512M",
      "--max-connections=500"
    ]
    volumes:
      - db_data:/var/lib/mysql
      - ./sql:/docker-entrypoint-initdb.d:ro
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 5s
      timeout: 5s
      retries: 5
      start_period: 20s
    networks:
      - pw-net

  pw-web:
    build:
      context: .
      dockerfile: Dockerfile.web
    container_name: pw-web
    restart: unless-stopped
    environment:
      PW_SERVER_HOST: pw-server
      DB_HOST: pw-db
      DB_NAME: ${DB_NAME}
      DB_USER: ${DB_USER}
      DB_PASSWORD: ${DB_PASSWORD}
    volumes:
      - ./etc:/etc
      - ./patcher:/opt/pw/patcher 
      - ./patcher/files/CPW:/var/www/html/autopatcher:ro
      - ./pwadmin:/var/www/html/pwadmin
    ports:
      - "80:80"
    depends_on:
      pw-db:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost/"]
      interval: 10s
      timeout: 5s
      retries: 3
    networks:
      - pw-net
  
networks:
  pw-net:
    driver: bridge

volumes:
  db_data:
# Launch
docker-compose up -d

# Viewing logs
docker-compose logs -f pw-web

# Executing a command in a container
docker-compose exec pw-web /opt/pw/patcher/cpw listgen

Docker

Build the image

Nginx to transfer finished files to the client.

# Dockerfile
FROM debian:bookworm-slim

RUN apt-get update && apt-get install -y --no-install-recommends \
    nginx ca-certificates curl \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app

ARG BASE_URL="https://github.com/MrBIOSs/cpw_pw/releases/latest/download"

RUN curl -L -s "${BASE_URL}/cpw" -o ./cpw \
    && curl -L -s "${BASE_URL}/cpw.sha256" -o ./cpw.sha256 \
    && sha256sum -c cpw.sha256 \
    && chmod +x ./cpw \
    && rm cpw.sha256

COPY config/ ./config/

RUN useradd -m -u 1000 patcher && chown -R patcher:patcher /app
USER patcher

HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
  CMD ./cpw || exit 1

CMD ["sh", "-c", "nginx -g 'daemon off;' & exec /app/bin/server"]
docker build -t pw-web .

and

docker exec -it pw-web ./cpw

Safety

File access rights

# After generating the keys
chmod 600 config/keys.json
chown patcher:patcher config/keys.json

# For logs
chmod 750 log/
chown -R patcher:patcher log/

Auditing and logging

  • Private keys are never logged.

Updating dependencies

# Vulnerability check
dart pub outdated --mode=null-safety
dart pub deps --style=compact

# Update
dart pub upgrade --major-versions
dart pub get

# Reanalysis
dart analyze
dart test

Troubleshooting

"Marker not found in executable"

Cause: The -----BEGIN PUBLIC KEY----- placeholder token was not found in the binary.

Solution:

  1. Ensure the token exists in the client's source code.
  2. Try specifying a custom token: ./cpw x client.exe --marker="__MARKER__"

"Database connection failed"

Cause: Failed to connect to the database.

Diagnostics:

# Access check
mysql -h $DB_HOST -u $DB_USER -p$DB_PASSWORD -e "SELECT 1;"

# Permissions check
mysql -h $DB_HOST -u $DB_USER -p$DB_PASSWORD $DB_NAME -e "SHOW TABLES;"

Solution:

  • Make sure the database is running and accessible over the network
  • Check the connection parameters in patcher.conf or env vars
  • Make sure the user has CREATE TABLE, INSERT, and SELECT permissions

"Serialized key exceeds placeholder size"

Cause: The serialized public key (216 bytes Base64 + 3 \n = 219 bytes) does not fit into the token.

"Version file out of sync"

Cause: The version file was manually modified and does not match the database.

Solution:

  • The system will automatically fix the desync the next time you run new or listgen
  • If you need to force a revision change: delete records with revision > N in the DB, then run ./cpw listgen

Logs for debugging

# View recent errors
tail -f log/errors.log

Migrating from Java

Transferring keys

If keys are stored in patcher.conf:

  1. Run ./cpw rsagen to generate new keys
  2. Manually change the data in keys.json

License

MIT License - see LICENSE file.


Contributing

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/your-feature
  3. Commit your changes + add tests
  4. Ensure all checks pass: dart analyze && dart test
  5. Submit a pull request

Support

Support the Project ๐Ÿ’–

If you want to support development, click the Sponsor button:

  • Use the Boosty link for card payments (RUB/USD/EUR).
  • Use the Tronscan link to copy the USDT (TRC-20) wallet address.

About

CPW for PW server

Topics

Resources

License

Stars

3 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages