This project attempts to emulate the server components of GunBound Thor's Hammer, which was discontinued in 2006.
녹염만세만세만만세
Screenshots
Broker Server: Fully implemented
Game Server: Mostly implemented
0x1032
is fully functional/q
to quit from own game/close
to close entire room (self)/bcm
to broadcast a message to all sessions/tankset
to force the server to assign a specific primary tank when responding to a game start packet. This may be useful to debug the dragon/knight issue/gender
sets own gender (m/f). Changes take place after relogin/sessions
prints all active sessions/save
serializes world user state and saves it to the database/flatfile/json
prints own session data in JSON format/special_bot_chance
Sets the percentage of success when a random bot rolls for Dragon/Knight (default: 2%)/cave_map_chance
Sets the percentage of success when a random map rolls for Cave (default: 20%)/credits
thanks everyone who has contributed to this project0x4500
is not implementedBuddy Server: Not implemented
Requires Python 3.6
pip install -r requirements.txt
)coordinator.py
, replace the 192.168.1.x
address with your current IPcoordinator.py
to launch the game server and the broker server/tankset 14
or /tankset 15
after joining a room to reproduce this bug*_stage_pos.txt
and slots are picked based on the map*_stage_pos.txt
indicate only minimum and maximum x values for each spawn point. Thanks phnx 0x1012
without issue, but never requests to join the channel.CommandProcessor
, split gameserver.py
into manageable-sized chunksSoftnyx ethera knights blash45 pirania chuko scjang loserii johnny5 designer reddragon jchlee75 yaong2 jaeyong yesoori enddream cozy comsik
CTRL+F
'phnx')There are plenty of # unknown
, comments in the code, TODOs, as well as guessed packet structures. If you spot an error, or would like to add information, please open an issue or PR. Don't worry about the formatting - I'll fix it.
MIT
Game client and artwork assets belong to Softnyx
This attempts to be a comprehensive GunBound documentation (no specific scope). There might be some overlaps with my other repositories.
To start the game, these files are required as a minimum
avatar.xfs
- avatar description and statssound.xfs
- almost all sound/music used by the clientgraphics.xfs
- all images, animations and accessory files such as localization strings, "bad words" etc. If a file does not fit into the existing XFS file, it gets placed here.characterdata.dat
, itemdata.dat
, specialdata.dat
, stage.dat
- game configuration data. characterdata.dat
describes the tank's attack properties such as angle ranges and damage type. In later versions, the *.dat
files were moved into graphics.xfs
. GunBound.gme
- the main game client, a 32-bit MSVC7 binary packed with Yoda's cryptor/ASPack/ASProtect.The main GunBound.gme
client is launched via the GunBound.exe
launcher. However it is possible (and desirable) to skip the launcher for the following reasons:
To launch the client directly:
0
0
0
s. They appear to used be for a "invite-to-game" feature FAEE85F24073D9161390197F6E562A67
GunBound.gme
with the above value as its only command line parameter.
"C:\Windows\system32\NOTEPAD.EXE" C:\some_file.txt
. subprocess.run
, System.Diagnostics.Process.Start
etc. do this by default)CreateProcess
with the credentials string for the lpCommandLine
parameter get around this issue An open-source replacement launcher is available here.
Most GunBound.gme clients that I've seen are packed with ASProtect/ASPack, which can be unpacked using stripper v2.07 by syd, kiev. Look around online for stripper_v207ht
.
In some clients, very useful debug logging is included, but not enabled. If a string containing "None Debug Mode\n"
exists, the feature is most likely present. Find the xref to that string, and look for the subsequent CALL
to find the debug function.
The original code likely had #define
directives to create a logfile and store its handle, located at a static address. Thereafter, when the function is called, the client checks if the handle is valid before writing to it. This handle has to be restored for the debug logs to work again
My preferred way to enable logging is to inject a DLL into the process, conveniently after starting it using CreateProcess
with the CREATE_SUSPENDED
flag. Thereafter, CreateFile
is called and the resulting handle is "restored" into the client. This also allows arbitary file names to be used. The debug log can alternatively be routed to a console using AllocConsole
, and calling CreateFile
with "CONOUT$"
as the lpFileName
parameter. A nearby jump also has to be disabled to prevent the handle from being overwritten.
GameGuard (anti-cheat rootkit) prevents the game from launching normally, as the compatible GameGuard servers are no longer operating.
GameGuard from Softnyx's perspective
NPGameLib.h
and a static library NPGameLib.lib
to be compiled into the client, along with external supporting filesCNPGameLib
is initialized. If the GameGuard functions do not respond with NPGAMEMON_SUCCESS
, the client is expected to exit#define NPGAMEMON_SUCCESS 0x755
From our perspective, the easiest way to disable GameGuard is to:
0x755
, and invert the jump so the client always believes that GameGuard is healthy GameGuard strings are encrypted, and are only decrypted right before they are used. They can be fixed using this IDAPython script by vkiko2. The automatic detection feature does not work by default, and requires manually finding the decryption method and plugging the address in the script. Thereafter, finding the GameGuard functions is much easier, with obvious strings like '== InitNPGameMon done'
.
The above technique has worked for me on the official GIS 313 client (protocol-incompatible with Serv2), where I could successfully play a buggy round of Jewel.
XFS (Xenesis File System) is a container format which is used for most of GunBound's assets, that is (probably) preferred for its ability to compress data while keeping changes contiguous to enable efficient patching. XFS2 is a versioned filesystem, and the client will compare the container's version with the registry value during startup to determine if it is outdated.
Softnyx appears to have leaked their editor XFS2.EXE
(XFS2 응용 프로그램), and XFS2.EXE
is the preferred tool for modifying GunBound Thor's Hammer files. For browsing, an "open source" tool InsideGB
works well. There is no clear origin/author or license to it.
These are the key "Serv2" components required for full GunBound functionality:
After establishing a connection, both client and server both track of the number of bytes that they have sent and received. "Sequence" is a 16-bit value (WORD) that can be calculated from the number of sent bytes:
Server to Client, where sum_packet_length
is the total number of bytes that the server has sent the client.
(((sum_packet_length * 0x43FD) & 0xFFFF) - 0x53FD) & 0xFFFF
When generating a sequence value for sending a packet, take note that the sum_packet_length
also includes the size of the outgoing packet.
This server does not verify the client's sequence, as TCP implies that the packet order and values are already checked at a lower layer. The sequence value may be more useful in UDP, especially during GunBound's in-game P2P communication.
GunBound encrypts some packets such as channel communications, avatar shop interactions, and places where sensitive data is exchanged. The process for setting up the encryption is as follows:
0x1000
packet0x1001
, with a 4-byte nonceFFB3B3BEAE97AD83B9610E23A43C2EB0
0x1010
). Subsequent encrypted packets will use the same dynamic AES keyBecause of the way which the dynamic key is determined (during the hashing process), user passwords have to be stored in plaintext.
A modified SHA-0 hash is used when the client and server generate a shared dynamic AES key. The hash itself is SHA-0, although every DWORD in the output (5 total) is endian-flipped. AES only requires 16 bytes for a key, so the last 4 bytes are discarded. The original code used by GunBound appears to be from OpenSSL, sharing the same SHA_CTX
structure.
For secure network traffic and passing of login parameters, GunBound uses AES-128 in ECB mode. This configuration is insecure.
FAEE85F24073D9161390197F6E562A67
FFB3B3BEAE97AD83B9610E23A43C2EB0
Padding is required to align to the 16-byte boundary during block encryption. The client does not care about the padding content. In fact the client sometimes sends irrelevant neighbouring bytes which may be mistaken for meaningful data.
To find the AES block operation, travel up the xrefs from the S/S-inv box (for encryption/decryption, respectively), which can be found in its expanded form. The right function should appear as a lot of move, shift and xor operations, along with comparisons/jumps for 10/12/14 (number of rounds for AES 128/192/256).
Keys can be found by placing a breakpoint before the AES block operation, and reading the memory region of the key schedule parameter. The memory region should contain two 176-byte key schedules, one of which is reversed in blocks of 16 bytes, each used for encryption and decryption. The key is the first 16 bytes of the encryption schedule, or the last 16 bytes of the decryption schedule.
Normal
Most unencrypted packets follow this format
Packet length: 2 Bytes / WORD
Sequence: 2 Bytes / WORD
Command: 2 Bytes / WORD
Payload: 0-N Bytes, defined by packet length
RTC
Essentially a normal packet with an extra "RTC" value before the payload. This is used in room-related packets. I have no idea what "RTC" stands for; the name is simply lifted from the disassembly.
Packet length: 2 Bytes / WORD
Sequence: 2 Bytes / WORD
Command: 2 Bytes / WORD
RTC: 2 Bytes / WORD
Payload: 0-N Bytes, defined by packet length
Encrypted
There are commands that encrypt the payload of the packet (cash-updates, avatar commands, user query, game start, channel chat and in-game server commands). Depending on the command, they can either be in the style of a "Normal" or "RTC" packet.
To encrypt a packet
0x8631607E + command
TODO: describe host "key"
TODO
TODO
The Softnyx programmers appear to be big fans of Neon Genesis Evangelion (1995)
Mobile/Bot/Tanks Internally represented as a BYTE
Name | Name (KR) | ID | Remarks |
---|---|---|---|
Armor | 아머 | 0x00 (0) |
Defaults to Armor when ID is invalid |
Mage | 메이지 | 0x01 (1) |
|
Nak | 나크 | 0x02 (2) |
|
Trico | 트리코 | 0x03 (3) |
|
Bigfoot | 빅풋 | 0x04 (4) |
|
Boomer | 부머 | 0x05 (5) |
|
Raon | 레온런쳐 | 0x06 (6) |
|
Lightning | 라이트닝 | 0x07 (7) |
|
J.D. | 제이디 | 0x08 (8) |
|
A.Sate | 에세트 | 0x09 (9) |
|
Ice | 아이스 | 0x0A (10) |
|
Turtle | 터틀 | 0x0B (11) |
|
Grub | 그럽 | 0x0C (12) |
|
Aduka | 슈퍼2 ("Super 2") | 0x0D (13) |
|
Dragon | 드래곤 | 0x11 (17) |
Serv2 incorrectly sends 14 |
Knight | 나이트 | 0x12 (18) |
Serv2 incorrectly sends 15 |
Random | - | 0xFF (-1) |
Asset name is "rider" |
Ranks Internally represented as a 16-bit WORD
Name | ID |
---|---|
20 | ADMINISTRATOR |
19 | CHICK |
18 | WOODEN |
17 | DOUBLE WOODEN |
16 | STONE |
15 | DOUBLE STONE |
14 | AXE |
13 | DOUBLE AXE |
12 | SILVER AXE |
11 | DOUBLE SILVER AXE |
10 | GOLD AXE |
9 | DOUBLE GOLD AXE |
8 | BATTLE AXE |
7 | BATTLE AXE PLUS |
6 | SILVER BATTLE AXE |
5 | SILVER BATTLE AXE PLUS |
4 | GOLDEN BATTLE AXE |
3 | GOLDEN BATTLE AXE PLUS |
2 | VIOLET WAND |
1 | SAPPHIRE WAND |
0 | RUBY WAND |
-1 | DIAMOND WAND |
-2 | BLUE DRAGON |
-3 | RED DRAGON |
-4 | SILVER DRAGON |
Game Mode Internal value used to track the client's state
ID | Name |
---|---|
1 | Intro Splash? (never called) |
2 | World Select |
3 | Channel |
5 | Init3D/Evangelion failed |
7 | Avatar Shop |
9 | Room |
11 | In Game Session (Play) |
15 | Exit to Desktop |