--- /dev/null
+#ifndef GAMESERVER_DB_H
+#define GAMESERVER_DB_H
+
+#include "vg/vg_log.h"
+#include "network_common.h"
+#include "dep/sqlite3/sqlite3.h"
+#include "highscores.h"
+
+#define DB_COURSE_UID_MAX 32
+#define DB_TABLE_UID_MAX (ADDON_UID_MAX+DB_COURSE_UID_MAX+32)
+#define DB_CRASH_ON_SQLITE_ERROR
+#define DB_LOG_SQL_STATEMENTS
+
+struct {
+ sqlite3 *db;
+}
+static database;
+
+/*
+ * Log the error code (or carry on if its OK).
+ */
+static void log_sqlite3( int code ){
+ if( code == SQLITE_OK ) return;
+ vg_print_backtrace();
+ vg_error( "sqlite3(%d): %s\n", code, sqlite3_errstr(code) );
+
+#ifdef DB_CRASH_ON_SQLITE_ERROR
+ int crash = *((int*)2);
+#endif
+}
+
+/*
+ * Perpare statement and auto throw away if fails. Returns NULL on failure.
+ */
+static sqlite3_stmt *db_stmt( const char *code ){
+#ifdef DB_LOG_SQL_STATEMENTS
+ vg_low( code );
+#endif
+
+ sqlite3_stmt *stmt;
+ int fc = sqlite3_prepare_v2( database.db, code, -1, &stmt, NULL );
+
+ if( fc != SQLITE_OK ){
+ log_sqlite3( fc );
+ sqlite3_finalize( stmt );
+ return NULL;
+ }
+
+ return stmt;
+}
+
+/*
+ * bind zero terminated string
+ */
+static int db_sqlite3_bind_sz( sqlite3_stmt *stmt, int pos, const char *sz ){
+ return sqlite3_bind_text( stmt, pos, sz, -1, SQLITE_STATIC );
+}
+
+/*
+ * Allowed characters in sqlite table names. We use "" as delimiters.
+ */
+static int db_verify_charset( const char *str, int mincount ){
+ for( int i=0; ; i++ ){
+ char c = str[i];
+ if( c == '\0' ){
+ if( i < mincount ) return 0;
+ else return 1;
+ }
+
+ if( !((c==' ')||(c=='!')||(c>='#'&&c<='~')) ) return 0;
+ }
+
+ return 0;
+}
+
+/*
+ * Find table name from mod UID and course UID, plus the week number
+ */
+static int db_get_highscore_table_name( char mod_uid[ADDON_UID_MAX],
+ char run_uid[DB_COURSE_UID_MAX],
+ u32 week,
+ char table_name[DB_TABLE_UID_MAX] ){
+ if( !db_verify_charset( mod_uid, 13 ) ||
+ !db_verify_charset( run_uid, 1 ) ) return 0;
+
+ vg_str a;
+ vg_strnull( &a, table_name, DB_TABLE_UID_MAX );
+ vg_strcat( &a, mod_uid );
+ vg_strcat( &a, ":" );
+ vg_strcat( &a, run_uid );
+
+ if( week ){
+ vg_strcat( &a, "#" );
+ vg_strcati32( &a, week );
+ }
+
+ return vg_strgood( &a );
+}
+
+/*
+ * Read value from highscore table. If not found or error, returns 0
+ */
+static i32 db_readusertime( char table[DB_TABLE_UID_MAX], u64 steamid ){
+ char buf[ 512 ];
+ vg_str q;
+ vg_strnull( &q, buf, 512 );
+ vg_strcat( &q, "SELECT time FROM \"" );
+ vg_strcat( &q, table );
+ vg_strcat( &q, "\" WHERE steamid = ?;" );
+ if( !vg_strgood(&q) ) return 0;
+
+ sqlite3_stmt *stmt = db_stmt( q.buffer );
+ sqlite3_bind_int64( stmt, 1, *((i64 *)&steamid) );
+
+ if( stmt ){
+ int fc = sqlite3_step( stmt );
+
+ i32 result = 0;
+
+ if( fc == SQLITE_ROW )
+ result = sqlite3_column_int( stmt, 0 );
+ else if( fc != SQLITE_DONE )
+ log_sqlite3(fc);
+
+ sqlite3_finalize( stmt );
+ return result;
+ }
+ else return 0;
+}
+
+/*
+ * Write to highscore table
+ */
+static int db_writeusertime( char table[DB_TABLE_UID_MAX], u64 steamid,
+ i32 score, int only_if_faster ){
+ /* auto create table
+ * ------------------------------------------*/
+ char buf[ 512 ];
+ vg_str q;
+ vg_strnull( &q, buf, 512 );
+ vg_strcat( &q, "CREATE TABLE IF NOT EXISTS \n \"" );
+ vg_strcat( &q, table );
+ vg_strcat( &q, "\"\n (steamid BIGINT PRIMARY KEY, time INT);" );
+ if( !vg_strgood(&q) ) return 0;
+
+ vg_str str;
+ sqlite3_stmt *create_table = db_stmt( q.buffer );
+
+ if( create_table ){
+ db_sqlite3_bind_sz( create_table, 1, table );
+
+ int fc = sqlite3_step( create_table );
+ sqlite3_finalize( create_table );
+ if( fc != SQLITE_DONE )
+ return 0;
+ }
+ else return 0;
+
+ if( only_if_faster ){
+ i32 current = db_readusertime( table, steamid );
+ if( (current != 0) && (score > current) )
+ return 1;
+ }
+
+ /* insert score
+ * -------------------------------------------------*/
+ vg_strnull( &q, buf, 512 );
+ vg_strcat( &q, "REPLACE INTO \"" );
+ vg_strcat( &q, table );
+ vg_strcat( &q, "\"(steamid,time)\n VALUES (?,?);" );
+ if( !vg_strgood(&q) ) return 0;
+
+ sqlite3_stmt *stmt = db_stmt( q.buffer );
+
+ if( stmt ){
+ sqlite3_bind_int64( stmt, 1, *((i64 *)&steamid) );
+ sqlite3_bind_int( stmt, 2, score );
+
+ int fc = sqlite3_step( stmt );
+ sqlite3_finalize( stmt );
+ if( fc != SQLITE_DONE )
+ return 0;
+ else
+ return 1;
+ }
+ else return 0;
+}
+
+/*
+ * Set username and type
+ */
+static int db_updateuser( u64 steamid, const char *username, int admin ){
+ sqlite3_stmt *stmt = db_stmt(
+ "INSERT OR REPLACE INTO users (steamid, name, type) "
+ "VALUES (?,?,?);" );
+
+ if( stmt ){
+ sqlite3_bind_int64( stmt, 1, *((i64*)(&steamid)) );
+ db_sqlite3_bind_sz( stmt, 2, username );
+ sqlite3_bind_int( stmt, 3, admin );
+
+ int fc = sqlite3_step( stmt );
+ sqlite3_finalize(stmt);
+
+ if( fc == SQLITE_DONE ){
+ vg_success( "Inserted %lu (%s), type: %d\n",
+ steamid, username, admin );
+ return 1;
+ }
+ else{
+ log_sqlite3( fc );
+ return 0;
+ }
+ }
+ else return 0;
+}
+
+/*
+ * Create database connection and users table
+ */
+static int db_init(void){
+ int rc = sqlite3_open( "highscores.db", &database.db );
+
+ if( rc ){
+ vg_error( "database failure: %s\n", sqlite3_errmsg(database.db) );
+ sqlite3_close( database.db );
+ return 0;
+ }
+
+ sqlite3_stmt *stmt = db_stmt(
+ "CREATE TABLE IF NOT EXISTS \n"
+ " users(steamid BIGINT PRIMARY KEY, name VARCHAR(128), type INT);" );
+
+ if( stmt ){
+ int fc = sqlite3_step( stmt );
+ sqlite3_finalize(stmt);
+
+ if( fc == SQLITE_DONE ){
+ vg_success( "Created users table\n" );
+ db_updateuser( 76561198072130043, "harry", 2 );
+ return 1;
+ }
+ else{
+ log_sqlite3( fc );
+ return 0;
+ }
+ }
+ else return 0;
+}
+
+static void db_free(void){
+ sqlite3_close( database.db );
+}
+
+#endif /* GAMESERVER_DB_H */