wrapping up for a public release

This commit is contained in:
fusion32 2025-08-15 14:53:56 -03:00
parent 20ca1cf661
commit 7ca3cfca6e
10 changed files with 213 additions and 109 deletions

24
LICENSE.txt Normal file
View File

@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <https://unlicense.org/>

View File

@ -10,7 +10,7 @@ LFLAGS = -Wl,-t
DEBUG ?= 0
ifneq ($(DEBUG), 0)
CFLAGS += -g -O0
CFLAGS += -g -Og
else
CFLAGS += -O2
endif

15
README.md Normal file
View File

@ -0,0 +1,15 @@
# Tibia 7.7 Query Manager
This is a simple query manager designed to support [Tibia Game Server](https://github.com/fusion32/tibia-game), [Tibia Login Server](https://github.com/fusion32/tibia-login), and [Tibia Web Server](https://github.com/fusion32/tibia-web). It uses the SQLite3 3.50.2 amalgamation for a database backend so we can completely avoid having to spin up yet another service for a separate database system, effectively turning the query manager into the database itself. This design choice was made with a single game server in mind. The networking protocol is NOT encrypted at all and won't accept remote connections. For a fully multi-world distributed infrastructure, a distributed database system like PostgreSQL and MySQL/MariaDB should be considered.
## Compiling
Even though there are no Linux specific features being used, it will currently only compile on Linux. It should be simple enough to support compiling on Windows but I don't think it would add any value, considering the game server needs to run on Linux and they need to be both on the same machine. The makefile is very simple and there are **ZERO** dependencies required. You can add the `-j N` switch to make it compile across N processes.
```
make # build in release mode
make DEBUG=1 # build in debug mode
make clean # remove `build` directory
```
## Running
The query manager will automatically manage the database schema based on files in `sql/` (see `sql/README.txt`), but won't automatically insert any initial data (see `sql/init.sql`). It does have a few configuration options that are loaded from `config.cfg` but the defaults should work for most use cases.
It is recommended that the query manager is setup as a service. There is a *systemd* configuration file (`tibia-querymanager.service`) in the repository that may be used for that purpose. The process is very similar to the one described in the [Game Server](https://github.com/fusion32/tibia-game) so I won't repeat myself here.

View File

@ -8,8 +8,8 @@ HostNameExpireTime = 30m
# Connection Config
UpdateRate = 20
QueryManagerPort = 7174
QueryManagerPort = 7173
QueryManagerPassword = "a6glaf0c"
MaxConnections = 50
MaxConnectionIdleTime = 60s
MaxConnections = 25
MaxConnectionIdleTime = 5m
MaxConnectionPacketSize = 1M

103
sql/init.sql Normal file
View File

@ -0,0 +1,103 @@
-- NOTE(fusion): The query manager WON'T automatically run this but the game
-- server still requires at least the world config to be able to boot up. It
-- is probably a good idea to keep this separated from `schema.sql` and then
-- running it with `sqlite3 -echo tibia.db < sql/init.sql`, although it is
-- not mandatory.
-- Because this isn't automatically managed, all queries are wrapped in a
-- transaction to avoid partial writes in case of errors.
--==============================================================================
BEGIN;
INSERT INTO Worlds (WorldID, Name, Type, RebootTime, Host, Port, MaxPlayers,
PremiumPlayerBuffer, MaxNewbies, PremiumNewbieBuffer)
VALUES (1, 'Zanera', 0, 5, 'localhost', 7172, 1000, 100, 300, 100);
-- 111111/tibia
INSERT INTO Accounts (AccountID, Email, Auth)
VALUES (111111, '@tibia', X'206699cbc2fae1683118c873d746aa376049cb5923ef0980298bb7acbba527ec9e765668f7a338dffea34acf61a20efb654c1e9c62d35148dba2aeeef8dc7788');
INSERT INTO Characters (WorldID, CharacterID, AccountID, Name, Sex)
VALUES (1, 1, 111111, 'Gamemaster', 1), (1, 2, 111111, 'Player', 1);
INSERT INTO CharacterRights (CharacterID, Right)
VALUES
(1, 'NOTATION'),
(1, 'NAMELOCK'),
(1, 'STATEMENT_REPORT'),
(1, 'BANISHMENT'),
(1, 'FINAL_WARNING'),
(1, 'IP_BANISHMENT'),
(1, 'KICK'),
(1, 'HOME_TELEPORT'),
(1, 'GAMEMASTER_BROADCAST'),
(1, 'ANONYMOUS_BROADCAST'),
(1, 'NO_BANISHMENT'),
(1, 'ALLOW_MULTICLIENT'),
(1, 'LOG_COMMUNICATION'),
(1, 'READ_GAMEMASTER_CHANNEL'),
(1, 'READ_TUTOR_CHANNEL'),
(1, 'HIGHLIGHT_HELP_CHANNEL'),
(1, 'SEND_BUGREPORTS'),
(1, 'NAME_INSULTING'),
(1, 'NAME_SENTENCE'),
(1, 'NAME_NONSENSICAL_LETTERS'),
(1, 'NAME_BADLY_FORMATTED'),
(1, 'NAME_NO_PERSON'),
(1, 'NAME_CELEBRITY'),
(1, 'NAME_COUNTRY'),
(1, 'NAME_FAKE_IDENTITY'),
(1, 'NAME_FAKE_POSITION'),
(1, 'STATEMENT_INSULTING'),
(1, 'STATEMENT_SPAMMING'),
(1, 'STATEMENT_ADVERT_OFFTOPIC'),
(1, 'STATEMENT_ADVERT_MONEY'),
(1, 'STATEMENT_NON_ENGLISH'),
(1, 'STATEMENT_CHANNEL_OFFTOPIC'),
(1, 'STATEMENT_VIOLATION_INCITING'),
(1, 'CHEATING_BUG_ABUSE'),
(1, 'CHEATING_GAME_WEAKNESS'),
(1, 'CHEATING_MACRO_USE'),
(1, 'CHEATING_MODIFIED_CLIENT'),
(1, 'CHEATING_HACKING'),
(1, 'CHEATING_MULTI_CLIENT'),
(1, 'CHEATING_ACCOUNT_TRADING'),
(1, 'CHEATING_ACCOUNT_SHARING'),
(1, 'GAMEMASTER_THREATENING'),
(1, 'GAMEMASTER_PRETENDING'),
(1, 'GAMEMASTER_INFLUENCE'),
(1, 'GAMEMASTER_FALSE_REPORTS'),
(1, 'KILLING_EXCESSIVE_UNJUSTIFIED'),
(1, 'DESTRUCTIVE_BEHAVIOUR'),
(1, 'SPOILING_AUCTION'),
(1, 'INVALID_PAYMENT'),
(1, 'TELEPORT_TO_CHARACTER'),
(1, 'TELEPORT_TO_MARK'),
(1, 'TELEPORT_VERTICAL'),
(1, 'TELEPORT_TO_COORDINATE'),
(1, 'LEVITATE'),
(1, 'SPECIAL_MOVEUSE'),
(1, 'MODIFY_GOSTRENGTH'),
(1, 'SHOW_COORDINATE'),
(1, 'RETRIEVE'),
(1, 'ENTER_HOUSES'),
(1, 'OPEN_NAMEDOORS'),
(1, 'INVULNERABLE'),
(1, 'UNLIMITED_MANA'),
(1, 'KEEP_INVENTORY'),
(1, 'ALL_SPELLS'),
(1, 'UNLIMITED_CAPACITY'),
(1, 'ATTACK_EVERYWHERE'),
(1, 'NO_LOGOUT_BLOCK'),
(1, 'GAMEMASTER_OUTFIT'),
(1, 'ILLUMINATE'),
(1, 'CHANGE_PROFESSION'),
(1, 'IGNORED_BY_MONSTERS'),
(1, 'SHOW_KEYHOLE_NUMBERS'),
(1, 'CREATE_OBJECTS'),
(1, 'CREATE_MONEY'),
(1, 'CREATE_MONSTERS'),
(1, 'CHANGE_SKILLS'),
(1, 'CLEANUP_FIELDS'),
(1, 'NO_STATISTICS');
COMMIT;

View File

@ -54,6 +54,16 @@ CREATE INDEX IF NOT EXISTS CharactersAccountIndex
CREATE INDEX IF NOT EXISTS CharactersGuildIndex
ON Characters(Guild, Rank);
/*
-- TODO(fusion): Have group rights instead of adding individual rights to characters?
ALTER TABLE Characters ADD GroupID INTEGER NOT NULL;
CREATE TABLE IF NOT EXISTS CharacterRights (
GroupID INTEGER NOT NULL,
Right TEXT NOT NULL COLLATE NOCASE,
PRIMARY KEY(GroupID, Right)
);
*/
CREATE TABLE IF NOT EXISTS CharacterRights (
CharacterID INTEGER NOT NULL,
Right TEXT NOT NULL COLLATE NOCASE,
@ -262,107 +272,3 @@ CREATE TABLE IF NOT EXISTS OnlineCharacters (
Profession TEXT NOT NULL,
PRIMARY KEY (WorldID, Name)
);
-- REMOVE(fusion): Testing Data.
--==============================================================================
INSERT INTO Worlds (WorldID, Name, Type, RebootTime, Host, Port, MaxPlayers,
PremiumPlayerBuffer, MaxNewbies, PremiumNewbieBuffer)
VALUES (1, 'Zanera', 0, 5, 'localhost', 7172, 1000, 100, 300, 100);
-- 111111/tibia
INSERT INTO Accounts (AccountID, Email, Auth)
VALUES (111111, '@tibia', X'206699cbc2fae1683118c873d746aa376049cb5923ef0980298bb7acbba527ec9e765668f7a338dffea34acf61a20efb654c1e9c62d35148dba2aeeef8dc7788');
INSERT INTO Characters (WorldID, CharacterID, AccountID, Name, Sex)
VALUES (1, 1, 111111, 'Gamemaster', 1), (1, 2, 111111, 'Player', 1);
/*
-- TODO(fusion): Have group rights instead of adding individual rights to characters?
ALTER TABLE Characters ADD GroupID INTEGER NOT NULL;
CREATE TABLE IF NOT EXISTS CharacterRights (
GroupID INTEGER NOT NULL,
Right TEXT NOT NULL COLLATE NOCASE,
PRIMARY KEY(GroupID, Right)
);
*/
INSERT INTO CharacterRights (CharacterID, Right)
VALUES
(1, 'NOTATION'),
(1, 'NAMELOCK'),
(1, 'STATEMENT_REPORT'),
(1, 'BANISHMENT'),
(1, 'FINAL_WARNING'),
(1, 'IP_BANISHMENT'),
(1, 'KICK'),
(1, 'HOME_TELEPORT'),
(1, 'GAMEMASTER_BROADCAST'),
(1, 'ANONYMOUS_BROADCAST'),
(1, 'NO_BANISHMENT'),
(1, 'ALLOW_MULTICLIENT'),
(1, 'LOG_COMMUNICATION'),
(1, 'READ_GAMEMASTER_CHANNEL'),
(1, 'READ_TUTOR_CHANNEL'),
(1, 'HIGHLIGHT_HELP_CHANNEL'),
(1, 'SEND_BUGREPORTS'),
(1, 'NAME_INSULTING'),
(1, 'NAME_SENTENCE'),
(1, 'NAME_NONSENSICAL_LETTERS'),
(1, 'NAME_BADLY_FORMATTED'),
(1, 'NAME_NO_PERSON'),
(1, 'NAME_CELEBRITY'),
(1, 'NAME_COUNTRY'),
(1, 'NAME_FAKE_IDENTITY'),
(1, 'NAME_FAKE_POSITION'),
(1, 'STATEMENT_INSULTING'),
(1, 'STATEMENT_SPAMMING'),
(1, 'STATEMENT_ADVERT_OFFTOPIC'),
(1, 'STATEMENT_ADVERT_MONEY'),
(1, 'STATEMENT_NON_ENGLISH'),
(1, 'STATEMENT_CHANNEL_OFFTOPIC'),
(1, 'STATEMENT_VIOLATION_INCITING'),
(1, 'CHEATING_BUG_ABUSE'),
(1, 'CHEATING_GAME_WEAKNESS'),
(1, 'CHEATING_MACRO_USE'),
(1, 'CHEATING_MODIFIED_CLIENT'),
(1, 'CHEATING_HACKING'),
(1, 'CHEATING_MULTI_CLIENT'),
(1, 'CHEATING_ACCOUNT_TRADING'),
(1, 'CHEATING_ACCOUNT_SHARING'),
(1, 'GAMEMASTER_THREATENING'),
(1, 'GAMEMASTER_PRETENDING'),
(1, 'GAMEMASTER_INFLUENCE'),
(1, 'GAMEMASTER_FALSE_REPORTS'),
(1, 'KILLING_EXCESSIVE_UNJUSTIFIED'),
(1, 'DESTRUCTIVE_BEHAVIOUR'),
(1, 'SPOILING_AUCTION'),
(1, 'INVALID_PAYMENT'),
(1, 'TELEPORT_TO_CHARACTER'),
(1, 'TELEPORT_TO_MARK'),
(1, 'TELEPORT_VERTICAL'),
(1, 'TELEPORT_TO_COORDINATE'),
(1, 'LEVITATE'),
(1, 'SPECIAL_MOVEUSE'),
(1, 'MODIFY_GOSTRENGTH'),
(1, 'SHOW_COORDINATE'),
(1, 'RETRIEVE'),
(1, 'ENTER_HOUSES'),
(1, 'OPEN_NAMEDOORS'),
(1, 'INVULNERABLE'),
(1, 'UNLIMITED_MANA'),
(1, 'KEEP_INVENTORY'),
(1, 'ALL_SPELLS'),
(1, 'UNLIMITED_CAPACITY'),
(1, 'ATTACK_EVERYWHERE'),
(1, 'NO_LOGOUT_BLOCK'),
(1, 'GAMEMASTER_OUTFIT'),
(1, 'ILLUMINATE'),
(1, 'CHANGE_PROFESSION'),
(1, 'IGNORED_BY_MONSTERS'),
(1, 'SHOW_KEYHOLE_NUMBERS'),
(1, 'CREATE_OBJECTS'),
(1, 'CREATE_MONEY'),
(1, 'CREATE_MONSTERS'),
(1, 'CHANGE_SKILLS'),
(1, 'CLEANUP_FIELDS'),
(1, 'NO_STATISTICS');

View File

@ -2042,7 +2042,7 @@ bool MergeKillStatistics(int WorldID, int NumStats, TKillStatistics *Stats){
"INSERT INTO KillStatistics (WorldID, RaceName, TimesKilled, PlayersKilled)"
" VALUES (?1, ?2, ?3, ?4)"
" ON CONFLICT DO UPDATE SET TimesKilled = TimesKilled + Excluded.TimesKilled,"
" PlayersKilled = PlayersKilled + Excluded.TimesKilled");
" PlayersKilled = PlayersKilled + Excluded.PlayersKilled");
if(Stmt == NULL){
LOG_ERR("Failed to prepare query");
return false;

View File

@ -44,6 +44,7 @@ void LogAdd(const char *Prefix, const char *Format, ...){
LocalTime.tm_year + 1900, LocalTime.tm_mon + 1, LocalTime.tm_mday,
LocalTime.tm_hour, LocalTime.tm_min, LocalTime.tm_sec,
Prefix, Entry);
fflush(stdout);
}
}
@ -63,6 +64,7 @@ void LogAddVerbose(const char *Prefix, const char *Function,
LocalTime.tm_year + 1900, LocalTime.tm_mon + 1, LocalTime.tm_mday,
LocalTime.tm_hour, LocalTime.tm_min, LocalTime.tm_sec,
Prefix, Function, Entry);
fflush(stdout);
}
}

View File

@ -0,0 +1,21 @@
# Basic SYSTEMD service file for the Tibia Query Manager
[Unit]
Description=Tibia Query Manager
After=network.target
[Install]
WantedBy=multi-user.target
[Service]
Type=simple
User=tibia-querymanager
Group=tibia-querymanager
ExecStart=/opt/tibia/querymanager/querymanager
WorkingDirectory=/opt/tibia/querymanager/
Restart=always
RestartSec=10
LimitCORE=infinity
StandardOutput=journal
StandardError=journal
SyslogIdentifier=%n

33
tools/pwdhash.go Normal file
View File

@ -0,0 +1,33 @@
package main
import (
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"fmt"
"os"
)
func main() {
if len(os.Args) <= 1 {
fmt.Printf("usage: pwdhash PASSWORD\n")
return
}
fmt.Printf("password = \"%v\"\n", os.Args[1])
var salt [32]byte
if n, err := rand.Read(salt[:]); err != nil || n != 32 {
fmt.Printf("Failed to generate salt: %v\n", err)
}
secret := sha256.Sum256([]byte(os.Args[1]))
for i := 0; i < 32; i += 1 {
secret[i] ^= salt[i]
}
pwdhash := sha256.Sum256(secret[:])
fmt.Printf("pwdhash = %v\n", hex.EncodeToString(pwdhash[:]))
fmt.Printf("salt = %v\n", hex.EncodeToString(salt[:]))
}