Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Home Page

Amazing Core

Amazing Core is an open-source server emulator for Amazing World, an MMO originally developed by Ganz and shut down in 2018. This project provides a modular, configurable framework with tools for server management, asset handling, and game services, accessible via a web-based dashboard.

⚠️ Still in development - not yet in a playable state.

  • No multiplayer, NPCs, or quests yet;
  • Only the intro level and the empty Spring Bay map are accessible;
  • Do not use your real username or password;
  • Use any dummy username and password to log in;

But you can check out the work-in-progress prototype and join our community!

Download the game

You can install the latest published version with the following Steam link: Install

The game has its page on SteamDB, where you can also see additional information.

It is also possible to find an Android version on the internet.

Connect to the demo server

After installation, navigate to the game folder and open the ServerConfig.xml file in a text editor.

Modify the server address value as shown below:

ServerIP = 'springbay.amazingcore.org'

Now you can start the game.

  • To play the intro level, click the I'm new! button in the main menu;
  • To explore the Spring Bay, click the Log in button and enter any username and password;

Host your own server

With your own server, you can access the admin dashboard to configure skins, maps, NPCs, and other features (work in progress)*.

Modify the server address value as shown below:

ServerIP = 'localhost'

Pre-compiled binaries

  1. Download the latest server release from the GitHub section.
  2. Extract the archive to a folder of your choice;
  3. Run the server binary;

Once started, it will download the blob.db file (~2 GB).*

When the download is finished, you can start the game.

  • The API server will be available at http://localhost:3000
  • Use admin / admin to log in to the dashboard
  • The game server will listen on localhost:8182

Configuration

You can customize server settings using the config.json file.

KeyDescription
logger.levelLog verbosity: debug, debug+sql, info, warn, error
logger.handlerLog format: pretty (colored, formatted), text, json
servers.apiHTTP API bind address (e.g. localhost:3000)
servers.gameTCP game server bind address (e.g. localhost:8182)
settings.assetDeliveryURLBase CDN URL sent to game clients
settings.syncServerIPSync server IP sent in InitLocation responses
settings.syncServerPortSync server port sent in InitLocation responses
storage.blob.downloadAuto-download blob.db on first start if missing
storage.blob.urlURL to fetch blob.db from
storage.databases.corePath to core.db - main SQLite database
storage.databases.blobPath to blob.db - assets SQLite database
storage.explorerEnable the dashboard SQL explorer - only for testing!
python.venvPath to the managed Python virtual environment
secure.auth.usernameDashboard admin username
secure.auth.passwordDashboard admin password
secure.session.keyCookie session signing key
secure.session.secureSet Secure flag on session cookie (enable behind HTTPS)

Running the game on macOS

The Steam version of the game is not compatible with modern macOS (Apple Silicon), but I created a patcher that fixes it.

  1. Download the game using Steam.

  2. Open the game folder by right-clicking the game in your Steam library and selecting Manage -> Browse local files.

  3. Open the folder in terminal:

  1. Paste the following command into Terminal and press Enter.
curl -fsSL https://raw.githubusercontent.com/dv1x3r/amazing-core/master/tools/silicon.sh | bash
  1. Once the patcher has finished, you can launch the game normally!

You can also patch the game manually by following this guide.

License

This project is licensed under the GNU AGPL v3.

Amazing World™ is a registered trademark of Ganz. Amazing Core is an unofficial, fan-made project intended for personal and educational use only. It is not affiliated with or endorsed by Ganz or Amazing World™ in any way.

Cache Archive

Amazing World used Unity Streaming Assets, meaning things like images, audio, and asset bundles were stored on official servers and loaded by the game whenever they were needed. These files were also cached locally. Below is a list of known assets that have been found and shared by members of the community. Check out our Google Sheets for more details!


Loading cache list...

OID Calculator

A simple calculator for checking the OID (a unique object identifier) used by the game. It is usually stored as a big int64 number, that can be sliced into specific groups of bits to get the Class, Type, Server and Object number.

[Class][Type][Server][Number...]
  8b     8b     8b       40b
  • The CDN ID is a base64 representation of GSF OID.
  • The GSF OID is an int64 number that is sliced into specific groups of bits.

Build from source

Build using Go

To build the server from source, you will need Go 1.25 or newer:

make
# or
go build -o ./build/server ./cmd/server/main.go

To build and run with a single command:

make run
# or
go run ./cmd/server/main.go

You can choose between SQLite drivers by setting the CGO_ENABLED environment variable:

  • Build with CGO_ENABLED=0 to use modernc.org/sqlite driver (default);
  • Build with CGO_ENABLED=1 to use github.com/mattn/go-sqlite3 driver;

Folder structure

amazing-core/
├── cmd/
│   └── server/
│       └── main.go           - server entry point
├── data/
│   ├── cache.json            - asset metadata generated by cache-tool
│   └── sql/
│       ├── core_db/
│       │   ├── base.sql      - core.db base migration
│       │   └── updates/      - incremental migrations
│       ├── blob_db/
│       │   └── base.sql      - blob.db schema
│       └── queries/          - sql queries for admin dashboard explorer
├── internal/
│   ├── api/                  - http server for admin dashboard and asset streaming
│   ├── game/                 - game server and gsf messages handling
│   ├── network/              - tcp server protocol implementation
│   ├── services/             - business logic and database interaction
│   ├── config/               - configuration variables
│   └── lib/                  - shared libraries (e.g. logging, helpers)
├── tools/                    - development tools (e.g. asset importers)
└── web/                      - embedded frontend for admin dashboard

Data and databases

The data/ directory contains .sql database migrations and a cache.json file with currently known asset files metadata.

SQLite Databases

For simplicity and portability, the project uses SQLite. The data/sql/ directory is embedded into the server binary at compile time, and migrations are run automatically at startup.

core.db

Schema: data/sql/core_db/

The main SQLite database. Contains the classified list of available assets and random names used in the sign-up scene.

Managed by goose using migrations in data/sql/core_db/updates/. Shortcuts for creating, applying, and resetting migrations are available in the Makefile.

An initial squashed migration (data/sql/core_db/base.sql) includes both schema and seed data.

Use data/sql/core_db/squash.sh to squash update migrations into base.sql.

Files under data/sql/queries/ are named example SQL queries available to the admin dashboard`s SQL Explorer.

blob.db

Schema: data/sql/blob_db/

A separate SQLite database storing the raw binary content of game asset files.

Downloaded on startup if blob.download = true and the database is missing.

When a client requests /cdn/{cdnid}, the server fetches the blob column for that cdnid.

The database can be populated using the blob-tool or the admin dashboard.

Cache Metadata

cache.json

A summary of known cache files, including name (CDN ID), size, type, hash, GSF OID and basic bundle info (version, object counts, scene roots).

Generated with the cache tool Python script:

python tools/cache.py /path/to/cache/folder \
  --summary-file data/cache.json

This file is already included in the core.db base migration. For future updates (if new unique cache assets are discovered), add a new migration under updates/ with the additional data.

Networking

Overview

The game client communicates with the server over a persistent TCP connection using a custom binary protocol. All data is serialized with the BitProtocol codec - a bit-level encoding scheme that compresses integers by encoding only the bytes that are actually needed.

The implementation lives in internal/network/

Wire Format

Every message follows this structure:

┌─────────────────────────────────────────────────────────┐
│  length prefix  │        payload bytes          │ 0x00  │
│  (varint)       │     (BitStream encoding)      │ (EOF) │
└─────────────────────────────────────────────────────────┘

Length prefix

The length prefix is a variable-length big-endian integer where the MSB of each byte signals whether more bytes follow:

Each group of 7 bits contributes to the value, MSB = continuation flag:

[1xxxxxxx] [1xxxxxxx] [1xxxxxxx] [0xxxxxxx]
 more bytes more bytes more bytes last byte

Payload

The payload is a BitStream - a sequence of bits packed into bytes. The stream is read and written bit-by-bit; byte boundaries are only respected for raw string/byte-array data (aligned reads/writes).

After the last payload byte comes a null terminator 0x00.

BitStream Encoding

Objects (nullable)

Objects begin with a null flag bit:

[0] -> object is not null
[1] -> null / not present

Booleans

A single bit: 1 = true, 0 = false.

Compressed integers (int16, int32, int64)

Integers are compressed using a variable-width encoding that minimizes bit usage for small values.

The encoding starts with a compressed flag bit:

[1] -> compressed (fewer than max bytes needed)
      followed by a count of how many bytes are needed
      [0]   -> 0 bytes -> value fits in 4 bits
      [10]  -> 1 byte  -> value is 1 byte  (8 bits)
      [110] -> 2 bytes -> value is 2 bytes (16 bits)
      ...
[0] -> full width (all bytes written)

Floating-point numbers (float32, float64)

Written as raw not compressed IEEE 754 bytes.

Strings

Strings are encoded as:

  1. A compressed int32 for the UTF-8 byte length.
  2. The raw UTF-8 bytes, byte-aligned (any remaining bits in the current byte are skipped before writing).

Byte arrays

Same as strings: compressed length prefix followed by byte-aligned data.

Dates

Dates are encoded relative to a fixed epoch (0001-03-01). A null flag bit precedes the value:

[1] -> zero time (null)
[0] -> seconds since epoch, written as a raw 8-byte integer

Message Structure

Every message has a header followed by an optional body. The outer wrapper adds a null flag for the entire message object.

[0]          -> message is not null
[header]     -> GSF header fields
[0]          -> body is not null
[body fields]

GSF Header

FieldTypeConditionDescription
flagsint32alwaysBit flags
svcClassint32alwaysMessage service class
msgTypeint32alwaysMessage type for the given svcClass
requestIdint32if IsService()Correlates request/response pairs
logCorrelatorstringif IsRequest()Client-side correlation string
resultCodeint32if IsResponse()Result code (0 = success)
appCodeint32if IsResponse()Application-level error code
appStringstringif appCode != 0Error description string
appCodesarrayif appCode == 17Extended error code list

Flag semantics:

BitMeaning
flags & 2 == 0IsService - a request/response pair
flags & 1 != 0IsResponse (only when IsService)
flags & 2 != 0IsNotify - fire-and-forget notification
flags & 16 != 0IsDiscardable

Annotated Example

The very first message the client sends after connecting is a GetClientVersionInfo request. It is 22 bytes total:

hex:  15 20 c2 5c 04 6d 0c 0c 18 41 6d 61 7a 69 6e 67 57 6f 72 6c 64 00

Breakdown:

BytesValueMeaning
1521Length prefix: payload is 21 bytes
20 c2 5c 04 6d 0c 0c 18bit streamHeader + body
41 6d 61 7a 69 6e 67 57 6f 72 6c 64AmazingWorldString content (byte-aligned)
000Null terminator

Bit-by-bit payload decoding:

Bit  8:  [0]                -> message is not null
Bit  9:  [0]                -> header is not null
Bit 10:  [1][0]             -> flags compressed, 0 bytes -> 4-bit value
Bit 14:  [0000]             -> flags = 0
Bit 16:  [1][1][0]          -> svcClass compressed, 1 byte follows
Bit 19:  [00010010]         -> svcClass = 18 (USER_SERVER)
Bit 27:  [1][1][1][0]       -> msgType compressed, 2 bytes follow
Bit 31:  [0000001000110110] -> msgType = 566 (GET_CLIENT_VERSION_INFO)
Bit 47:  [1][0]             -> requestId compressed, 0 bytes -> 4-bit value
Bit 51:  [0001]             -> requestId = 1
Bit 53:  [1][0]             -> logCorrelator length compressed, 0 bytes -> 4-bit value
Bit 57:  [0000]             -> length = 0 (empty string)
Bit 59:  [0]                -> body is not null
Bit 60:  [1][1][0]          -> clientName length compressed, 1 byte follows
Bit 63:  [00001100]         -> length = 12 (bytes in "AmazingWorld")
Bit 71:  [0] align          -> skip to byte boundary
Bits 72–167:                -> "AmazingWorld" (12 bytes, 96 bits)

Go Implementation

Reading a message

// 1. Read framing length from buffered reader
length, err := codec.ReadLength(stream)

// 2. Read 'length' bytes into data buffer
data := make([]byte, length)
io.ReadFull(stream, data)

// 3. Create a BitReader over the raw bytes
reader := bitprotocol.NewBitReader(data)

// 4. Parse the header
header, err := gsf.ReadHeader(reader)

// 5. Look up the handler
handler, ok := router.Lookup(header.SvcClass, header.MsgType)

// 6. Build request and response objects
conn := &gsf.Connection{remoteIP: remoteAddr}
req := gsf.NewRequest(ctx, header, reader, conn)
res := gsf.NewResponse(header, writer)

// 7. Call the handler
handler(res, req)

Implementing a handler

func GetClientVersionInfo(w gsf.ResponseWriter, r *gsf.Request) error {
    // Decode the request body
    req := &messages.GetClientVersionInfoRequest{}
    if err := r.Read(req); err != nil {
        return err
    }

    // Build and send the response
    res := &messages.GetClientVersionInfoResponse{
        ClientVersionInfo: "133852.true",
    }
    return w.Write(res)
}

Defining a message type

Request and response structs implement gsf.Deserializable and gsf.Serializable:

type GetClientVersionInfoRequest struct {
    ClientName string
}

func (req *GetClientVersionInfoRequest) Deserialize(reader gsf.ProtocolReader) {
    req.ClientName = reader.ReadString()
}

type GetClientVersionInfoResponse struct {
    ClientVersionInfo string
}

func (res *GetClientVersionInfoResponse) Serialize(writer gsf.ProtocolWriter) {
    writer.WriteString(res.ClientVersionInfo)
}

Collection helpers

The gsf package provides generic helpers for encoding and decoding slices, maps, and nullable values:

// Slices: int32 length prefix (−1 = nil), then elements
gsf.ReadSlice(reader, func() MyType { ... })
gsf.WriteSlice(writer, slice, func(v MyType) { ... })

// Maps: int32 length prefix (−1 = nil), then string key + value pairs
gsf.ReadMap(reader, func() MyType { ... })
gsf.WriteMap(writer, dict, func(v MyType) { ... })

// Nullable objects: bool null flag + optional value
gsf.ReadNullable(reader, func() MyType { ... })
gsf.WriteNullable(writer, value, func(v MyType) { ... })

Game decompilation

Assembly-CSharp

The game is built using Unity engine. The latest version of the game was compiled with Unity 5.3.6f1.

Most of the game’s core logic is contained in the Assembly-CSharp.dll file.

This DLL may be decompiled, modified, and reassembled using common .NET tools such as:

  • ILSpy: primary decompiler, successfully decompiles nearly all code without issues;
  • dnSpy: used for edge cases where ILSpy fails (in this project, only a single file required it);

This should be done for educational purposes only.

Source reference

pkg.go.dev

Amazing Core keeps its detailed handler, message, and GSF object reference in Godoc comments.

Message handlers document the internal/game handler methods. Use this page to understand when the client calls a handler and what server workflow the handler coordinates.

Message payloads document internal/network/gsf/messages. Use this page for request, response, and notification payload structs. These comments should focus on the wire message and confirmed client behavior.

GSF object types document internal/network/gsf/types. Use this page for reusable objects nested inside messages, such as players, avatars, assets, items, zones, OIDs, and enum values.

Unreleased versions

Public pkg.go.dev can show an exact unreleased commit only through a Go pseudo-version.

Resolve a branch to a pseudo-version with:

go list -m github.com/dv1x3r/amazing-core@dev

Then open:

https://pkg.go.dev/github.com/dv1x3r/amazing-core@<pseudo-version>

QA Tools Menu

The game contains a hidden QA tools menu that can be enabled through ServerConfig.xml.

Enabling it

Add a Version attribute to the config:

Version = '1.8'

After launching the game with this flag enabled, press F3 to open the QA tools menu.

F2 also should open a related QA layout, but it probably requires an additional UI file.

How It Works

The Version attribute is obfuscated, and is not treated as a normal version number.

The client decodes it as an encoded options flag in GameSettings.cs:748.

  1. Parse the value as a float.
  2. Multiply it by 10.
  3. Round it to an integer.
  4. If the result is divisible by 9, accept it as a valid options code.
  5. Divide it by 10 and interpret the result as a bitmask.

Bitmask Values

The decoded bitmask controls three flags:

1 -> debuggingEnabled
2 -> skipIntro
4 -> showVersion

In practice, valid values are:

1.8 -> debug enabled
2.7 -> skip intro
3.6 -> debug enabled + skip intro
4.5 -> show version
5.4 -> debug enabled + show version
6.3 -> skip intro + show version
7.2 -> debug enabled + skip intro + show version