Skip to content

Commit

Permalink
Merge branch 'release/v0.3.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
MinoMino committed Dec 4, 2015
2 parents 4db8c88 + fb917f1 commit 672ec2e
Show file tree
Hide file tree
Showing 18 changed files with 481 additions and 111 deletions.
30 changes: 30 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Compiled Object files
*.slo
*.lo
*.o
*.obj

# Precompiled Headers
*.gch
*.pch

# Compiled Dynamic libraries
*.so
*.dylib
*.dll

# Fortran module files
*.mod

# Compiled Static libraries
*.lai
*.la
*.a
*.lib

# Executables
*.exe
*.out
*.app

bin/minqlx.zip
24 changes: 10 additions & 14 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -21,44 +21,40 @@ OBJS_NOPY = $(SOURCES_NOPY:.c=.o)
OUTPUT = $(BINDIR)/minqlx.so
OUTPUT_NOPY = $(BINDIR)/minqlx_nopy.so
PYMODULE = $(BINDIR)/minqlx.zip
PYFILES = $(wildcard python/minqlx/*.py)

.PHONY: depend clean

all: CFLAGS += $(shell python3.5-config --cflags)
all: version $(OUTPUT) $(PYMODULE)
@python3.5 python/version.py -unset
all: VERSION := MINQLX_VERSION=\"$(shell python3.5 python/version.py)\"
all: $(OUTPUT) $(PYMODULE)
@echo Done!

debug: CFLAGS += $(shell python3.5-config --includes) -gdwarf-2 -Wall -O0 -fvar-tracking
debug: version_debug $(OUTPUT)
@python3.5 python/version.py -unset_debug
debug: VERSION := MINQLX_VERSION=\"$(shell python3.5 python/version.py -d)\"
debug: $(OUTPUT)
@echo Done!

nopy: CFLAGS += -Wall -DNOPY
nopy: VERSION := MINQLX_VERSION=\"$(shell git describe --long --tags --dirty --always)-nopy\"
nopy: $(OUTPUT_NOPY)
@echo Done!

nopy_debug: CFLAGS += -gdwarf-2 -Wall -O0 -DNOPY
nopy_debug: $(OUTPUT_NOPY)
@echo Done!

version:
@python3.5 python/version.py -set

version_debug:
@python3.5 python/version.py -set_debug

$(OUTPUT): $(OBJS)
$(CC) $(CFLAGS) -o $(OUTPUT) $(OBJS) $(LDFLAGS)
$(CC) $(CFLAGS) -D$(VERSION) -o $(OUTPUT) $(OBJS) $(LDFLAGS)

$(OUTPUT_NOPY): $(OBJS_NOPY)
$(CC) $(CFLAGS) -o $(OUTPUT_NOPY) $(OBJS_NOPY) $(LDFLAGS_NOPY)
$(CC) $(CFLAGS) -D$(VERSION) -o $(OUTPUT_NOPY) $(OBJS_NOPY) $(LDFLAGS_NOPY)

$(PYMODULE):
$(PYMODULE): $(PYFILES)
@python3.5 -m zipfile -c $(PYMODULE) python/minqlx

.c.o:
$(CC) $(CFLAGS) -c $< -o $@
$(CC) $(CFLAGS) -D$(VERSION) -c $< -o $@

clean:
@echo Cleaning...
Expand Down
2 changes: 2 additions & 0 deletions common.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
#ifndef COMMON_H
#define COMMON_H

#ifndef MINQLX_VERSION
#define MINQLX_VERSION "NOT_SET"
#endif

#define DEBUG_PRINT_PREFIX "[minqlx] "
#define DEBUG_ERROR_FORMAT "[minqlx] ERROR @ %s:%d in %s:\n" DEBUG_PRINT_PREFIX
Expand Down
16 changes: 16 additions & 0 deletions dllmain.c
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ SV_SetConfigstring_ptr SV_SetConfigstring;
SV_GetConfigstring_ptr SV_GetConfigstring;
SV_DropClient_ptr SV_DropClient;
Sys_SetModuleOffset_ptr Sys_SetModuleOffset;
SV_SpawnServer_ptr SV_SpawnServer;
Cmd_ExecuteString_ptr Cmd_ExecuteString;

// VM functions
G_RunFrame_ptr G_RunFrame;
Expand Down Expand Up @@ -246,6 +248,20 @@ static void SearchFunctions(void) {
}
else DebugPrint("Sys_SetModuleOffset: %p\n", Sys_SetModuleOffset);

SV_SpawnServer = (SV_SpawnServer_ptr)PatternSearchModule(&module, PTRN_SV_SPAWNSERVER, MASK_SV_SPAWNSERVER);
if (SV_SpawnServer == NULL) {
DebugPrint("ERROR: Unable to find SV_SpawnServer.\n");
failed = 1;
}
else DebugPrint("SV_SpawnServer: %p\n", SV_SpawnServer);

Cmd_ExecuteString = (Cmd_ExecuteString_ptr)PatternSearchModule(&module, PTRN_CMD_EXECUTESTRING, MASK_CMD_EXECUTESTRING);
if (Cmd_ExecuteString == NULL) {
DebugPrint("ERROR: Unable to find Cmd_ExecuteString.\n");
failed = 1;
}
else DebugPrint("Cmd_ExecuteString: %p\n", Cmd_ExecuteString);

// Cmd_Argc is really small, making it hard to search for, so we use a reference to it instead.
Cmd_Argc = (Cmd_Argc_ptr)(*(int32_t*)OFFSET_RELP_CMD_ARGC + OFFSET_RELP_CMD_ARGC + 4);

Expand Down
17 changes: 16 additions & 1 deletion hooks.c
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ void __cdecl My_G_InitGame(int levelTime, int randomSeed, int restart) {
InitializeCvars();

#ifndef NOPY
NewGameDispatcher(restart);
if (restart)
NewGameDispatcher(restart);
#endif
}

Expand Down Expand Up @@ -152,6 +153,14 @@ void __cdecl My_Com_Printf(char* fmt, ...) {
Com_Printf(buf);
}

void __cdecl My_SV_SpawnServer(char* server, qboolean killBots) {
SV_SpawnServer(server, killBots);

// We call NewGameDispatcher here instead of G_InitGame when it's not just a map_restart,
// otherwise configstring 0 and such won't be initialized and we can't instantiate minqlx.Game.
NewGameDispatcher(qfalse);
}

void __cdecl My_G_RunFrame(int time) {
// Dropping frames is probably not a good idea, so we don't allow cancelling.
FrameDispatcher();
Expand Down Expand Up @@ -235,6 +244,12 @@ void HookStatic(void) {
DebugPrint("ERROR: Failed to hook Com_Printf: %d\n", res);
failed = 1;
}

res = Hook((void*)SV_SpawnServer, My_SV_SpawnServer, (void*)&SV_SpawnServer);
if (res) {
DebugPrint("ERROR: Failed to hook SV_SpawnServer: %d\n", res);
failed = 1;
}
#endif

if (failed) {
Expand Down
4 changes: 4 additions & 0 deletions patterns.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@
#define MASK_VA "XXXX----XXXXXX-XXXX-XXXX-XXXX-XXXX-X-XXXX-XXXX-"
#define PTRN_CLIENTSPAWN "\x41\x57\x41\x56\x49\x89\xfe\x41\x55\x41\x54\x55\x53\x48\x81\xec\x00\x00\x00\x00\x4c\x8b\xbf\x00\x00\x00\x00\x64\x48\x8b\x04\x25\x00\x00\x00\x00\x48\x89\x84\x24\x00\x00\x00\x00\x31\xc0"
#define MASK_CLIENTSPAWN "XXXXXXXXXXXXXXXX----XXX----XXXXX----XXXX----XX"
#define PTRN_SV_SPAWNSERVER "\x41\x55\x41\x54\x41\x89\xf4\x55\x48\x89\xfd\x53\x48\x81\xec\x00\x00\x00\x00\x64\x48\x8b\x04\x25\x00\x00\x00\x00\x48\x89\x84\x24\x00\x00\x00\x00\x31\xc0\xe8\x00\x00\x00\x00\x31\xc0\xbf\x00\x00\x00\x00"
#define MASK_SV_SPAWNSERVER "XXXXXXXXXXXXXXX----XXXXX----XXXX----XXX----XXX----"
#define PTRN_CMD_EXECUTESTRING "\x41\x54\x49\x89\xfc\x55\x53\xe8\x00\x00\x00\x00\x44\x8b\x0d\x00\x00\x00\x00\x45\x85\xc9\x0f\x84\x00\x00\x00\x00\x48\x8b\x1d\x00\x00\x00\x00\xbd\x00\x00\x00\x00\x48\x85\xdb\x75\x00\xeb\x00\x90"
#define MASK_CMD_EXECUTESTRING "XXXXXXXX----XXX----XXXXX----XXX----X----XXXX-X-X"

// Generated by minfuncfind64. qagame functions.
#define PTRN_G_RUNFRAME "\x8b\x05\x00\x00\x00\x00\x85\xc0\x74\x00\xf3\xc3"
Expand Down
5 changes: 4 additions & 1 deletion python/minqlx/_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,10 @@ def __init__(self):

@minqlx.next_frame
def reply(self, msg, limit=100, delimiter=" "):
msg = str(msg)
# We convert whatever we got to a string and replace all double quotes
# to single quotes, since the engine doesn't support escaping them.
# TODO: rcon can print quotes to clients using NET_OutOfBandPrint. Maybe we should too?
msg = str(msg).replace("\"", "'")
# Can deal with all the below ChatChannel subclasses.
last_color = ""
targets = None
Expand Down
65 changes: 48 additions & 17 deletions python/minqlx/_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,21 @@
TEAMS = collections.OrderedDict(enumerate(("free", "red", "blue", "spectator")))

# Game type number -> string
GAMETYPES = collections.OrderedDict(enumerate(("Free for All", "Duel", "Race", "Team Deathmatch", "Clan Arena",
"Capture the Flag", "Overload", "Harvester", "Freeze Tag", "Domination", "Attack and Defend", "Red Rover")))
GAMETYPES = collections.OrderedDict([(i, gt) for i, gt in enumerate(("Free for All", "Duel", "Race", "Team Deathmatch",
"Clan Arena", "Capture the Flag", "One Flag", "", "Harvester", "Freeze Tag", "Domination", "Attack and Defend",
"Red Rover")) if gt])

# Game type number -> short string
GAMETYPES_SHORT = collections.OrderedDict(enumerate(("ffa", "duel", "race", "tdm", "ca", "ctf", "ob", "har", "ft", "dom", "ad", "rr")))
GAMETYPES_SHORT = collections.OrderedDict([(i, gt) for i, gt in enumerate(("ffa", "duel", "race", "tdm", "ca", "ctf",
"1f", "", "har", "ft", "dom", "ad", "rr")) if gt])

# Connection states.
CONNECTION_STATES = collections.OrderedDict(enumerate(("free", "zombie", "connected", "primed", "active")))

WEAPONS = collections.OrderedDict(enumerate(("_none", "g", "mg", "sg", "gl", "rl", "lg", "rg",
"pg", "bfg", "gh", "ng", "pl", "cg", "hmg", "hands")))
WEAPONS = collections.OrderedDict([(i, w) for i, w in enumerate(("", "g", "mg", "sg", "gl", "rl", "lg", "rg",
"pg", "bfg", "gh", "ng", "pl", "cg", "hmg", "hands")) if w])

DEFAULT_PLUGINS = ("plugin_manager", "essentials", "motd", "permission", "ban", "silence", "clan", "names", "log")

# ====================================================================
# HELPERS
Expand Down Expand Up @@ -184,27 +188,50 @@ def set_cvar_limit_once(name, value, minimum, maximum, flags=0):
return False

def set_plugins_version(path):
args = shlex.split("git describe --long --tags --dirty --always")
args_version = shlex.split("git describe --long --tags --dirty --always")
args_branch = shlex.split("git rev-parse --abbrev-ref HEAD")

# We keep environment variables, but remove LD_PRELOAD to avoid a warning the OS might throw.
env = dict(os.environ)
del env["LD_PRELOAD"]
try:
p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=path, env=env)
# Get the version using git describe.
p = subprocess.Popen(args_version, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=path, env=env)
p.wait(timeout=1)
if p.returncode != 0:
setattr(minqlx, "__plugins_version__", "NOT_SET")
return

version = p.stdout.read().decode().strip()

# Get the branch using git rev-parse.
p = subprocess.Popen(args_branch, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=path, env=env)
p.wait(timeout=1)
if p.returncode != 0:
setattr(minqlx, "__plugins_version__", version)
return

branch = p.stdout.read().decode().strip()
except (FileNotFoundError, subprocess.TimeoutExpired):
setattr(minqlx, "__plugins_version__", "NOT_SET")
return

setattr(minqlx, "__plugins_version__", p.stdout.read().decode().strip())
setattr(minqlx, "__plugins_version__", "{}-{}".format(version, branch))

def set_map_subtitles():
minqlx.set_configstring(678, "Running minqlx ^6{}^7 with plugins ^6{}^7."
cs = minqlx.get_configstring(678)
if cs:
cs += " - "
minqlx.set_configstring(678, cs + "Running minqlx ^6{}^7 with plugins ^6{}^7."
.format(minqlx.__version__, minqlx.__plugins_version__))
minqlx.set_configstring(679, "Check ^6http://github.com/MinoMino/minqlx^7 for more details.")
cs = minqlx.get_configstring(679)
if cs:
cs += " - "
minqlx.set_configstring(679, cs + "Check ^6http://github.com/MinoMino/minqlx^7 for more details.")

def reference_steamworks(item_id):
new_ref = minqlx.get_configstring(715) + "{} ".format(item_id)
minqlx.set_configstring(715, new_ref)

# ====================================================================
# DECORATORS
Expand Down Expand Up @@ -279,15 +306,19 @@ class PluginUnloadError(Exception):
pass

def load_preset_plugins():
plugins_cvar = minqlx.get_cvar("qlx_plugins")
plugins = minqlx.Plugin.get_cvar("qlx_plugins", set)
if "DEFAULT" in plugins:
plugins.remove("DEFAULT")
plugins.update(DEFAULT_PLUGINS)

plugins_path = os.path.abspath(minqlx.get_cvar("qlx_pluginsPath"))
plugins_dir = os.path.basename(plugins_path)

if os.path.isdir(plugins_path):
# Filter out already loaded plugins.
plugins = [p.strip() for p in plugins_cvar.split(",") if "{}.{}".format(plugins_dir, p) not in sys.modules]
for plugin in plugins:
load_plugin(plugin.strip())
plugins = [p for p in plugins if "{}.{}".format(plugins_dir, p) not in sys.modules]
for p in plugins:
load_plugin(p)
else:
raise(PluginLoadError("Cannot find the plugins directory '{}'."
.format(os.path.abspath(plugins_path))))
Expand Down Expand Up @@ -362,12 +393,12 @@ def reload_plugin(plugin):
def initialize_cvars():
# Core
minqlx.set_cvar_once("qlx_owner", "-1")
minqlx.set_cvar_once("qlx_plugins", "plugin_manager, essentials, motd, permission, ban, clan, names")
minqlx.set_cvar_once("qlx_plugins", ", ".join(DEFAULT_PLUGINS))
minqlx.set_cvar_once("qlx_pluginsPath", "minqlx-plugins")
minqlx.set_cvar_once("qlx_database", "Redis")
minqlx.set_cvar_once("qlx_commandPrefix", "!")
minqlx.set_cvar_once("qlx_logs", "5")
minqlx.set_cvar_once("qlx_logsSize", str(5*10**6)) # 5 MB
minqlx.set_cvar_once("qlx_logs", "2")
minqlx.set_cvar_once("qlx_logsSize", str(3*10**6)) # 3 MB
# Redis
minqlx.set_cvar_once("qlx_redisAddress", "127.0.0.1")
minqlx.set_cvar_once("qlx_redisDatabase", "0")
Expand Down
22 changes: 20 additions & 2 deletions python/minqlx/_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,11 +321,12 @@ class ChatEventDispatcher(EventDispatcher):
name = "chat"

def dispatch(self, player, msg, channel):
ret = super().dispatch(player, msg, channel)
ret = minqlx.COMMANDS.handle_input(player, msg, channel)
if not ret: # Stop event if told to.
return False

return minqlx.COMMANDS.handle_input(player, msg, channel)
return super().dispatch(player, msg, channel)


class UnloadDispatcher(EventDispatcher):
"""Event that triggers whenever a plugin is unloaded. Cannot be cancelled."""
Expand Down Expand Up @@ -517,6 +518,22 @@ class DeathDispatcher(EventDispatcher):
def dispatch(self, victim, killer, data):
return super().dispatch(victim, killer, data)

class UserinfoDispatcher(EventDispatcher):
"""Event for clients changing their userinfo."""
name = "userinfo"

def dispatch(self, player, changed):
return super().dispatch(player, changed)

def handle_return(self, handler, value):
"""Takes a returned dictionary and applies it to the current userinfo."""
if isinstance(value, dict):
player, changed = self.args
self.args = (player, changed)
self.return_value = changed
else:
return super().handle_return(handler, value)

EVENT_DISPATCHERS = EventDispatcherManager()
EVENT_DISPATCHERS.add_dispatcher(ConsolePrintDispatcher)
EVENT_DISPATCHERS.add_dispatcher(CommandDispatcher)
Expand Down Expand Up @@ -546,3 +563,4 @@ def dispatch(self, victim, killer, data):
EVENT_DISPATCHERS.add_dispatcher(NewGameDispatcher)
EVENT_DISPATCHERS.add_dispatcher(KillDispatcher)
EVENT_DISPATCHERS.add_dispatcher(DeathDispatcher)
EVENT_DISPATCHERS.add_dispatcher(UserinfoDispatcher)
11 changes: 11 additions & 0 deletions python/minqlx/_game.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,17 @@ def tags(self, new_tags):
else:
raise ValueError("tags need to be a string or an iterable returning strings.")

@property
def steamworks_items(self):
return minqlx.get_configstring(715).split()

@steamworks_items.setter
def steamworks_items(self, new_items):
if hasattr(new_items, "__iter__"):
minqlx.set_configstring(715, " ".join([str(i) for i in new_items]) + " ")
else:
raise ValueError("The value needs to be an iterable.")

@classmethod
def shuffle(cls):
minqlx.console_command("forceshuffle")
Expand Down
Loading

0 comments on commit 672ec2e

Please sign in to comment.