/**
* This file is part of Hercules.
* http://herc.ws - http://github.com/HerculesWS/Hercules
*
* Copyright (C) 2012-2020 Hercules Dev Team
* Copyright (C) Athena Dev Teams
*
* Hercules is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#define HERCULES_CORE
#include "config/core.h" // CONSOLE_INPUT, MAX_CONSOLE_INPUT
#include "console.h"
#include "common/atomic.h"
#include "common/cbasetypes.h"
#include "common/core.h"
#include "common/ers.h"
#include "common/memmgr.h"
#include "common/mmo.h"
#include "common/mutex.h"
#include "common/nullpo.h"
#include "common/showmsg.h"
#include "common/spinlock.h"
#include "common/sql.h"
#include "common/strlib.h"
#include "common/sysinfo.h"
#include "common/thread.h"
#include "common/timer.h"
#include
#include
#if !defined(WIN32)
# include
# include
#else
# include "common/winapi.h" // Console close event handling
# ifdef CONSOLE_INPUT
# include /* _kbhit() */
# endif
#endif
static struct console_interface console_s;
struct console_interface *console;
#ifdef CONSOLE_INPUT
static struct console_input_interface console_input_s;
static struct spin_lock console_ptlock_s;
static struct {
char queue[CONSOLE_PARSE_SIZE][MAX_CONSOLE_INPUT];
unsigned short count;
} cinput;
#endif
/*======================================
* CORE : Display title
*--------------------------------------*/
static void display_title(void)
{
const char *vcstype = sysinfo->vcstype();
ShowMessage("\n");
ShowMessage(""CL_BG_RED""CL_BT_WHITE" "CL_CLL""CL_NORMAL"\n");
ShowMessage(""CL_BG_RED""CL_BT_WHITE" Hercules Development Team presents "CL_CLL""CL_NORMAL"\n");
ShowMessage(""CL_BG_RED""CL_BT_WHITE" _ _ _ "CL_CLL""CL_NORMAL"\n");
ShowMessage(""CL_BG_RED""CL_BT_WHITE" | | | | | | "CL_CLL""CL_NORMAL"\n");
ShowMessage(""CL_BG_RED""CL_BT_WHITE" | |_| | ___ _ __ ___ _ _| | ___ ___ "CL_CLL""CL_NORMAL"\n");
ShowMessage(""CL_BG_RED""CL_BT_WHITE" | _ |/ _ \\ '__/ __| | | | |/ _ \\/ __| "CL_CLL""CL_NORMAL"\n");
ShowMessage(""CL_BG_RED""CL_BT_WHITE" | | | | __/ | | (__| |_| | | __/\\__ \\ "CL_CLL""CL_NORMAL"\n");
ShowMessage(""CL_BG_RED""CL_BT_WHITE" \\_| |_/\\___|_| \\___|\\__,_|_|\\___||___/ "CL_CLL""CL_NORMAL"\n");
ShowMessage(""CL_BG_RED""CL_BT_WHITE" "CL_CLL""CL_NORMAL"\n");
ShowMessage(""CL_BG_RED""CL_BT_WHITE" http://herc.ws/board/ "CL_CLL""CL_NORMAL"\n");
ShowMessage(""CL_BG_RED""CL_BT_WHITE" "CL_CLL""CL_NORMAL"\n");
ShowInfo("Hercules %d-bit for %s\n", sysinfo->is64bit() ? 64 : 32, sysinfo->platform());
ShowInfo("%s revision (src): '"CL_WHITE"%s"CL_RESET"'\n", vcstype, sysinfo->vcsrevision_src());
ShowInfo("%s revision (scripts): '"CL_WHITE"%s"CL_RESET"'\n", vcstype, sysinfo->vcsrevision_scripts());
ShowInfo("OS version: '"CL_WHITE"%s"CL_RESET" [%s]'\n", sysinfo->osversion(), sysinfo->arch());
ShowInfo("CPU: '"CL_WHITE"%s [%d]"CL_RESET"'\n", sysinfo->cpu(), sysinfo->cpucores());
ShowInfo("Compiled with %s\n", sysinfo->compiler());
ShowInfo("Compile Flags: %s\n", sysinfo->cflags());
ShowInfo("Timer Function Type: %s\n", sysinfo->time());
ShowInfo("Packet version: %d " PACKETTYPE "\n", PACKETVER);
}
/**
* Shows a license notice as per GNU GPL recommendation.
*/
static void display_gplnotice(void)
{
ShowInfo("Hercules, Copyright (C) 2012-2018, Hercules Dev Team and others.\n");
ShowInfo("Licensed under the GNU General Public License, version 3 or later.\n");
}
#ifdef CONSOLE_INPUT
static int console_parse_key_pressed(void)
{
#ifdef WIN32
return _kbhit();
#else // ! WIN32
struct timeval tv;
fd_set fds;
tv.tv_sec = 0;
tv.tv_usec = 0;
FD_ZERO(&fds);
FD_SET(STDIN_FILENO, &fds);
select(STDIN_FILENO+1, &fds, NULL, NULL, &tv);
return FD_ISSET(STDIN_FILENO, &fds);
#endif // WIN32
}
/*======================================
* CORE: Console commands
*--------------------------------------*/
/**
* Stops server
**/
static CPCMD_C(exit, server)
{
if (core->shutdown_callback != NULL)
core->shutdown_callback();
else
core->runflag = CORE_ST_STOP;
}
/**
* Displays ERS-related statistics (Entry Reusage System)
**/
static CPCMD_C(ers_report, server)
{
ers_report();
}
/**
* Displays memory usage
**/
static CPCMD_C(mem_report, server)
{
#ifdef USE_MEMMGR
memmgr_report(line?atoi(line):0);
#endif
}
/**
* Displays command list
**/
static CPCMD(help)
{
int i;
for (i = 0; i < VECTOR_LENGTH(console->input->command_list); i++) {
struct CParseEntry *entry = VECTOR_INDEX(console->input->command_list, i);
if (entry->type == CPET_CATEGORY) {
ShowInfo("- '"CL_WHITE"%s"CL_RESET"' subs\n", entry->cmd);
console->input->parse_list_subs(entry, 2);
} else {
ShowInfo("- '"CL_WHITE"%s"CL_RESET"'\n", entry->cmd);
}
}
}
/**
* [Ind/Hercules]
* Displays current malloc usage
*/
static CPCMD_C(malloc_usage, server)
{
unsigned int val = (unsigned int)iMalloc->usage();
ShowInfo("malloc_usage: %.2f MB\n",(double)(val)/1024);
}
/**
* Skips an sql update
* Usage: sql update skip UPDATE-FILE.sql
**/
static CPCMD_C(skip, update)
{
if( !line ) {
ShowDebug("usage example: sql update skip 2013-02-14--16-15.sql\n");
return;
}
Sql_HerculesUpdateSkip(console->input->SQL, line);
}
/**
* Loads console commands list
**/
static void console_load_defaults(void)
{
/**
* Defines a main category.
*
* Categories can't be used as commands!
* E.G.
* - sql update skip
* 'sql' is the main category
* CP_DEF_C(category)
**/
#define CP_DEF_C(x) { #x , CPET_CATEGORY, NULL , NULL, NULL }
/**
* Defines a sub-category.
*
* Sub-categories can't be used as commands!
* E.G.
* - sql update skip
* 'update' is a sub-category
* CP_DEF_C2(command, category)
**/
#define CP_DEF_C2(x,y) { #x , CPET_CATEGORY, NULL , #y, NULL }
/**
* Defines a command that is inside a category or sub-category
* CP_DEF_S(command, category/sub-category)
**/
#define CP_DEF_S(x,y) { #x, CPET_FUNCTION, CPCMD_C_A(x,y), #y, NULL }
/**
* Defines a command that is _not_ inside any category
* CP_DEF_S(command)
**/
#define CP_DEF(x) { #x , CPET_FUNCTION, CPCMD_A(x), NULL, NULL }
struct {
char *name;
int type;
CParseFunc func;
char *connect;
struct CParseEntry *self;
} default_list[] = {
CP_DEF(help),
/**
* Server related commands
**/
CP_DEF_C(server),
CP_DEF_S(ers_report,server),
CP_DEF_S(mem_report,server),
CP_DEF_S(malloc_usage,server),
CP_DEF_S(exit,server),
/**
* Sql related commands
**/
CP_DEF_C(sql),
CP_DEF_C2(update,sql),
CP_DEF_S(skip,update),
};
int len = ARRAYLENGTH(default_list);
struct CParseEntry *cmd;
int i;
VECTOR_ENSURE(console->input->commands, len, 1);
for(i = 0; i < len; i++) {
CREATE(cmd, struct CParseEntry, 1);
safestrncpy(cmd->cmd, default_list[i].name, CP_CMD_LENGTH);
cmd->type = default_list[i].type;
switch (cmd->type) {
case CPET_FUNCTION:
cmd->u.func = default_list[i].func;
break;
case CPET_CATEGORY:
VECTOR_INIT(cmd->u.children);
break;
case CPET_UNKNOWN:
break;
}
VECTOR_PUSH(console->input->commands, cmd);
default_list[i].self = cmd;
if (!default_list[i].connect) {
VECTOR_ENSURE(console->input->command_list, 1, 1);
VECTOR_PUSH(console->input->command_list, cmd);
}
}
for (i = 0; i < len; i++) {
int k;
if (!default_list[i].connect)
continue;
ARR_FIND(0, VECTOR_LENGTH(console->input->commands), k, strcmpi(default_list[i].connect, VECTOR_INDEX(console->input->commands, k)->cmd) == 0);
if (k != VECTOR_LENGTH(console->input->commands)) {
struct CParseEntry *parent = VECTOR_INDEX(console->input->commands, k);
Assert_retb(parent->type == CPET_CATEGORY);
cmd = default_list[i].self;
VECTOR_ENSURE(parent->u.children, 1, 1);
VECTOR_PUSH(parent->u.children, cmd);
}
}
#undef CP_DEF_C
#undef CP_DEF_C2
#undef CP_DEF_S
#undef CP_DEF
}
/**
* Creates a new console command entry.
*
* @param name The command name.
* @param func The command callback.
*/
static void console_parse_create(char *name, CParseFunc func)
{
int i;
char *tok;
char sublist[CP_CMD_LENGTH * 5];
struct CParseEntry *cmd;
nullpo_retv(name);
safestrncpy(sublist, name, CP_CMD_LENGTH * 5);
tok = strtok(sublist,":");
nullpo_retv(tok);
ARR_FIND(0, VECTOR_LENGTH(console->input->command_list), i, strcmpi(tok, VECTOR_INDEX(console->input->command_list, i)->cmd) == 0);
if (i == VECTOR_LENGTH(console->input->command_list)) {
CREATE(cmd, struct CParseEntry, 1);
safestrncpy(cmd->cmd, tok, CP_CMD_LENGTH);
cmd->type = CPET_UNKNOWN;
VECTOR_ENSURE(console->input->commands, 1, 1);
VECTOR_PUSH(console->input->commands, cmd);
VECTOR_ENSURE(console->input->command_list, 1, 1);
VECTOR_PUSH(console->input->command_list, cmd);
}
cmd = VECTOR_INDEX(console->input->command_list, i);
while ((tok = strtok(NULL, ":")) != NULL) {
if (cmd->type == CPET_UNKNOWN) {
cmd->type = CPET_CATEGORY;
VECTOR_INIT(cmd->u.children);
}
Assert_retv(cmd->type == CPET_CATEGORY);
ARR_FIND(0, VECTOR_LENGTH(cmd->u.children), i, strcmpi(VECTOR_INDEX(cmd->u.children, i)->cmd,tok) == 0);
if (i == VECTOR_LENGTH(cmd->u.children)) {
struct CParseEntry *entry;
CREATE(entry, struct CParseEntry, 1);
safestrncpy(entry->cmd, tok, CP_CMD_LENGTH);
entry->type = CPET_UNKNOWN;
VECTOR_ENSURE(console->input->commands, 1, 1);
VECTOR_PUSH(console->input->commands, entry);
VECTOR_ENSURE(cmd->u.children, 1, 1);
VECTOR_PUSH(cmd->u.children, entry);
cmd = entry;
continue;
}
cmd = VECTOR_INDEX(cmd->u.children, i);
}
Assert_retv(cmd->type != CPET_CATEGORY);
cmd->type = CPET_FUNCTION;
cmd->u.func = func;
}
/**
* Shows the help message for a console command category.
*
* @param cmd The command entry.
* @param depth The current tree depth (for display purposes).
*/
static void console_parse_list_subs(struct CParseEntry *cmd, unsigned char depth)
{
int i;
char msg[CP_CMD_LENGTH * 2];
nullpo_retv(cmd);
Assert_retv(cmd->type == CPET_CATEGORY);
for (i = 0; i < VECTOR_LENGTH(cmd->u.children); i++) {
struct CParseEntry *child = VECTOR_INDEX(cmd->u.children, i);
memset(msg, '-', depth);
snprintf(msg + depth, (CP_CMD_LENGTH * 2) - depth, " '"CL_WHITE"%s"CL_RESET"'", child->cmd);
if (child->type == CPET_FUNCTION) {
ShowInfo("%s\n",msg);
} else {
ShowInfo("%s subs\n",msg);
console->input->parse_list_subs(child,depth + 1);
}
}
}
/**
* Parses a console command.
*
* @param line The input line.
*/
static void console_parse_sub(char *line)
{
struct CParseEntry *cmd;
char bline[200];
char *tok;
char sublist[CP_CMD_LENGTH * 5];
int i;
nullpo_retv(line);
memcpy(bline, line, 200);
tok = strtok(line, " ");
if (tok == NULL) {
// Ignore empty commands
return;
}
ARR_FIND(0, VECTOR_LENGTH(console->input->command_list), i, strcmpi(tok, VECTOR_INDEX(console->input->command_list, i)->cmd) == 0);
if (i == VECTOR_LENGTH(console->input->command_list)) {
ShowError("'"CL_WHITE"%s"CL_RESET"' is not a known command, type '"CL_WHITE"help"CL_RESET"' to list all commands\n",line);
return;
}
cmd = VECTOR_INDEX(console->input->command_list, i);
snprintf(sublist, sizeof(sublist), "%s", cmd->cmd);
if (cmd->type == CPET_FUNCTION) {
tok = strtok(NULL, "");
if (tok != NULL) {
while (tok[0] == ' ')
tok++;
if (tok[0] == '\0')
tok = NULL;
}
cmd->u.func(tok);
return;
}
while ((tok = strtok(NULL, " ")) != NULL) {
struct CParseEntry *entry = NULL;
Assert_retv(cmd->type == CPET_CATEGORY);
ARR_FIND(0, VECTOR_LENGTH(cmd->u.children), i, strcmpi(VECTOR_INDEX(cmd->u.children, i)->cmd, tok) == 0);
if (i == VECTOR_LENGTH(cmd->u.children)) {
if (strcmpi("help", tok) == 0) {
if (VECTOR_LENGTH(cmd->u.children)) {
ShowInfo("- '"CL_WHITE"%s"CL_RESET"' subs\n",sublist);
console->input->parse_list_subs(cmd,2);
} else {
ShowError("'"CL_WHITE"%s"CL_RESET"' doesn't possess any subcommands\n",sublist);
}
return;
}
ShowError("'"CL_WHITE"%s"CL_RESET"' is not a known subcommand of '"CL_WHITE"%s"CL_RESET"'\n",tok,cmd->cmd);
ShowError("type '"CL_WHITE"%s help"CL_RESET"' to list its subcommands\n",sublist);
return;
}
entry = VECTOR_INDEX(cmd->u.children, i);
if (entry->type == CPET_FUNCTION) {
tok = strtok(NULL, "");
if (tok != NULL) {
while (tok[0] == ' ')
tok++;
if (tok[0] == '\0')
tok = NULL;
}
entry->u.func(tok);
return;
}
cmd = entry;
if (strlen(sublist) < sizeof(sublist)-1)
snprintf(sublist+strlen(sublist), sizeof(sublist), " %s", cmd->cmd);
}
ShowError("Is only a category, type '"CL_WHITE"%s help"CL_RESET"' to list its subcommands\n",sublist);
}
static void console_parse(char *line)
{
int c, i = 0, len = MAX_CONSOLE_INPUT - 1;/* we leave room for the \0 :P */
nullpo_retv(line);
while( (c = fgetc(stdin)) != EOF ) {
if( --len == 0 )
break;
if( (line[i++] = c) == '\n') {
line[i-1] = '\0';/* clear, we skip the new line */
break;/* new line~! we leave it for the next cycle */
}
}
line[i++] = '\0';
}
static void *cThread_main(void *x)
{
while( console->input->ptstate ) {/* loopx */
if( console->input->key_pressed() ) {
char input[MAX_CONSOLE_INPUT];
console->input->parse(input);
if( input[0] != '\0' ) {/* did we get something? */
EnterSpinLock(console->input->ptlock);
if( cinput.count == CONSOLE_PARSE_SIZE ) {
LeaveSpinLock(console->input->ptlock);
continue;/* drop */
}
safestrncpy(cinput.queue[cinput.count++],input,MAX_CONSOLE_INPUT);
LeaveSpinLock(console->input->ptlock);
}
}
mutex->lock(console->input->ptmutex);
mutex->cond_wait(console->input->ptcond, console->input->ptmutex, -1);
mutex->unlock(console->input->ptmutex);
}
return NULL;
}
static int console_parse_timer(int tid, int64 tick, int id, intptr_t data)
{
int i;
EnterSpinLock(console->input->ptlock);
for(i = 0; i < cinput.count; i++) {
console->input->parse_sub(cinput.queue[i]);
}
cinput.count = 0;
LeaveSpinLock(console->input->ptlock);
mutex->cond_signal(console->input->ptcond);
return 0;
}
static void console_parse_final(void)
{
if( console->input->ptstate ) {
InterlockedDecrement(&console->input->ptstate);
mutex->cond_signal(console->input->ptcond);
/* wait for thread to close */
thread->wait(console->input->pthread, NULL);
mutex->cond_destroy(console->input->ptcond);
mutex->destroy(console->input->ptmutex);
}
}
static void console_parse_init(void)
{
cinput.count = 0;
console->input->ptstate = 1;
InitializeSpinLock(console->input->ptlock);
console->input->ptmutex = mutex->create();
console->input->ptcond = mutex->cond_create();
if( (console->input->pthread = thread->create(console->input->pthread_main, NULL)) == NULL ){
ShowFatalError("console_parse_init: failed to spawn console_parse thread.\n");
exit(EXIT_FAILURE);
}
timer->add_func_list(console->input->parse_timer, "console_parse_timer");
timer->add_interval(timer->gettick() + 1000, console->input->parse_timer, 0, 0, 500);/* start listening in 1s; re-try every 0.5s */
}
static void console_setSQL(struct Sql *SQL_handle)
{
console->input->SQL = SQL_handle;
}
#endif /* CONSOLE_INPUT */
static void console_init(void)
{
#ifdef CONSOLE_INPUT
VECTOR_INIT(console->input->command_list);
VECTOR_INIT(console->input->commands);
console->input->load_defaults();
console->input->parse_init();
#endif
}
static void console_final(void)
{
#ifdef CONSOLE_INPUT
console->input->parse_final();
while (VECTOR_LENGTH(console->input->commands)) {
struct CParseEntry *entry = VECTOR_POP(console->input->commands);
if (entry->type == CPET_CATEGORY)
VECTOR_CLEAR(entry->u.children);
aFree(entry);
}
VECTOR_CLEAR(console->input->commands);
VECTOR_CLEAR(console->input->command_list);
#endif
}
void console_defaults(void)
{
console = &console_s;
console->init = console_init;
console->final = console_final;
console->display_title = display_title;
console->display_gplnotice = display_gplnotice;
#ifdef CONSOLE_INPUT
console->input = &console_input_s;
console->input->ptlock = &console_ptlock_s;
console->input->parse_init = console_parse_init;
console->input->parse_final = console_parse_final;
console->input->parse_timer = console_parse_timer;
console->input->pthread_main = cThread_main;
console->input->parse = console_parse;
console->input->parse_sub = console_parse_sub;
console->input->key_pressed = console_parse_key_pressed;
console->input->load_defaults = console_load_defaults;
console->input->parse_list_subs = console_parse_list_subs;
console->input->addCommand = console_parse_create;
console->input->setSQL = console_setSQL;
console->input->SQL = NULL;
#else
console->input = NULL;
#endif
}