Removed dependency on Kirby from the game server. Started file API.

This commit is contained in:
Scott Duensing 2022-02-08 21:07:18 -06:00
parent 26938e5aaf
commit 409c3b4cfb
49 changed files with 831 additions and 1336 deletions

View file

@ -58,7 +58,7 @@ ALL_LDLIBS := -lc
# Source, Binaries, Dependencies
SRC := $(shell find $(SRCDIR) -type f -name '*.c' | grep -v '/linux/' | grep -v '/server/' | grep -v '/primes/' | grep -v '/font/' | grep -v '/retired/' | grep -v '/test/')
SRC := $(shell find $(SRCDIR) -type f -name '*.c' | grep -v '/linux/' | grep -v '/server/' | grep -v '/primes/' | grep -v '/font/' | grep -v '/precache/' | grep -v '/retired/' | grep -v '/test/')
OBJ := $(patsubst $(SRCDIR)/%,$(OBJDIR)/%,$(SRC:.c=.o))
DEP := $(OBJ:.o=.d)
BIN := $(BINDIR)/$(TARGET)

View file

@ -23,11 +23,10 @@
# See: https://github.com/andrewwutw/build-djgpp
mkdir -p \
bin/data \
obj/client/src/system \
obj/client/src/dos \
obj/client/src/gui \
obj/client/src/embedded \
obj/client/src/thirdparty/SHA256 \
obj/client/src/thirdparty/serial \
obj/shared/thirdparty/memwatch \
obj/shared/thirdparty/blowfish-api \
@ -41,4 +40,4 @@ make -f Makefile.djgpp
rm bin/client
cp client/data/* bin/data/.
cp precache/out/* bin/.

View file

@ -60,7 +60,7 @@ void linuxOsStartup(void);
#else
#define BITS32
#define PATH_SLASH '\\'
#define OS_PATH_SLASH '\\'
// DOS includes.
#include <dos.h>

View file

@ -1,23 +0,0 @@
<?php
function kpApiConfigGetConfig(&$response) {
$response['payload'] = collection('kwconfig');
$response['result'] = 'true';
$response['reason'] = 'Configuration entries returned.';
}
function kpApiConfigGetNumbers(&$response) {
$response['payload'] = collection('kwnumbers');
$response['result'] = 'true';
$response['reason'] = 'Number entries returned.';
}
function kpApiConfigGetStrings(&$response) {
$response['payload'] = collection('kwstrings');
$response['result'] = 'true';
$response['reason'] = 'String entries returned.';
}
?>

View file

@ -1,11 +0,0 @@
<?php
function kpApiTest(&$response) {
$response['payload'] = collection('kwstrings');
$response['result'] = 'true';
$response['reason'] = 'Dataset returned.';
}
?>

View file

@ -37,6 +37,7 @@ function kpApiUserCreate($first, $last, $username, $email, $password, &$response
]);
// Save SQL attributes.
// ***TODO***
// ***TODO*** Intercept password change and user enable/disable to update the DB.
// Return result.
$response['result'] = 'true';
@ -51,26 +52,4 @@ function kpApiUserCreate($first, $last, $username, $email, $password, &$response
}
}
function kpApiUserLogin($username, $pass, &$response) {
try {
// Find user by name instead of email.
$user = kirby()->users()->filterBy('name', $username)->first();
if ($user) {
// ***TODO*** Must be activated!
// Attempt to sign them in.
kirby()->auth()->login($user->email(), $pass);
// They don't need an actual Kirby session, so log them off.
kirby()->auth()->logout();
// Report it.
$response['result'] = 'true';
$response['reason'] = 'User logged in.';
} else {
$response['reason'] = 'User name or password incorrect.';
}
} catch(Exception $e) {
$response['reason'] = 'User name or password incorrect.';
}
}
?>

View file

@ -1,50 +0,0 @@
<?php
namespace KangarooPunch;
use \PDO;
class KPunch {
public static function databaseGet() {
$db = null;
try {
$host = option('kangaroopunch.kangaworld-integration.sql.host');
$port = option('kangaroopunch.kangaworld-integration.sql.port');
$data = option('kangaroopunch.kangaworld-integration.sql.data');
$user = option('kangaroopunch.kangaworld-integration.sql.user');
$pass = option('kangaroopunch.kangaworld-integration.sql.pass');
$db = new PDO("mysql:host=$host;port=$port;dbname=$data", $user, $pass);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch(PDOException $ex) {
$db = null;
}
return $db;
}
public static function databaseQueryToJSON($db, $query, $options = null) {
$result = null;
if ($db) {
$statement = $db->prepare($query);
$statement->execute($options);
$resultset = array();
while ($row = $statement->fetch(PDO::FETCH_ASSOC)) {
$resultset[] = $row;
}
$result = json_encode($resultset);
}
return $result;
}
}
?>

View file

@ -1,90 +0,0 @@
<?php
namespace KangarooPunch;
use KangarooPunch\KPunch;
use Kirby\Exception\InvalidArgumentException;
use Kirby\Exception\NotFoundException;
use Kirby\Toolkit\V;
class KwConfig {
public static function create(array $input): bool {
validate($input);
$db = KPunch::databaseGet();
KPunch::databaseQueryToJSON($db,
'INSERT INTO config (name, data, description) VALUES (?, ?, ?)',
array(
$input['name'],
$input['data'],
$input['description']
)
);
return true;
}
public static function delete(string $id): bool {
$db = KPunch::databaseGet();
KPunch::databaseQueryToJSON($db, 'DELETE FROM config WHERE id = ?', array($id));
return true;
}
public static function find(string $id): array {
$db = KPunch::databaseGet();
$result = KPunch::databaseQueryToJSON($db, 'SELECT * FROM config WHERE id = ?', array($id));
if ($result == null) {
throw new NotFoundException('The entry could not be found');
}
return json_decode($result);
}
public static function list(): array {
$db = KPunch::databaseGet();
return json_decode(KPunch::databaseQueryToJSON($db, 'SELECT * FROM config'));
}
public static function update(array $input): bool {
validate($input);
$db = KPunch::databaseGet();
KPunch::databaseQueryToJSON($db,
'UPDATE config SET name = ?, data = ?, description = ? WHERE id = ?',
array(
$input['name'],
$input['data'],
$input['description'],
$input['id']
)
);
return true;
}
private static function validate($input) {
if (V::minlength($input['name'], 1) === false) {
throw new InvalidArgumentException('The name must not be empty');
}
if (V::maxlength($input['name'], 32) === false) {
throw new InvalidArgumentException('The name must not be longer than 32 characters');
}
if (V::minlength($input['data'], 1) === false) {
throw new InvalidArgumentException('The data must not be empty');
}
if (V::maxlength($input['data'], 1024) === false) {
throw new InvalidArgumentException('The data must not be longer than 1024 characters');
}
if (V::maxlength($input['description'], 255) === false) {
throw new InvalidArgumentException('The description must not be longer than 255 characters');
}
}
}
?>

View file

@ -1,83 +0,0 @@
<?php
namespace KangarooPunch;
use KangarooPunch\KPunch;
use Kirby\Exception\InvalidArgumentException;
use Kirby\Exception\NotFoundException;
use Kirby\Toolkit\V;
class KwNumbers {
public static function create(array $input): bool {
validate($input);
$db = KPunch::databaseGet();
KPunch::databaseQueryToJSON($db,
'INSERT INTO numbers (name, data, description) VALUES (?, ?, ?)',
array(
$input['name'],
$input['data'],
$input['description']
)
);
return true;
}
public static function delete(string $id): bool {
$db = KPunch::databaseGet();
KPunch::databaseQueryToJSON($db, 'DELETE FROM numbers WHERE id = ?', array($id));
return true;
}
public static function find(string $id): array {
$db = KPunch::databaseGet();
$result = KPunch::databaseQueryToJSON($db, 'SELECT * FROM numbers WHERE id = ?', array($id));
if ($result == null) {
throw new NotFoundException('The entry could not be found');
}
return json_decode($result);
}
public static function list(): array {
$db = KPunch::databaseGet();
return json_decode(KPunch::databaseQueryToJSON($db, 'SELECT * FROM numbers'));
}
public static function update(array $input): bool {
validate($input);
$db = KPunch::databaseGet();
KPunch::databaseQueryToJSON($db,
'UPDATE numbers SET name = ?, data = ?, description = ? WHERE id = ?',
array(
$input['name'],
$input['data'],
$input['description'],
$input['id']
)
);
return true;
}
private static function validate($input) {
if (V::minlength($input['name'], 1) === false) {
throw new InvalidArgumentException('The name must not be empty');
}
if (V::maxlength($input['name'], 32) === false) {
throw new InvalidArgumentException('The name must not be longer than 32 characters');
}
if (V::maxlength($input['description'], 255) === false) {
throw new InvalidArgumentException('The description must not be longer than 255 characters');
}
}
}
?>

View file

@ -1,90 +0,0 @@
<?php
namespace KangarooPunch;
use KangarooPunch\KPunch;
use Kirby\Exception\InvalidArgumentException;
use Kirby\Exception\NotFoundException;
use Kirby\Toolkit\V;
class KwStrings {
public static function create(array $input): bool {
validate($input);
$db = KPunch::databaseGet();
KPunch::databaseQueryToJSON($db,
'INSERT INTO strings (name, data, description) VALUES (?, ?, ?)',
array(
$input['name'],
$input['data'],
$input['description']
)
);
return true;
}
public static function delete(string $id): bool {
$db = KPunch::databaseGet();
KPunch::databaseQueryToJSON($db, 'DELETE FROM strings WHERE id = ?', array($id));
return true;
}
public static function find(string $id): array {
$db = KPunch::databaseGet();
$result = KPunch::databaseQueryToJSON($db, 'SELECT * FROM strings WHERE id = ?', array($id));
if ($result == null) {
throw new NotFoundException('The entry could not be found');
}
return json_decode($result);
}
public static function list(): array {
$db = KPunch::databaseGet();
return json_decode(KPunch::databaseQueryToJSON($db, 'SELECT * FROM strings'));
}
public static function update(array $input): bool {
validate($input);
$db = KPunch::databaseGet();
KPunch::databaseQueryToJSON($db,
'UPDATE strings SET name = ?, data = ?, description = ? WHERE id = ?',
array(
$input['name'],
$input['data'],
$input['description'],
$input['id']
)
);
return true;
}
private static function validate($input) {
if (V::minlength($input['name'], 1) === false) {
throw new InvalidArgumentException('The name must not be empty');
}
if (V::maxlength($input['name'], 32) === false) {
throw new InvalidArgumentException('The name must not be longer than 32 characters');
}
if (V::minlength($input['data'], 1) === false) {
throw new InvalidArgumentException('The data must not be empty');
}
if (V::maxlength($input['data'], 1024) === false) {
throw new InvalidArgumentException('The data must not be longer than 1024 characters');
}
if (V::maxlength($input['description'], 255) === false) {
throw new InvalidArgumentException('The description must not be longer than 255 characters');
}
}
}
?>

View file

@ -1,22 +0,0 @@
<?php
use KangarooPunch\KwConfig;
return [
'pattern' => 'kwconfig/create',
'load' => function () {
return [
'component' => 'k-form-dialog',
'props' => [
'fields' => require __DIR__ . '/fields.php',
'submitButton' => t('create'),
]
];
},
'submit' => function () {
return KwConfig::create(get());
}
];
?>

View file

@ -1,20 +0,0 @@
<?php
use KangarooPunch\KwConfig;
return [
'pattern' => 'kwconfig/(:any)/delete',
'load' => function (string $id) {
return [
'component' => 'k-remove-dialog',
'props' => [
'text' => 'Do you really want to delete this entry?'
]
];
},
'submit' => function (string $id) {
return KwConfig::delete($id);
}
];
?>

View file

@ -1,31 +0,0 @@
<?php
use KangarooPunch\KwConfig;
use Kirby\Toolkit\A;
return [
'name' => [
'label' => 'Name',
'type' => 'text'
],
'type' => [
'label' => 'Type',
'type' => 'select',
'empty' => false,
'width' => '1/2',
'options' => A::map(KwConfig::types(), function ($type) {
return ['value' => $type, 'text' => $type];
})
],
'description' => [
'label' => 'Description',
'type' => 'textarea',
'buttons' => false
],
'data' => [
'label' => 'Value',
'type' => 'text'
]
];
?>

View file

@ -1,23 +0,0 @@
<?php
use KangarooPunch\KwConfig;
return [
'pattern' => 'kwconfig/(:any)/update',
'load' => function (string $id) {
$kwconfig = KwConfig::find($id);
return [
'component' => 'k-form-dialog',
'props' => [
'fields' => require __DIR__ . '/fields.php',
'value' => $kwconfig
]
];
},
'submit' => function (string $id) {
return KwConfig::update(get());
}
];
?>

View file

@ -1,21 +0,0 @@
<?php
return [
'pattern' => 'kwconfig/(:any)',
'action' => function (string $id) {
return [
[
'text' => 'Edit',
'icon' => 'edit',
'dialog' => 'kwconfig/' . $id . '/update'
],
[
'text' => 'Delete',
'icon' => 'trash',
'dialog' => 'kwconfig/' . $id . '/delete'
]
];
}
];
?>

View file

@ -1,7 +1,5 @@
<?php
require __DIR__ . '/../api/config.php';
require __DIR__ . '/../api/test.php';
require __DIR__ . '/../api/user.php';
@ -24,34 +22,14 @@ return [
switch (get('command')) {
case 'API_AVAILABLE':
$response['result'] = 'true';
$response['result'] = 'true';
$response['reason'] = 'API Available.';
break;
case 'API_TEST':
kpApiTest($response);
break;
case 'CONFIG_GET_CONFIG':
kpApiConfigGetConfig($response);
break;
case 'CONFIG_GET_NUMBERS':
kpApiConfigGetNumbers($response);
break;
case 'CONFIG_GET_STRINGS':
kpApiConfigGetStrings($response);
break;
case 'USER_CREATE':
kpApiUserCreate(get('first'), get('last'), get('user'), get('email'), get('pass'), $response);
break;
case 'USER_LOGIN':
kpApiUserLogin(get('user'), get('pass'), $response);
break;
default:
$response['reason'] = 'Invalid command.';
break;

View file

@ -1,32 +0,0 @@
<?php
return [
'kwconfig' => [
'label' => 'Kanga World Configuration',
'icon' => 'globe',
'menu' => true,
// update and delete dialogs
'dialogs' => [
require __DIR__ . '/../dialogs/create.php',
require __DIR__ . '/../dialogs/update.php',
require __DIR__ . '/../dialogs/delete.php'
],
// dropdown with edit and delete buttons
'dropdowns' => [
require __DIR__ . '/../dropdowns/kwconfig.php'
],
// search for settings
'searches' => [
'kwconfig' => require __DIR__ . '/../searches/kwconfig.php'
],
// view route
'views' => [
require __DIR__ . '/../views/kwconfig.php',
require __DIR__ . '/../views/kwentry.php'
],
]
];
?>

View file

@ -1,21 +0,0 @@
<?php
use KangarooPunch\KwConfig;
use KangarooPunch\KwNumbers;
use KangarooPunch\KwStrings;
return [
'kwconfig' => function($site) {
return new Collection(KwConfig::list());
},
'kwnumbers' => function($site) {
return new Collection(KwNumbers::list());
},
'kwstrings' => function($site) {
return new Collection(KwStrings::list());
}
];
?>

View file

@ -1,13 +0,0 @@
<?php
return [
'sql' => [
'host' => 'mysql',
'port' => 3306,
'data' => 'dosThing',
'user' => 'dosThing',
'pass' => 'password'
]
];
?>

View file

@ -1,43 +0,0 @@
.k-kwconfig {
width: 100%;
table-layout: fixed;
border-spacing: 1px;
}
.k-kwconfig td,
.k-kwconfig th {
text-align: left;
font-size: var(--text-sm);
padding: var(--spacing-2);
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
background: var(--color-white);
}
.k-kwconfig th {
font-weight: var(--font-bold);
}
.k-kwconfig th button {
font: inherit;
text-align: left;
width: 100%;
}
.k-kwconfig-name {
/* width: 8rem; */
}
.k-kwconfig-type {
/* width: 8rem; */
}
.k-kwconfig-description {
width: 50%;
}
.k-kwconfig-data {
/* width: 10rem; */
font-variant-numeric: tabular-nums;
text-align: right !important;
}
.k-kwconfig-options {
padding: 0 !important;
width: 3rem;
overflow: visible !important;
}

View file

@ -1,145 +0,0 @@
(function() {
"use strict";
var render$1 = function() {
var _vm = this;
var _h = _vm.$createElement;
var _c = _vm._self._c || _h;
return _c("k-inside", [_c("k-view", [_c("k-header", [_vm._v(" Kanga World Configuration "), _c("k-button-group", { attrs: { "slot": "right" }, slot: "right" }, [_c("k-button", { attrs: { "text": "New Entry", "icon": "add" }, on: { "click": function($event) {
return _vm.$dialog("kwconfig/create");
} } })], 1)], 1), _c("table", { staticClass: "k-kwconfig" }, [_c("tr", [_c("th", { staticClass: "k-kwconfig-name" }, [_c("button", { on: { "click": function($event) {
return _vm.sortBy("name");
} } }, [_vm._v(" Name "), _vm.sort === "name" ? _c("span", { domProps: { "innerHTML": _vm._s(_vm.sortArrow) } }) : _vm._e()])]), _c("th", { staticClass: "k-kwconfig-type" }, [_c("button", { on: { "click": function($event) {
return _vm.sortBy("type");
} } }, [_vm._v(" Type "), _vm.sort === "type" ? _c("span", { domProps: { "innerHTML": _vm._s(_vm.sortArrow) } }) : _vm._e()])]), _c("th", { staticClass: "k-kwconfig-description" }, [_c("button", { on: { "click": function($event) {
return _vm.sortBy("description");
} } }, [_vm._v(" Description "), _vm.sort === "description" ? _c("span", { domProps: { "innerHTML": _vm._s(_vm.sortArrow) } }) : _vm._e()])]), _c("th", { staticClass: "k-kwconfig-data" }, [_c("button", { on: { "click": function($event) {
return _vm.sortBy("data");
} } }, [_vm._v(" Value "), _vm.sort === "data" ? _c("span", { domProps: { "innerHTML": _vm._s(_vm.sortArrow) } }) : _vm._e()])]), _c("th", { staticClass: "k-kwconfig-options" })]), _vm._l(_vm.kwconfig, function(kwconfig, name) {
return _c("tr", { key: name }, [_c("td", { staticClass: "k-kwconfig-name" }, [_vm._v(_vm._s(kwconfig.name))]), _c("td", { staticClass: "k-kwconfig-type" }, [_vm._v(_vm._s(kwconfig.type))]), _c("td", { staticClass: "k-kwconfig-description" }, [_vm._v(_vm._s(kwconfig.description))]), _c("td", { staticClass: "k-kwconfig-data" }, [_vm._v(_vm._s(kwconfig.data))]), _c("td", { staticClass: "k-kwconfig-options" }, [_c("k-options-dropdown", { attrs: { "options": "kwconfig/" + name } })], 1)]);
})], 2)], 1)], 1);
};
var staticRenderFns$1 = [];
render$1._withStripped = true;
var KwConfig_vue_vue_type_style_index_0_lang = "";
function normalizeComponent(scriptExports, render2, staticRenderFns2, functionalTemplate, injectStyles, scopeId, moduleIdentifier, shadowMode) {
var options = typeof scriptExports === "function" ? scriptExports.options : scriptExports;
if (render2) {
options.render = render2;
options.staticRenderFns = staticRenderFns2;
options._compiled = true;
}
if (functionalTemplate) {
options.functional = true;
}
if (scopeId) {
options._scopeId = "data-v-" + scopeId;
}
var hook;
if (moduleIdentifier) {
hook = function(context) {
context = context || this.$vnode && this.$vnode.ssrContext || this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext;
if (!context && typeof __VUE_SSR_CONTEXT__ !== "undefined") {
context = __VUE_SSR_CONTEXT__;
}
if (injectStyles) {
injectStyles.call(this, context);
}
if (context && context._registeredComponents) {
context._registeredComponents.add(moduleIdentifier);
}
};
options._ssrRegister = hook;
} else if (injectStyles) {
hook = shadowMode ? function() {
injectStyles.call(this, (options.functional ? this.parent : this).$root.$options.shadowRoot);
} : injectStyles;
}
if (hook) {
if (options.functional) {
options._injectStyles = hook;
var originalRender = options.render;
options.render = function renderWithStyleInjection(h, context) {
hook.call(context);
return originalRender(h, context);
};
} else {
var existing = options.beforeCreate;
options.beforeCreate = existing ? [].concat(existing, hook) : [hook];
}
}
return {
exports: scriptExports,
options
};
}
const __vue2_script$1 = {
props: {
dir: String,
sort: String,
kwconfig: Object
},
computed: {
sortArrow() {
return this.dir === "asc" ? "&darr;" : "&uarr;";
}
},
methods: {
sortBy(sort) {
let dir = "asc";
if (sort === this.sort)
dir = this.dir === "asc" ? "desc" : "asc";
this.$reload({
query: {
dir,
sort
}
});
}
}
};
const __cssModules$1 = {};
var __component__$1 = /* @__PURE__ */ normalizeComponent(__vue2_script$1, render$1, staticRenderFns$1, false, __vue2_injectStyles$1, null, null, null);
function __vue2_injectStyles$1(context) {
for (let o in __cssModules$1) {
this[o] = __cssModules$1[o];
}
}
__component__$1.options.__file = "src/components/KwConfig.vue";
var KwConfig = /* @__PURE__ */ function() {
return __component__$1.exports;
}();
var render = function() {
var _vm = this;
var _h = _vm.$createElement;
var _c = _vm._self._c || _h;
return _c("k-inside", [_c("k-view", [_c("k-header", [_vm._v(" " + _vm._s(_vm.kwentry.name) + " "), _c("k-button-group", { attrs: { "slot": "left" }, slot: "left" }, [_c("k-button", { attrs: { "text": "Edit", "icon": "edit" }, on: { "click": function($event) {
return _vm.$dialog("kwconfig/" + _vm.kwentry.name + "/update");
} } }), _c("k-button", { attrs: { "text": "Delete", "icon": "trash" }, on: { "click": function($event) {
return _vm.$dialog("kwconfig/" + _vm.kwentry.name + "/delete");
} } })], 1)], 1), _c("table", { staticClass: "k-kwconfig" }, [_c("tr", [_c("th", { staticClass: "k-kwconfig-type" }, [_vm._v("Type")]), _c("th", { staticClass: "k-kwconfig-description" }, [_vm._v("Description")]), _c("th", { staticClass: "k-kwconfig-data" }, [_vm._v("Value")])]), _c("tr", [_c("td", { staticClass: "k-kwconfig-type" }, [_vm._v(_vm._s(_vm.kwentry.type))]), _c("td", { staticClass: "k-kwconfig-description" }, [_vm._v(_vm._s(_vm.kwentry.description))]), _c("td", { staticClass: "k-kwconfig-data" }, [_vm._v(_vm._s(_vm.kwentry.data))])])])], 1)], 1);
};
var staticRenderFns = [];
render._withStripped = true;
const __vue2_script = {
props: {
kwentry: Object
}
};
const __cssModules = {};
var __component__ = /* @__PURE__ */ normalizeComponent(__vue2_script, render, staticRenderFns, false, __vue2_injectStyles, null, null, null);
function __vue2_injectStyles(context) {
for (let o in __cssModules) {
this[o] = __cssModules[o];
}
}
__component__.options.__file = "src/components/KwEntry.vue";
var KwEntry = /* @__PURE__ */ function() {
return __component__.exports;
}();
panel.plugin("kangaroopunch/kangaworld-integration", {
components: {
"k-kwconfig-view": KwConfig,
"k-kwentry-view": KwEntry
}
});
})();

View file

@ -1,19 +1,8 @@
<?php
load([
'KangarooPunch\KwConfig' => 'KwConfig.php',
'KangarooPunch\KwNumbers' => 'KwNumbers.php',
'KangarooPunch\KwStrings' => 'KwStrings.php',
'KangarooPunch\KPunch' => 'KPunch.php'
], __DIR__ . '/classes');
Kirby::plugin('kangaroopunch/kangaworld-integration', [
'api' => require __DIR__ . '/features/api.php',
// 'areas' => require __DIR__ . '/features/areas.php',
'collections' => require __DIR__ . '/features/collections.php',
'options' => require __DIR__ . '/features/options.php'
'api' => require __DIR__ . '/features/api.php'
]);

View file

@ -1,6 +0,0 @@
{
"scripts": {
"dev": "npx -y kirbyup src/index.js --watch",
"build": "npx -y kirbyup src/index.js"
}
}

View file

@ -1,32 +0,0 @@
<?php
use KangarooPunch\KwConfig;
return [
'label' => 'Configuration',
'icon' => 'globe',
'query' => function(string $query) {
$kwconfig = KwConfig::list();
$results = [];
foreach ($kwconfig as $kwentry) {
if ((Str::contains($kwentry['name'], $query, true) === true) ||
(Str::contains($kwentry['description'], $query, true) === true) ||
(Str::contains($kwentry['data'], $query, true) === true)) {
$results[] = [
'text' => $kwentry['name'],
'link' => '/kwconfig/' . esc($kwentry['name'], 'url'),
'image' => [
'icon' => 'globe',
'back' => 'purple-400'
]
];
}
}
return $results;
}
];
?>

View file

@ -1,126 +0,0 @@
<template>
<k-inside>
<k-view>
<k-header>
Kanga World Configuration
<k-button-group slot="right">
<k-button
text="New Entry"
icon="add"
@click="$dialog('kwconfig/create')"
/>
</k-button-group>
</k-header>
<table class="k-kwconfig">
<tr>
<th class="k-kwconfig-name">
<button @click="sortBy('name')">
Name
<span v-if="sort === 'name'" v-html="sortArrow"/>
</button>
</th>
<th class="k-kwconfig-type">
<button @click="sortBy('type')">
Type
<span v-if="sort === 'type'" v-html="sortArrow"/>
</button>
</th>
<th class="k-kwconfig-description">
<button @click="sortBy('description')">
Description
<span v-if="sort === 'description'" v-html="sortArrow"/>
</button>
</th>
<th class="k-kwconfig-data">
<button @click="sortBy('data')">
Value
<span v-if="sort === 'data'" v-html="sortArrow"/>
</button>
</th>
<th class="k-kwconfig-options"></th>
</tr>
<tr v-for="(kwconfig, name) in kwconfig" :key="name">
<td class="k-kwconfig-name">{{ kwconfig.name }}</td>
<td class="k-kwconfig-type">{{ kwconfig.type }}</td>
<td class="k-kwconfig-description">{{ kwconfig.description }}</td>
<td class="k-kwconfig-data">{{ kwconfig.data }}</td>
<td class="k-kwconfig-options">
<k-options-dropdown :options="'kwconfig/' + name" />
</td>
</tr>
</table>
</k-view>
</k-inside>
</template>
<script>
export default {
props: {
dir: String,
sort: String,
kwconfig: Object
},
computed: {
sortArrow() {
return this.dir === "asc" ? "&darr;" : "&uarr;";
},
},
methods: {
sortBy(sort) {
let dir = "asc";
if (sort === this.sort) dir = this.dir === "asc" ? "desc" : "asc";
this.$reload({
query: {
dir: dir,
sort: sort
},
});
}
}
};
</script>
<style>
.k-kwconfig {
width: 100%;
table-layout: fixed;
border-spacing: 1px;
}
.k-kwconfig td,
.k-kwconfig th {
text-align: left;
font-size: var(--text-sm);
padding: var(--spacing-2);
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
background: var(--color-white);
}
.k-kwconfig th {
font-weight: var(--font-bold);
}
.k-kwconfig th button {
font: inherit;
text-align: left;
width: 100%;
}
.k-kwconfig-name {
/* width: 8rem; */
}
.k-kwconfig-type {
/* width: 8rem; */
}
.k-kwconfig-description {
width: 50%;
}
.k-kwconfig-data {
/* width: 10rem; */
font-variant-numeric: tabular-nums;
text-align: right !important;
}
.k-kwconfig-options {
padding: 0 !important;
width: 3rem;
overflow: visible !important;
}
</style>

View file

@ -1,41 +0,0 @@
<template>
<k-inside>
<k-view>
<k-header>
{{ kwentry.name }}
<k-button-group slot="left">
<k-button
text="Edit"
icon="edit"
@click="$dialog('kwconfig/' + kwentry.name + '/update')"
/>
<k-button
text="Delete"
icon="trash"
@click="$dialog('kwconfig/' + kwentry.name + '/delete')"
/>
</k-button-group>
</k-header>
<table class="k-kwconfig">
<tr>
<th class="k-kwconfig-type">Type</th>
<th class="k-kwconfig-description">Description</th>
<th class="k-kwconfig-data">Value</th>
</tr>
<tr>
<td class="k-kwconfig-type">{{ kwentry.type }}</td>
<td class="k-kwconfig-description">{{ kwentry.description }}</td>
<td class="k-kwconfig-data">{{ kwentry.data }}</td>
</tr>
</table>
</k-view>
</k-inside>
</template>
<script>
export default {
props: {
kwentry: Object
}
};
</script>

View file

@ -1,9 +0,0 @@
import KwConfig from "./components/KwConfig.vue";
import KwEntry from "./components/KwEntry.vue";
panel.plugin("kangaroopunch/kangaworld-integration", {
components: {
"k-kwconfig-view": KwConfig,
"k-kwentry-view": KwEntry
}
});

View file

@ -1,25 +0,0 @@
<?php
use KangarooPunch\KwConfig;
return [
'pattern' => 'kwconfig',
'action' => function () {
$sort = get('sort', 'name');
$dir = get('dir', 'asc');
$kwconfig = KwConfig::list();
$kwconfig = A::sort($kwconfig, $sort, $dir);
return [
'component' => 'k-kwconfig-view',
'props' => [
'dir' => $dir,
'sort' => $sort,
'kwconfig' => $kwconfig
],
'search' => 'kwconfig'
];
}
];
?>

View file

@ -1,25 +0,0 @@
<?php
use KangarooPunch\KwConfig;
return [
'pattern' => 'kwconfig/(:any)',
'action' => function ($id) {
$kwentry = KwConfig::find($id);
return [
'component' => 'k-kwentry-view',
'breadcrumb' => [
[
'label' => $kwentry['name'],
'link' => 'kwconfig/' . $id
]
],
'props' => [
'kwentry' => $kwentry
]
];
}
];
?>

View file

@ -21,8 +21,8 @@ TEMPLATE = subdirs
CONFIG *= ORDERED
SUBDIRS = \
client
# server
# client \
server
# precache
# font
# primes

View file

@ -50,6 +50,7 @@ HEADERS = \
$$SHARED/thirdparty/tiny-AES-c/aes.h \
$$SHARED/thirdparty/tiny-AES128-C/pkcs7_padding.h \
src/client.h \
src/client/file.h \
src/client/login.h \
src/client/pong.h \
src/client/shutdown.h \
@ -59,6 +60,7 @@ HEADERS = \
src/network.h \
src/os.h \
src/rest.h \
src/database.h \
src/server.h
SOURCES = \
@ -72,6 +74,7 @@ SOURCES = \
$$SHARED/thirdparty/tiny-AES-c/aes.c \
$$SHARED/thirdparty/tiny-AES128-C/pkcs7_padding.c \
src/client.c \
src/client/file.c \
src/client/login.c \
src/client/pong.c \
src/client/shutdown.c \
@ -81,12 +84,14 @@ SOURCES = \
src/main.c \
src/network.c \
src/rest.c \
src/database.c \
src/server.c
LIBS = \
-L/usr/lib/x86_64-linux-gnu/ \
-ldl \
-lm \
-lmariadb \
-lpthread \
-lgnutls \
-lcrypt \

View file

@ -24,13 +24,13 @@
#include "console.h"
#include "packet.h"
#include "server.h"
#include "rest.h"
#include "client/login.h"
#include "client/signup.h"
#include "client/pong.h"
#include "client/version.h"
#include "client/shutdown.h"
#include "client/file.h"
typedef void (*clientApi)(ClientThreadT *client, PacketDecodeDataT *data);
@ -40,7 +40,7 @@ typedef void (*clientApi)(ClientThreadT *client, PacketDecodeDataT *data);
static clientApi _clientApiMethod[PACKET_TYPE_COUNT];
static uint8_t clientDequeuePacket(ClientThreadT *client);
static uint8_t clientDequeuePacket(ClientThreadT *client);
static uint8_t clientDequeuePacket(ClientThreadT *client) {
@ -105,6 +105,7 @@ void clientStartup(void) {
memset(_clientApiMethod, 0, sizeof(_clientApiMethod));
_clientApiMethod[PACKET_TYPE_CLIENT_SHUTDOWN] = clientApiClientShutdown;
_clientApiMethod[PACKET_TYPE_FILE_REQUEST] = clientApiFileRequest;
_clientApiMethod[PACKET_TYPE_LOGIN] = clientApiLogin;
_clientApiMethod[PACKET_TYPE_PONG] = clientApiPong;
_clientApiMethod[PACKET_TYPE_SIGNUP] = clientApiSignup;

View file

@ -35,6 +35,7 @@ typedef struct ClientRawPacketS {
} ClientRawPacketT;
typedef struct ClientThreadS {
// System Stuff.
uint64_t threadIndex;
pthread_t threadHandle;
pthread_attr_t threadAttributes;
@ -49,6 +50,8 @@ typedef struct ClientThreadS {
double pingHigh;
double pingLow;
void *peer;
// User State Stuff.
uint8_t authenticated;
} ClientThreadT;

126
server/src/client/file.c Normal file
View file

@ -0,0 +1,126 @@
/*
* Kangaroo Punch MultiPlayer Game Server Mark II
* Copyright (C) 2020-2021 Scott Duensing
*
* This program 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 <https://www.gnu.org/licenses/>.
*
*/
#include "database.h"
#include "file.h"
static void clientApiFileRequestCacheCheck(ClientThreadT *client, PacketDecodeDataT *data);
static void clientApiFileRequestClose(ClientThreadT *client, PacketDecodeDataT *data);
static void clientApiFileRequestOpen(ClientThreadT *client, PacketDecodeDataT *data);
static void clientApiFileRequestRead(ClientThreadT *client, PacketDecodeDataT *data);
void clientApiFileRequest(ClientThreadT *client, PacketDecodeDataT *data) {
FileRequestsT request = 0;
PacketEncodeDataT encoded = { 0 };
char *packetData = NULL;
uint16_t length = 0;
// Must be logged in to do file operations.
if (!client->authenticated) {
// Extract the request type. We get more data later.
packetContentUnpack(data->data, "i", &request);
switch (request) {
case FILE_REQUEST_CACHE_CHECK:
clientApiFileRequestCacheCheck(client, data);
break;
case FILE_REQUEST_OPEN:
clientApiFileRequestOpen(client, data);
break;
case FILE_REQUEST_READ:
clientApiFileRequestRead(client, data);
break;
case FILE_REQUEST_CLOSE:
clientApiFileRequestClose(client, data);
break;
default:
// No idea what they want. First value is 0 for fail, 1 for success.
packetData = packetContentPack(&length, "i", 0);
// Build packet.
encoded.control = PACKET_CONTROL_DAT;
encoded.packetType = PACKET_TYPE_FILE_RESPONSE;
encoded.channel = data->channel;
encoded.encrypt = 0;
packetEncode(client->packetThreadData, &encoded, packetData, length);
// Send it.
packetSend(client->packetThreadData, &encoded);
DEL(packetData);
break;
}
} // authenticated
}
static void clientApiFileRequestCacheCheck(ClientThreadT *client, PacketDecodeDataT *data) {
FileRequestsT request = 0;
char *path = NULL;
char *sha256 = NULL;
PacketEncodeDataT encoded = { 0 };
char *packetData = NULL;
uint16_t length = 0;
// Extract the request.
packetContentUnpack(data->data, "i", &request, &path, &sha256);
// Look up the entry and compare SHA.
}
static void clientApiFileRequestClose(ClientThreadT *client, PacketDecodeDataT *data) {
FileRequestsT request = 0;
PacketEncodeDataT encoded = { 0 };
char *packetData = NULL;
uint16_t length = 0;
}
static void clientApiFileRequestOpen(ClientThreadT *client, PacketDecodeDataT *data) {
FileRequestsT request = 0;
char *path = NULL;
char *sha256 = NULL;
PacketEncodeDataT encoded = { 0 };
char *packetData = NULL;
uint16_t length = 0;
// Extract the request.
packetContentUnpack(data->data, "i", &request, &path);
// Look up the entry and fetch SHA.
}
static void clientApiFileRequestRead(ClientThreadT *client, PacketDecodeDataT *data) {
FileRequestsT request = 0;
PacketEncodeDataT encoded = { 0 };
char *packetData = NULL;
uint16_t length = 0;
}

32
server/src/client/file.h Normal file
View file

@ -0,0 +1,32 @@
/*
* Kangaroo Punch MultiPlayer Game Server Mark II
* Copyright (C) 2020-2021 Scott Duensing
*
* This program 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 <https://www.gnu.org/licenses/>.
*
*/
#ifndef FILE_H
#define FILE_H
#include "os.h"
#include "client.h"
void clientApiFileRequest(ClientThreadT *client, PacketDecodeDataT *data);
#endif // FILE_H

View file

@ -18,7 +18,7 @@
*/
#include "rest.h"
#include "database.h"
#include "login.h"
@ -28,31 +28,26 @@ void clientApiLogin(ClientThreadT *client, PacketDecodeDataT *data) {
char *user = NULL;
char *pass = NULL;
PacketEncodeDataT encoded = { 0 };
json_object *response = NULL;
uint8_t result = 0;
char *string = NULL;
char *buffer = NULL;
uint16_t length = 0;
packetContentUnpack(data->data, "ss", &user, &pass);
response = restRequest("USER_LOGIN", "ss",
"user", user,
"pass", pass
);
DEL(user);
DEL(pass);
if (response) {
string = (char *)json_object_get_string(json_object_object_get(response, "result"));
result = (string[0] == 't' || string[0] == 'T') ? 1 : 0;
buffer = (char *)json_object_get_string(json_object_object_get(response, "reason"));
// This only makes sense if they're not logged in.
if (!client->authenticated) {
packetContentUnpack(data->data, "ss", &user, &pass);
result = dbUserLogin(user, pass);
DEL(user);
DEL(pass);
}
if (result) {
buffer = "Logged in.";
client->authenticated = 1;
} else {
// Something bad happened.
result = 0;
buffer = "Unknown error. Sorry.";
}
logWrite("Login: %d %s\r\n", result, buffer);
packetData = packetContentPack(&length, "is", result, buffer);
if (response) restRelease(response);
// Build packet.
encoded.control = PACKET_CONTROL_DAT;
encoded.packetType = PACKET_TYPE_LOGIN_RESULT;

View file

@ -38,7 +38,6 @@ void clientApiPong(ClientThreadT *client, PacketDecodeDataT *data) {
client->pingStats[client->pingHead] = d;
client->pingHead++;
if (client->pingHead >= PING_STATS_SIZE) client->pingHead = 0;
// ***TODO*** Probably need a mutex here.
if (d > client->pingHigh) client->pingHigh = d;
if (d < client->pingLow) client->pingLow = d;
client->pingAverage = 0;

View file

@ -19,6 +19,7 @@
#include "rest.h"
#include "database.h"
#include "signup.h"
@ -37,23 +38,62 @@ void clientApiSignup(ClientThreadT *client, PacketDecodeDataT *data) {
PacketEncodeDataT encoded = { 0 };
uint16_t length = 0;
packetContentUnpack(data->data, "sssss", &email, &first, &last, &pass, &user);
response = restRequest("USER_CREATE", "sssss",
"first", first,
"last", last,
"user", user,
"pass", pass,
"email", email
);
if (response) {
string = (char *)json_object_get_string(json_object_object_get(response, "result"));
result = (string[0] == 't' || string[0] == 'T') ? 1 : 0;
buffer = (char *)json_object_get_string(json_object_object_get(response, "reason"));
} else {
// Something bad happened.
result = 0;
buffer = "Unknown error. Sorry.";
// This only makes sense if they're not logged in.
if (!client->authenticated) {
// Get info for the user.
packetContentUnpack(data->data, "sssss", &email, &first, &last, &pass, &user);
// See if they already exist in the database.
if (dbUserNameExists(user)) {
// User name already exists.
result = 0;
buffer = "User name already exists.";
} else {
if (dbUserEmailExists(email)) {
// Email already exists.
result = 0;
buffer = "E-mail already exists.";
} else {
// Create user in DB.
if (dbUserCreate(first, last, user, pass, email) == FAIL) {
result = 0;
buffer = "Unknown error. Sorry.";
} else {
// If we found a REST endpoint, create the user there as well.
if (__restAvailable) {
response = restRequest("USER_CREATE", "sssss",
"first", first,
"last", last,
"user", user,
"pass", pass,
"email", email
);
if (response) {
// Success with REST.
string = (char *)json_object_get_string(json_object_object_get(response, "result"));
result = (string[0] == 't' || string[0] == 'T') ? 1 : 0;
buffer = (char *)json_object_get_string(json_object_object_get(response, "reason"));
} else {
// Something bad happened.
result = 0;
buffer = "Unknown error. Sorry.";
}
} else {
// Success with DB only!
result = 1;
buffer = "Check your E-mail for activation instructions.";
}
}
}
}
DEL(first);
DEL(last);
DEL(user);
DEL(pass);
DEL(email);
}
packetData = packetContentPack(&length, "is", result, buffer);
if (response) restRelease(response);
// Build packet.

View file

@ -18,7 +18,7 @@
*/
#include "rest.h"
#include "database.h"
#include "console.h"
#include "array.h"
@ -31,96 +31,88 @@ void clientApiVersionBad(ClientThreadT *client, PacketDecodeDataT *data) {
void clientApiVersionOkay(ClientThreadT *client, PacketDecodeDataT *data) {
json_object *response = NULL;
RestStringMapT *strings = NULL;
RestIntegerMapT *integers = NULL;
uint64_t i = 0;
uint64_t x = 0;
uint32_t y = 0;
uint16_t length = 0;
char *buffer = NULL;
PacketEncodeDataT encoded = { 0 };
DBTableT *record = NULL;
DBTableT **table = NULL;
(void)data;
// Fetch string table from REST.
response = restRequest("CONFIG_GET_STRINGS", NULL);
if (!response) {
// Fetch string table from DB.
if (dbTableGet("strings", table) == SUCCESS) {
while (arrlen(table)) {
record = table[0];
arrdel(table, 0);
// Strings are encoded in a single buffer as: KEY\0DATA\0
x = strlen(record->name);
y = strlen(record->data);
length = x + y + 2;
buffer = (char *)malloc(length);
if (!buffer) {
consoleMessageQueue("%ld: Unable to allocate buffer for string packet!\n", client->threadIndex);
break;
}
memcpy(buffer, record->name, x + 1);
memcpy(&buffer[x + 1], record->data, y + 1);
// Build packet.
encoded.control = PACKET_CONTROL_DAT;
encoded.packetType = PACKET_TYPE_STRING;
encoded.channel = data->channel;
encoded.encrypt = 0;
packetEncode(client->packetThreadData, &encoded, buffer, length);
// Send it.
packetSend(client->packetThreadData, &encoded);
DEL(buffer);
DEL(record->name);
DEL(record->data);
DEL(record);
consoleMessageQueue("PACKET_TYPE_STRING [%s] sent.\n", record->name);
}
arrfree(table);
} else {
consoleMessageQueue("%ld: Unable to fetch strings!\n", client->threadIndex);
return;
}
strings = restHelperConfigStringMapGet(response);
if (!strings) {
consoleMessageQueue("%ld: Unable to map strings!\n", client->threadIndex);
return;
}
restRelease(response);
// Send string table to client.
for (i=0; i<(unsigned)shlen(strings); i++) {
// Strings are encoded in a single buffer as: KEY\0DATA\0
x = strlen(strings[i].key);
y = strlen(strings[i].value);
length = x + y + 2;
buffer = (char *)malloc(length);
if (!buffer) {
consoleMessageQueue("%ld: Unable to allocate buffer for string packet!\n", client->threadIndex);
break;
// Fetch number table from DB.
if (dbTableGet("numbers", table) == SUCCESS) {
while (arrlen(table)) {
record = table[0];
arrdel(table, 0);
// Integers are encoded in a single buffer as: 1234DATA\0
// Integers are 64 bit until sent to the client when they are truncated to 32.
x = strlen(record->name);
y = atol(record->data);
length = x + 5;
buffer = (char *)malloc(length);
if (!buffer) {
consoleMessageQueue("%ld: Unable to allocate buffer for number packet!\n", client->threadIndex);
break;
}
memcpy(buffer, &y, 4);
memcpy(&buffer[4], record->name, x + 1);
// Build packet.
encoded.control = PACKET_CONTROL_DAT;
encoded.packetType = PACKET_TYPE_NUMBER;
encoded.channel = data->channel;
encoded.encrypt = 0;
packetEncode(client->packetThreadData, &encoded, buffer, length);
// Send it.
packetSend(client->packetThreadData, &encoded);
DEL(buffer);
DEL(record->name);
DEL(record->data);
DEL(record);
consoleMessageQueue("PACKET_TYPE_NUMBER [%s] sent.\n", record->name);
}
memcpy(buffer, strings[i].key, x + 1);
memcpy(&buffer[x + 1], strings[i].value, y + 1);
// Build packet.
encoded.control = PACKET_CONTROL_DAT;
encoded.packetType = PACKET_TYPE_STRING;
encoded.channel = data->channel;
encoded.encrypt = 0;
packetEncode(client->packetThreadData, &encoded, buffer, length);
// Send it.
packetSend(client->packetThreadData, &encoded);
DEL(buffer);
consoleMessageQueue("PACKET_TYPE_STRING [%s] sent.\n", strings[i].key);
}
restHelperConfigStringMapRelease(strings);
// Fetch number table from REST.
response = restRequest("CONFIG_GET_NUMBERS", NULL);
if (!response) {
arrfree(table);
} else {
consoleMessageQueue("%ld: Unable to fetch numbers!\n", client->threadIndex);
return;
}
integers = restHelperConfigIntegerMapGet(response);
if (!integers) {
consoleMessageQueue("%ld: Unable to map numbers!\n", client->threadIndex);
return;
}
restRelease(response);
// Send number table to client.
for (i=0; i<(unsigned)shlen(integers); i++) {
// Integers are encoded in a single buffer as: 1234DATA\0
// Integers are 64 bit until sent to the client when they are truncated to 32.
x = strlen(integers[i].key);
y = integers[i].value; // 64->32
length = x + 5;
buffer = (char *)malloc(length);
if (!buffer) {
consoleMessageQueue("%ld: Unable to allocate buffer for number packet!\n\r", client->threadIndex);
break;
}
memcpy(buffer, &y, 4);
memcpy(&buffer[4], integers[i].key, x + 1);
// Build packet.
encoded.control = PACKET_CONTROL_DAT;
encoded.packetType = PACKET_TYPE_NUMBER;
encoded.channel = data->channel;
encoded.encrypt = 0;
packetEncode(client->packetThreadData, &encoded, buffer, length);
// Send it.
packetSend(client->packetThreadData, &encoded);
DEL(buffer);
consoleMessageQueue("PACKET_TYPE_NUMBER [%s] sent.\n", integers[i].key);
}
restHelperConfigIntegerMapRelease(integers);
// Build PROCEED packet.
encoded.control = PACKET_CONTROL_DAT;

View file

@ -69,7 +69,7 @@ void consoleRun(void) {
terminalModeConioSet();
while (_running) {
while (__running) {
if (kbhit()) {
@ -120,7 +120,7 @@ void consoleRun(void) {
commandOk = 1;
}
if (!strcasecmp(command, "SHUTDOWN")) {
_running = 0;
__running = 0;
commandOk = 1;
}
// Did we grok it?

342
server/src/database.c Normal file
View file

@ -0,0 +1,342 @@
/*
* Kangaroo Punch MultiPlayer Game Server Mark II
* Copyright (C) 2020-2021 Scott Duensing
*
* This program 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 <https://www.gnu.org/licenses/>.
*
*/
#include <mysql.h>
#include <pthread.h>
#include <crypt.h>
#include "array.h"
#include "database.h"
#define STATEMENT_MAX 2048
static MYSQL *_sql = NULL;
static pthread_mutex_t _mutex = PTHREAD_MUTEX_INITIALIZER;
uint8_t dbConnect(char *host, uint16_t port, char *database, char *user, char *password) {
uint8_t reconnect = 1;
if (_sql == NULL) {
_sql = mysql_init(NULL);
mysql_options(_sql, MYSQL_OPT_RECONNECT, (const void *)&reconnect);
if (mysql_real_connect(_sql, host, user, password, database, port, NULL, 0) == NULL) {
logWrite("dbConnect: %s\n", mysql_error(_sql));
return FAIL;
}
if (pthread_mutex_init(&_mutex, NULL)) {
logWrite("dbConnect: SQL mutex creation failed.\n");
return FAIL;
}
return SUCCESS;
}
return FAIL;
}
uint8_t dbDisconnect(void) {
if (_sql) {
mysql_close(_sql);
_sql = NULL;
pthread_mutex_destroy(&_mutex);
return SUCCESS;
}
return FAIL;
}
uint8_t dbSettingsStringGet(char *key, char *value, uint32_t max) {
char statement[STATEMENT_MAX];
char *p = statement;
MYSQL_RES *result = NULL;
MYSQL_ROW row;
int count;
pthread_mutex_lock(&_mutex);
if (!_sql) {
pthread_mutex_unlock(&_mutex);
return FAIL;
}
p += sprintf(p, "SELECT data FROM config WHERE name='");
p += mysql_real_escape_string(_sql, p, key, strlen(key));
p += sprintf(p, "'");
if (mysql_real_query(_sql, statement, p - statement) != 0) {
logWrite("dbConfigValueGet: %s\n", mysql_error(_sql));
pthread_mutex_unlock(&_mutex);
return FAIL;
}
result = mysql_store_result(_sql);
count = mysql_num_rows(result);
if (count != 1) {
logWrite("dbConfigValueGet: Too many rows returned: %d.\n", count);
mysql_free_result(result);
pthread_mutex_unlock(&_mutex);
return FAIL;
}
if ((row = mysql_fetch_row(result)) == NULL) {
logWrite("dbConfigValueGet: %s\n", mysql_error(_sql));
pthread_mutex_unlock(&_mutex);
return FAIL;
}
strncpy(value, row[0], max);
mysql_free_result(result);
pthread_mutex_unlock(&_mutex);
return SUCCESS;
}
uint8_t dbSettingsValueGet(char *key, int32_t *value) {
char temp[DB_CONFIG_ITEM_SIZE] = { 0 };
if (dbSettingsStringGet(key, temp, DB_CONFIG_ITEM_SIZE) == FAIL) return FAIL;
*value = atoi(temp);
return SUCCESS;
}
uint8_t dbTableGet(char *which, DBTableT **table) {
char statement[STATEMENT_MAX];
char *p = statement;
MYSQL_RES *result = NULL;
MYSQL_ROW row;
int count;
DBTableT *record = NULL;
pthread_mutex_lock(&_mutex);
if (!_sql) {
pthread_mutex_unlock(&_mutex);
return FAIL;
}
p += sprintf(p, "SELECT name, data FROM ");
p += mysql_real_escape_string(_sql, p, which, strlen(which));
if (mysql_real_query(_sql, statement, p - statement) != 0) {
logWrite("dbTableGet: %s\n", mysql_error(_sql));
pthread_mutex_unlock(&_mutex);
return FAIL;
}
result = mysql_store_result(_sql);
for (count = 0; count < (int)mysql_num_rows(result); count++) {
if ((row = mysql_fetch_row(result)) == NULL) {
logWrite("dbTableGet: %s\n", mysql_error(_sql));
pthread_mutex_unlock(&_mutex);
return FAIL;
}
NEW(DBTableT, record);
record->name = strdup(row[0]);
record->data = strdup(row[1]);
arrput(table, record);
}
mysql_free_result(result);
pthread_mutex_unlock(&_mutex);
return SUCCESS;
}
uint8_t dbUserCreate(char *first, char *last, char *user, char *pass, char *email) {
char statement[STATEMENT_MAX];
char *p = statement;
char *hash = NULL;
char *salt = NULL;
pthread_mutex_lock(&_mutex);
if (!_sql) {
pthread_mutex_unlock(&_mutex);
return FAIL;
}
// Not thread safe. Protected by SQL _mutex.
salt = crypt_gensalt(0, 0, 0, 0);
hash = crypt(pass, salt);
// We don't pass 'enabled' into the database and instead rely on the default value.
p += sprintf(p, "INSERT INTO users (first, last, user, pass, email) VALUES ('");
p += mysql_real_escape_string(_sql, p, first, strlen(first));
p += sprintf(p, "', '");
p += mysql_real_escape_string(_sql, p, last, strlen(last));
p += sprintf(p, "', '");
p += mysql_real_escape_string(_sql, p, user, strlen(user));
p += sprintf(p, "', '");
p += mysql_real_escape_string(_sql, p, hash, strlen(hash));
p += sprintf(p, "', '");
p += mysql_real_escape_string(_sql, p, email, strlen(email));
p += sprintf(p, "')");
if (mysql_real_query(_sql, statement, p - statement) != 0) {
logWrite("dbUserCreate: %s\n", mysql_error(_sql));
pthread_mutex_unlock(&_mutex);
return FAIL;
}
pthread_mutex_unlock(&_mutex);
return SUCCESS;
}
uint8_t dbUserEmailExists(char *email) {
char statement[STATEMENT_MAX];
char *p = statement;
MYSQL_RES *result = NULL;
int count;
pthread_mutex_lock(&_mutex);
if (!_sql) {
pthread_mutex_unlock(&_mutex);
return FAIL;
}
p += sprintf(p, "SELECT id FROM users WHERE email='");
p += mysql_real_escape_string(_sql, p, email, strlen(email));
p += sprintf(p, "'");
if (mysql_real_query(_sql, statement, p - statement) != 0) {
logWrite("dbUserEmailExists: %s\n", mysql_error(_sql));
pthread_mutex_unlock(&_mutex);
return FAIL;
}
result = mysql_store_result(_sql);
count = mysql_num_rows(result);
if (count != 1) {
logWrite("dbUserEmailExists: Too many rows returned: %d.\n", count);
mysql_free_result(result);
pthread_mutex_unlock(&_mutex);
return FAIL;
}
mysql_free_result(result);
pthread_mutex_unlock(&_mutex);
return SUCCESS;
}
uint8_t dbUserLogin(char *user, char *password) {
char statement[STATEMENT_MAX];
char *p = statement;
MYSQL_RES *result = NULL;
MYSQL_ROW row;
int count;
uint8_t status = FAIL;
pthread_mutex_lock(&_mutex);
if (!_sql) {
pthread_mutex_unlock(&_mutex);
return FAIL;
}
p += sprintf(p, "SELECT pass FROM users WHERE user='");
p += mysql_real_escape_string(_sql, p, user, strlen(user));
p += sprintf(p, "' AND enabled = 1");
if (mysql_real_query(_sql, statement, p - statement) != 0) {
logWrite("dbUserLogin: %s\n", mysql_error(_sql));
pthread_mutex_unlock(&_mutex);
return FAIL;
}
result = mysql_store_result(_sql);
count = mysql_num_rows(result);
if (count != 1) {
logWrite("dbUserLogin: Too many rows returned: %d.\n", count);
mysql_free_result(result);
pthread_mutex_unlock(&_mutex);
return FAIL;
}
if ((row = mysql_fetch_row(result)) == NULL) {
logWrite("dbUserLogin: %s\n", mysql_error(_sql));
pthread_mutex_unlock(&_mutex);
return FAIL;
}
// Not thread safe. Protected by SQL _mutex.
p = crypt(password, row[0]);
if (strcmp(p, password) == 0) status = SUCCESS;
mysql_free_result(result);
pthread_mutex_unlock(&_mutex);
return status;
}
uint8_t dbUserNameExists(char *user) {
char statement[STATEMENT_MAX];
char *p = statement;
MYSQL_RES *result = NULL;
int count;
pthread_mutex_lock(&_mutex);
if (!_sql) {
pthread_mutex_unlock(&_mutex);
return FAIL;
}
p += sprintf(p, "SELECT id FROM users WHERE user='");
p += mysql_real_escape_string(_sql, p, user, strlen(user));
p += sprintf(p, "'");
if (mysql_real_query(_sql, statement, p - statement) != 0) {
logWrite("dbUserNameExists: %s\n", mysql_error(_sql));
pthread_mutex_unlock(&_mutex);
return FAIL;
}
result = mysql_store_result(_sql);
count = mysql_num_rows(result);
if (count != 1) {
logWrite("dbUserNameExists: Too many rows returned: %d.\n", count);
mysql_free_result(result);
pthread_mutex_unlock(&_mutex);
return FAIL;
}
mysql_free_result(result);
pthread_mutex_unlock(&_mutex);
return SUCCESS;
}

48
server/src/database.h Normal file
View file

@ -0,0 +1,48 @@
/*
* Kangaroo Punch MultiPlayer Game Server Mark II
* Copyright (C) 2020-2021 Scott Duensing
*
* This program 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 <https://www.gnu.org/licenses/>.
*
*/
#ifndef DATABASE_H
#define DATABASE_H
#include "os.h"
#define DB_CONFIG_ITEM_SIZE 1024
typedef struct DBTableS {
char *name;
char *data;
} DBTableT;
uint8_t dbConnect(char *host, uint16_t port, char *database, char *user, char *password);
uint8_t dbDisconnect(void);
uint8_t dbSettingsStringGet(char *key, char *value, uint32_t max);
uint8_t dbSettingsValueGet(char *key, int32_t *value);
uint8_t dbTableGet(char *which, DBTableT **table);
uint8_t dbUserCreate(char *first, char *last, char *user, char *pass, char *email);
uint8_t dbUserEmailExists(char *email);
uint8_t dbUserLogin(char *user, char *password);
uint8_t dbUserNameExists(char *user);
#endif // DATABASE_H

View file

@ -23,39 +23,50 @@
#include "stddclmr.h"
#include "server.h"
#include "rest.h"
#include "database.h"
#include "thirdparty/ini/src/ini.h"
uint8_t _running = 1; // Exported in os.h
// Exported in os.h
uint8_t __running = 1;
uint8_t __restAvailable = 1;
// "Config" items come from the INI file. "Settings" are from the database.
static char *_configUser = NULL;
static char *_configPassword = NULL;
static char *_configREST = NULL;
static char *_configServer = NULL;
static uint16_t _configPort = 0;
static char *_configDatabase = NULL;
static char *_configUser = NULL;
static char *_configPassword = NULL;
static void configRead(char *file);
static void configWrite(char *file);
static void configRead(char *file);
static void configWrite(char *file);
static void configRead(char *file) {
ini_t *ini = NULL;
// Numeric defaults.
_configPort = 16550;
ini = ini_load(file);
if (ini) {
ini_sget(ini, "SERVER", "PORT", "%d", &_configPort);
_configServer = strdup(ini_get(ini, "SERVER", "HOST"));
_configDatabase = strdup(ini_get(ini, "SERVER", "DATA"));
_configUser = strdup(ini_get(ini, "SERVER", "USER"));
_configPassword = strdup(ini_get(ini, "SERVER", "PASS"));
_configREST = strdup(ini_get(ini, "SERVER", "REST"));
ini_free(ini);
}
// String defaults.
if (!_configServer) strdup("kanga.world");
if (!_configDatabase) strdup("kpmpgsmkii");
if (!_configUser) strdup("");
if (!_configPassword) strdup("");
if (!_configREST) strdup("http://localhost:8000/api/kp/kangaworld/v1");
}
@ -66,17 +77,22 @@ static void configWrite(char *file) {
if (out) {
fprintf(out,
"[SERVER]\n"
"REST=%s\n"
"HOST=%s\n"
"PORT=%d\n"
"DATA=%s\n"
"USER=%s\n"
"PASS=%s\n",
_configREST,
_configServer,
_configPort,
_configDatabase,
_configUser,
_configPassword
);
fclose(out);
}
DEL(_configREST);
DEL(_configServer);
DEL(_configDatabase);
DEL(_configUser);
DEL(_configPassword);
}
@ -84,11 +100,15 @@ static void configWrite(char *file) {
int main(int argc, char *argv[]) {
char *configFile = NULL;
int64_t settingsMaxClients = 0;
int64_t settingsPortNumber = 0;
int64_t settingsClientVersion = 0;
json_object *response = NULL;
char *configFile = NULL;
char hostname[256] = { 0 };
char settingsFile[DB_CONFIG_ITEM_SIZE] = { 0 };
char settingsRest[DB_CONFIG_ITEM_SIZE] = { 0 };
char settingsUser[DB_CONFIG_ITEM_SIZE] = { 0 };
char settingsPass[DB_CONFIG_ITEM_SIZE] = { 0 };
int64_t settingsMaxClients = 0;
int64_t settingsPortNumber = 0;
int64_t settingsClientVersion = 0;
(void)argc;
@ -96,18 +116,39 @@ int main(int argc, char *argv[]) {
logOpenByHandle(memoryLogHandleGet());
logWrite("%s", "Kangaroo Punch MultiPlayer Game Server Mark II\n");
logWrite("%s", "Copyright (C) 2020-2021 Scott Duensing scott@kangaroopunch.com\n\n");
// Load our config.
configFile = utilAppNameWithNewExtensionGet(argv[0], "ini");
configRead(configFile);
if (!restStartup(_configREST, _configUser, _configPassword)) {
utilDie("Unable to start REST.\n");
// Find our hostname.
gethostname(hostname, 256);
// Get settings entries for this host.
snprintf(settingsFile, DB_CONFIG_ITEM_SIZE, "%s_file", hostname);
snprintf(settingsRest, DB_CONFIG_ITEM_SIZE, "%s_rest", hostname);
snprintf(settingsUser, DB_CONFIG_ITEM_SIZE, "%s_user", hostname);
snprintf(settingsPass, DB_CONFIG_ITEM_SIZE, "%s_pass", hostname);
// Connect to database.
if (!dbConnect(_configServer, _configPort, _configDatabase, _configUser, _configPassword)) {
utilDie("Unable to connect to database.\n");
}
response = restRequest("CONFIG_GET_CONFIG", NULL);
settingsMaxClients = restHelperConfigIntegerGet(response, "maxClients", 2);
settingsPortNumber = restHelperConfigIntegerGet(response, "portNumber", 16550);
settingsClientVersion = restHelperConfigIntegerGet(response, "clientVersion", 1);
restRelease(response);
// Fetch settings needed to start server.
if (!dbSettingsValueGet("maxClients", (int32_t *)&settingsMaxClients)) utilDie("Unable to load maxClients.\n");
if (!dbSettingsValueGet("portNumber", (int32_t *)&settingsPortNumber)) utilDie("Unable to load portNumber.\n");
if (!dbSettingsValueGet("clientVersion", (int32_t *)&settingsClientVersion)) utilDie("Unable to load clientVersion.\n");
if (!dbSettingsStringGet(settingsFile, settingsFile, DB_CONFIG_ITEM_SIZE)) utilDie("Unable to load file location.\n");
if (!dbSettingsStringGet(settingsRest, settingsRest, DB_CONFIG_ITEM_SIZE)) utilDie("Unable to load REST URL.\n");
if (!dbSettingsStringGet(settingsUser, settingsUser, DB_CONFIG_ITEM_SIZE)) utilDie("Unable to load REST user.\n");
if (!dbSettingsStringGet(settingsPass, settingsPass, DB_CONFIG_ITEM_SIZE)) utilDie("Unable to load REST password.\n");
// Start up REST.
if (!restStartup(settingsRest, settingsUser, settingsPass)) {
logWrite("Unable to locate REST API. Web site integration disabled.\n");
__restAvailable = 0;
}
clientStartup();
serverStartup(settingsPortNumber, settingsMaxClients);
@ -117,7 +158,7 @@ int main(int argc, char *argv[]) {
consoleRun();
// Console closed. Stop server.
_running = 0;
__running = 0;
// Wait for all running threads to shut down.
logWrite("Shutting down.\n");

View file

@ -48,7 +48,9 @@
#define FAIL 0
extern uint8_t _running; // Declared in main.c
// Declared in main.c
extern uint8_t __running;
extern uint8_t __restAvailable;
#endif // OS_H

View file

@ -46,126 +46,6 @@ static unsigned long restThreadtIdGet(void);
static json_object *restUrlPost(json_object *request);
int64_t restHelperConfigIntegerGet(json_object *object, char *name, int64_t defaultValue) {
uint64_t i = 0;
json_object *config = NULL;
json_object *data = NULL;
json_object *item = NULL;
int64_t result = defaultValue;
config = json_object_object_get(object, "payload");
if (config) {
data = json_object_object_get(config, "data");
if (data) {
for (i=0; i<json_object_array_length(data); i++) {
item = json_object_array_get_idx(data, i);
if (strcmp(name, json_object_get_string(json_object_object_get(item, "name"))) == 0) {
// Because of the way Kirby is sending us REST data, everything is a string. We have to convert it to an integer.
result = atol(json_object_get_string(json_object_object_get(item, "data")));
break;
}
}
}
}
return result;
}
RestIntegerMapT *restHelperConfigIntegerMapGet(json_object *object) {
uint64_t i = 0;
json_object *config = NULL;
json_object *data = NULL;
json_object *item = NULL;
RestIntegerMapT *result = NULL;
sh_new_strdup(result);
config = json_object_object_get(object, "payload");
if (config) {
data = json_object_object_get(config, "data");
if (data) {
for (i=0; i<json_object_array_length(data); i++) {
item = json_object_array_get_idx(data, i);
shput(result, json_object_get_string(json_object_object_get(item, "name")), atol(json_object_get_string(json_object_object_get(item, "data"))));
}
}
}
return result;
}
void restHelperConfigIntegerMapRelease(RestIntegerMapT *map) {
while (shlen(map) > 0) {
shdel(map, map[0].key);
}
shfree(map);
}
/*
char *restHelperConfigStringGet(json_object *object, char *name, char *defaultValue) {
uint64_t i = 0;
json_object *config = NULL;
json_object *data = NULL;
json_object *item = NULL;
char *result = strdup(defaultValue);
config = json_object_object_get(object, "payload");
if (config) {
data = json_object_object_get(config, "data");
if (data) {
for (i=0; i<json_object_array_length(data); i++) {
item = json_object_array_get_idx(data, i);
if (strcmp(name, json_object_get_string(json_object_object_get(item, "name"))) == 0) {
DEL(result);
result = strdup(json_object_get_string(json_object_object_get(item, "data")));
break;
}
}
}
}
return result;
}
*/
RestStringMapT *restHelperConfigStringMapGet(json_object *object) {
uint64_t i = 0;
json_object *config = NULL;
json_object *data = NULL;
json_object *item = NULL;
RestStringMapT *result = NULL;
sh_new_strdup(result);
config = json_object_object_get(object, "payload");
if (config) {
data = json_object_object_get(config, "data");
if (data) {
for (i=0; i<json_object_array_length(data); i++) {
item = json_object_array_get_idx(data, i);
shput(result, json_object_get_string(json_object_object_get(item, "name")), strdup(json_object_get_string(json_object_object_get(item, "data"))));
//consoleMessageQueue("[%s]=[%s]\n", json_object_get_string(json_object_object_get(item, "name")), json_object_get_string(json_object_object_get(item, "data")));
}
}
}
return result;
}
void restHelperConfigStringMapRelease(RestStringMapT *map) {
while (shlen(map) > 0) {
DEL(map[0].value);
shdel(map, map[0].key);
}
shfree(map);
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-function" // It's used, but the compiler isn't picking it up because it's via a callback.
static void restMutexLocker(int mode, int n, const char *file, int line) {
@ -275,8 +155,9 @@ void restShutdown(void) {
uint8_t restStartup(char *url, char *user, char *password) {
uint64_t i;
json_object *response;
uint64_t i = 0;
json_object *response = NULL;
uint8_t result = SUCCESS;
curl_global_init(CURL_GLOBAL_ALL);
@ -302,10 +183,10 @@ uint8_t restStartup(char *url, char *user, char *password) {
// Is the remote server there?
response = restRequest("API_AVAILABLE", NULL);
if (!response) return FAIL;
if (!response) result = FAIL;
restRelease(response);
return SUCCESS;
return result;
}

View file

@ -25,23 +25,6 @@
#include <json-c/json.h>
typedef struct RestStringMapS {
char *key;
char *value;
} RestStringMapT;
typedef struct RestIntegerMapS {
char *key;
int64_t value;
} RestIntegerMapT;
int64_t restHelperConfigIntegerGet(json_object *object, char *name, int64_t defaultValue);
RestIntegerMapT *restHelperConfigIntegerMapGet(json_object *object);
void restHelperConfigIntegerMapRelease(RestIntegerMapT *map);
//char *restHelperConfigStringGet(json_object *object, char *name, char *defaultValue);
RestStringMapT *restHelperConfigStringMapGet(json_object *object);
void restHelperConfigStringMapRelease(RestStringMapT *map);
void restRelease(json_object *object);
json_object *restRequest(char *command, char *format, ...);
void restShutdown(void);

View file

@ -25,9 +25,15 @@
#include "packet.h"
ENetHost *_server = NULL;
pthread_t _serverThreadHandle = { 0 };
void *_serverThreadStatus = NULL;
#define CLIENT_OVERFLOW 8
static ENetHost *_server = NULL;
static pthread_t _serverThreadHandle = { 0 };
static void *_serverThreadStatus = NULL;
static uint32_t _serverMaxClients = 0;
static uint32_t _serverClients = 0;
static pthread_mutex_t _serverMutex = { 0 };
static void serverPacketSender(char *data, uint32_t length, void *userData);
@ -46,6 +52,10 @@ void serverDisconnectClient(ClientThreadT *client) {
pthread_join(client->threadHandle, &status);
// Hang up.
enet_peer_reset(peer);
// Adjust client count.
pthread_mutex_lock(&_serverMutex);
_serverClients--;
pthread_mutex_unlock(&_serverMutex);
}
@ -61,6 +71,7 @@ static void serverPacketSender(char *data, uint32_t length, void *userData) {
void serverShutdown(void) {
pthread_join(_serverThreadHandle, &_serverThreadStatus);
pthread_mutex_destroy(&_serverMutex);
enet_host_destroy(_server);
}
@ -69,21 +80,25 @@ void serverStartup(uint32_t port, uint32_t maxClients) {
ENetAddress address = { 0 };
pthread_attr_t serverThreadAttributes = { 0 };
_serverMaxClients = maxClients;
// Tell the packet code how to send packets.
packetSenderRegister(serverPacketSender);
// Set up listening socket.
address.host = ENET_HOST_ANY;
address.port = port;
_server = enet_host_create(&address, /* the address to bind the server host to */
maxClients, /* allow up to XX clients and/or outgoing connections */
1, /* allow up to 2 channels to be used, 0 and 1 */
0, /* assume any amount of incoming bandwidth */
0 /* assume any amount of outgoing bandwidth */
_server = enet_host_create(&address, /* the address to bind the server host to */
maxClients + CLIENT_OVERFLOW, /* allow up to XX clients and/or outgoing connections (some extra so we can send "server full" messages) */
1, /* allow up to 2 channels to be used, 0 and 1 */
0, /* assume any amount of incoming bandwidth */
0 /* assume any amount of outgoing bandwidth */
);
if (_server == NULL) utilDie("Unable to open server listening port.\n");
// Start server thread.
memset(&_serverMutex, 0, sizeof(pthread_mutex_t));
pthread_mutex_init(&_serverMutex, NULL);
if (pthread_attr_init(&serverThreadAttributes) != 0) utilDie("Unable to create server thread attributes.\n");
pthread_attr_setdetachstate(&serverThreadAttributes, PTHREAD_CREATE_JOINABLE);
if (pthread_create(&_serverThreadHandle, &serverThreadAttributes, serverThread, (void *)_server) != 0) utilDie("Unable to start server thread.\n");
@ -99,21 +114,35 @@ void *serverThread(void *data) {
size_t i = 0;
char buffer[2048] = { 0 };
while (_running) {
while (__running) {
while (enet_host_service(server, &event, 1) > 0) {
switch (event.type) {
case ENET_EVENT_TYPE_NONE:
break;
case ENET_EVENT_TYPE_CONNECT:
// Is the server full?
if (_serverClients >= _serverMaxClients) {
// Send full banner to client.
packet = enet_packet_create("KPMPGSMKII\rFULL\r", 16, ENET_PACKET_FLAG_RELIABLE);
enet_peer_send(event.peer, 0, packet);
break;
}
// Create new client.
pthread_mutex_lock(&_serverMutex);
_serverClients++;
pthread_mutex_unlock(&_serverMutex);
NEW(ClientThreadT, client);
if (!client) utilDie("Unable to allocate new client.\n");
// System Stuff.
client->packetThreadData = packetThreadDataCreate(event.peer);
if (!client->packetThreadData) {
utilDie("Unable to allocate packetThreadData for new client.\n");
break; // This break silences an invalid clang warning.
}
for (i=0; i<PING_STATS_SIZE; i++) client->pingStats[i] = 0;
memset(&client->packetQueueMutex, 0, sizeof(pthread_mutex_t));
pthread_mutex_init(&client->packetQueueMutex, NULL);
client->threadIndex = nextIndex++;
client->running = 1;
client->packetQueue = NULL;
@ -122,9 +151,8 @@ void *serverThread(void *data) {
client->pingAverage = 0;
client->pingHigh = 0;
client->pingLow = 9999;
for (i=0; i<PING_STATS_SIZE; i++) client->pingStats[i] = 0;
memset(&client->packetQueueMutex, 1, sizeof(pthread_mutex_t));
pthread_mutex_init(&client->packetQueueMutex, NULL);
// User State Stuff.
client->authenticated = 0;
// Keep our client in the peer data for later.
event.peer->data = (void *)client;
// Make new thread for this client.

View file

@ -18,6 +18,9 @@
*/
// ***TODO*** On DOS, open and close the log on every write so the file gets updated and we still have data on a crash.
#include "log.h"
@ -27,6 +30,7 @@ static char *_logBuffer = NULL;
static uint16_t _logBufferSize = 0;
static logCallback _logCallback = NULL;
void logCallbackSet(logCallback callback) {
_logCallback = callback;
}

View file

@ -41,11 +41,13 @@ typedef enum PacketTypeE {
// Packets received by only the server:
PACKET_TYPE_CLIENT_SHUTDOWN,
PACKET_TYPE_FILE_REQUEST,
PACKET_TYPE_LOGIN,
PACKET_TYPE_PONG,
PACKET_TYPE_SIGNUP,
// Packets received by only the client:
PACKET_TYPE_FILE_RESPONSE,
PACKET_TYPE_LOGIN_RESULT,
PACKET_TYPE_NUMBER,
PACKET_TYPE_PING,
@ -58,5 +60,13 @@ typedef enum PacketTypeE {
PACKET_TYPE_COUNT
} PacketTypeT;
typedef enum FileRequestsE {
FILE_REQUEST_UNKNOWN = 0,
FILE_REQUEST_CACHE_CHECK,
FILE_REQUEST_OPEN,
FILE_REQUEST_READ,
FILE_REQUEST_CLOSE
} FileRequestsT;
#endif // PACKETS_H