From d04acc06eb6e2862c1860fd997101204a4fd99df Mon Sep 17 00:00:00 2001 From: Sky Johnson Date: Fri, 8 Aug 2025 22:37:20 -0500 Subject: [PATCH] finish install command --- .gitignore | 3 + cmd/main.go | 7 - go.mod | 17 +- go.sum | 56 ++++ internal/install/install.go | 510 ++++++++++++++++++++++++++++++++++ internal/password/password.go | 83 ++++++ internal/server/server.go | 19 ++ main.go | 40 +++ 8 files changed, 727 insertions(+), 8 deletions(-) create mode 100644 .gitignore delete mode 100644 cmd/main.go create mode 100644 internal/install/install.go create mode 100644 internal/password/password.go create mode 100644 internal/server/server.go create mode 100644 main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6e162f4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +# Dragon Knight test/build files +/dk +/dk.db \ No newline at end of file diff --git a/cmd/main.go b/cmd/main.go deleted file mode 100644 index bee84bb..0000000 --- a/cmd/main.go +++ /dev/null @@ -1,7 +0,0 @@ -package main - -import "fmt" - -func main() { - fmt.Println("Main!") -} diff --git a/go.mod b/go.mod index be2d9f6..702b583 100644 --- a/go.mod +++ b/go.mod @@ -2,10 +2,25 @@ module dk go 1.24.6 -require github.com/valyala/fasthttp v1.64.0 +require ( + github.com/valyala/fasthttp v1.64.0 + zombiezen.com/go/sqlite v1.4.2 +) require ( github.com/andybalholm/brotli v1.2.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/klauspost/compress v1.18.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/ncruces/go-strftime v0.1.9 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect + golang.org/x/crypto v0.41.0 // indirect + golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect + golang.org/x/sys v0.35.0 // indirect + modernc.org/libc v1.65.7 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.11.0 // indirect + modernc.org/sqlite v1.37.1 // indirect ) diff --git a/go.sum b/go.sum index 3d99259..f89f5a2 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,66 @@ github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.64.0 h1:QBygLLQmiAyiXuRhthf0tuRkqAFcrC42dckN2S+N3og= github.com/valyala/fasthttp v1.64.0/go.mod h1:dGmFxwkWXSK0NbOSJuF7AMVzU+lkHz0wQVvVITv2UQA= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= +golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= +golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM= +golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8= +golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= +golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= +golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= +golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= +golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= +modernc.org/cc/v4 v4.26.1 h1:+X5NtzVBn0KgsBCBe+xkDC7twLb/jNVj9FPgiwSQO3s= +modernc.org/cc/v4 v4.26.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= +modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU= +modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE= +modernc.org/fileutil v1.3.1 h1:8vq5fe7jdtEvoCf3Zf9Nm0Q05sH6kGx0Op2CPx1wTC8= +modernc.org/fileutil v1.3.1/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= +modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= +modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/libc v1.65.7 h1:Ia9Z4yzZtWNtUIuiPuQ7Qf7kxYrxP1/jeHZzG8bFu00= +modernc.org/libc v1.65.7/go.mod h1:011EQibzzio/VX3ygj1qGFt5kMjP0lHb0qCW5/D/pQU= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= +modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= +modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= +modernc.org/sqlite v1.37.1 h1:EgHJK/FPoqC+q2YBXg7fUmES37pCHFc97sI7zSayBEs= +modernc.org/sqlite v1.37.1/go.mod h1:XwdRtsE1MpiBcL54+MbKcaDvcuej+IYSMfLN6gSKV8g= +modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= +modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +zombiezen.com/go/sqlite v1.4.2 h1:KZXLrBuJ7tKNEm+VJcApLMeQbhmAUOKA5VWS93DfFRo= +zombiezen.com/go/sqlite v1.4.2/go.mod h1:5Kd4taTAD4MkBzT25mQ9uaAlLjyR0rFhsR6iINO70jc= diff --git a/internal/install/install.go b/internal/install/install.go new file mode 100644 index 0000000..ab4e6d7 --- /dev/null +++ b/internal/install/install.go @@ -0,0 +1,510 @@ +package install + +import ( + "fmt" + "time" + + "dk/internal/password" + + "zombiezen.com/go/sqlite" + "zombiezen.com/go/sqlite/sqlitex" +) + +const dbPath = "dk.db" + +func Run() error { + fmt.Println("Dragon Knight Installation") + fmt.Println("==========================") + + start := time.Now() + + // Open database connection + conn, err := sqlite.OpenConn(dbPath, sqlite.OpenCreate|sqlite.OpenReadWrite|sqlite.OpenWAL) + if err != nil { + return fmt.Errorf("failed to open database: %w", err) + } + defer conn.Close() + + // Create tables + if err := createTables(conn); err != nil { + return fmt.Errorf("failed to create tables: %w", err) + } + + // Populate initial data + if err := populateData(conn); err != nil { + return fmt.Errorf("failed to populate data: %w", err) + } + + // Create demo user + if err := createDemoUser(conn); err != nil { + return fmt.Errorf("failed to create demo user: %w", err) + } + + elapsed := time.Since(start) + fmt.Printf("\nDatabase setup complete in %.4f seconds.\n", elapsed.Seconds()) + fmt.Println("Demo user created:") + fmt.Println(" Username: demo") + fmt.Println(" Password: Demo123!") + fmt.Println(" Email: demo@demo.com") + fmt.Println("\nInstallation complete!") + + return nil +} + +func createTables(conn *sqlite.Conn) error { + tables := []struct { + name string + sql string + }{ + {"babble", `CREATE TABLE babble ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + posted INTEGER NOT NULL DEFAULT (unixepoch()), + author TEXT NOT NULL DEFAULT '', + babble TEXT NOT NULL DEFAULT '' + )`}, + {"control", `CREATE TABLE control ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + world_size INTEGER NOT NULL DEFAULT 250, + open INTEGER NOT NULL DEFAULT 1, + admin_email TEXT NOT NULL DEFAULT '', + class_1_name TEXT NOT NULL DEFAULT '', + class_2_name TEXT NOT NULL DEFAULT '', + class_3_name TEXT NOT NULL DEFAULT '' + )`}, + {"drops", `CREATE TABLE drops ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL DEFAULT '', + level INTEGER NOT NULL DEFAULT 0, + type INTEGER NOT NULL DEFAULT 0, + att1 TEXT NOT NULL DEFAULT '', + att2 TEXT NOT NULL DEFAULT '' + )`}, + {"forum", `CREATE TABLE forum ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + posted INTEGER NOT NULL DEFAULT (unixepoch()), + last_post INTEGER NOT NULL DEFAULT (unixepoch()), + author INTEGER NOT NULL, + parent INTEGER NOT NULL DEFAULT 0, + replies INTEGER NOT NULL DEFAULT 0, + title TEXT NOT NULL, + content TEXT NOT NULL + )`}, + {"items", `CREATE TABLE items ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + type INTEGER NOT NULL DEFAULT 0, + name TEXT NOT NULL, + value INTEGER NOT NULL DEFAULT 0, + att INTEGER NOT NULL DEFAULT 0, + special TEXT NOT NULL DEFAULT '' + )`}, + {"monsters", `CREATE TABLE monsters ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + max_hp INTEGER NOT NULL DEFAULT 0, + max_dmg INTEGER NOT NULL DEFAULT 0, + armor INTEGER NOT NULL DEFAULT 0, + level INTEGER NOT NULL DEFAULT 0, + max_exp INTEGER NOT NULL DEFAULT 0, + max_gold INTEGER NOT NULL DEFAULT 0, + immune INTEGER NOT NULL DEFAULT 0 + )`}, + {"news", `CREATE TABLE news ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + author INTEGER NOT NULL, + posted INTEGER NOT NULL DEFAULT (unixepoch()), + content TEXT NOT NULL + )`}, + {"spells", `CREATE TABLE spells ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + mp INTEGER NOT NULL DEFAULT 0, + attribute INTEGER NOT NULL DEFAULT 0, + type INTEGER NOT NULL DEFAULT 0 + )`}, + {"towns", `CREATE TABLE towns ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + x INTEGER NOT NULL DEFAULT 0, + y INTEGER NOT NULL DEFAULT 0, + inn_cost INTEGER NOT NULL DEFAULT 0, + map_cost INTEGER NOT NULL DEFAULT 0, + tp_cost INTEGER NOT NULL DEFAULT 0, + shop_list TEXT NOT NULL DEFAULT '' + )`}, + {"users", `CREATE TABLE users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT NOT NULL, + password TEXT NOT NULL, + email TEXT NOT NULL, + verified INTEGER NOT NULL DEFAULT 0, + token TEXT NOT NULL DEFAULT '', + registered INTEGER NOT NULL DEFAULT (unixepoch()), + last_online INTEGER NOT NULL DEFAULT (unixepoch()), + auth INTEGER NOT NULL DEFAULT 0, + x INTEGER NOT NULL DEFAULT 0, + y INTEGER NOT NULL DEFAULT 0, + class_id INTEGER NOT NULL DEFAULT 0, + currently TEXT NOT NULL DEFAULT 'In Town', + fighting INTEGER NOT NULL DEFAULT 0, + monster_id INTEGER NOT NULL DEFAULT 0, + monster_hp INTEGER NOT NULL DEFAULT 0, + monster_sleep INTEGER NOT NULL DEFAULT 0, + monster_immune INTEGER NOT NULL DEFAULT 0, + uber_damage INTEGER NOT NULL DEFAULT 0, + uber_defense INTEGER NOT NULL DEFAULT 0, + hp INTEGER NOT NULL DEFAULT 15, + mp INTEGER NOT NULL DEFAULT 0, + tp INTEGER NOT NULL DEFAULT 10, + max_hp INTEGER NOT NULL DEFAULT 15, + max_mp INTEGER NOT NULL DEFAULT 0, + max_tp INTEGER NOT NULL DEFAULT 10, + level INTEGER NOT NULL DEFAULT 1, + gold INTEGER NOT NULL DEFAULT 100, + exp INTEGER NOT NULL DEFAULT 0, + gold_bonus INTEGER NOT NULL DEFAULT 0, + exp_bonus INTEGER NOT NULL DEFAULT 0, + strength INTEGER NOT NULL DEFAULT 5, + dexterity INTEGER NOT NULL DEFAULT 5, + attack INTEGER NOT NULL DEFAULT 5, + defense INTEGER NOT NULL DEFAULT 5, + weapon_id INTEGER NOT NULL DEFAULT 0, + armor_id INTEGER NOT NULL DEFAULT 0, + shield_id INTEGER NOT NULL DEFAULT 0, + slot_1_id INTEGER NOT NULL DEFAULT 0, + slot_2_id INTEGER NOT NULL DEFAULT 0, + slot_3_id INTEGER NOT NULL DEFAULT 0, + weapon_name TEXT NOT NULL DEFAULT '', + armor_name TEXT NOT NULL DEFAULT '', + shield_name TEXT NOT NULL DEFAULT '', + slot_1_name TEXT NOT NULL DEFAULT '', + slot_2_name TEXT NOT NULL DEFAULT '', + slot_3_name TEXT NOT NULL DEFAULT '', + drop_code INTEGER NOT NULL DEFAULT 0, + spells TEXT NOT NULL DEFAULT '', + towns TEXT NOT NULL DEFAULT '' + )`}, + } + + for _, table := range tables { + if err := sqlitex.ExecuteTransient(conn, table.sql, nil); err != nil { + return fmt.Errorf("failed to create %s table: %w", table.name, err) + } + fmt.Printf("✓ %s table created\n", table.name) + } + + return nil +} + +func populateData(conn *sqlite.Conn) error { + if err := sqlitex.ExecuteTransient(conn, + "INSERT INTO control VALUES (1, 250, 1, '', 'Mage', 'Warrior', 'Paladin')", + nil); err != nil { + return fmt.Errorf("failed to populate control table: %w", err) + } + fmt.Println("✓ Control table populated") + + dropsSQL := `INSERT INTO drops VALUES + (1, 'Life Pebble', 1, 1, 'maxhp,10', 'X'), + (2, 'Life Stone', 10, 1, 'maxhp,25', 'X'), + (3, 'Life Rock', 25, 1, 'maxhp,50', 'X'), + (4, 'Magic Pebble', 1, 1, 'maxmp,10', 'X'), + (5, 'Magic Stone', 10, 1, 'maxmp,25', 'X'), + (6, 'Magic Rock', 25, 1, 'maxmp,50', 'X'), + (7, 'Dragon''s Scale', 10, 1, 'defensepower,25', 'X'), + (8, 'Dragon''s Plate', 30, 1, 'defensepower,50', 'X'), + (9, 'Dragon''s Claw', 10, 1, 'attackpower,25', 'X'), + (10, 'Dragon''s Tooth', 30, 1, 'attackpower,50', 'X'), + (11, 'Dragon''s Tear', 35, 1, 'strength,50', 'X'), + (12, 'Dragon''s Wing', 35, 1, 'dexterity,50', 'X'), + (13, 'Demon''s Sin', 35, 1, 'maxhp,-50', 'strength,50'), + (14, 'Demon''s Fall', 35, 1, 'maxmp,-50', 'strength,50'), + (15, 'Demon''s Lie', 45, 1, 'maxhp,-100', 'strength,100'), + (16, 'Demon''s Hate', 45, 1, 'maxmp,-100', 'strength,100'), + (17, 'Angel''s Joy', 25, 1, 'maxhp,25', 'strength,25'), + (18, 'Angel''s Rise', 30, 1, 'maxhp,50', 'strength,50'), + (19, 'Angel''s Truth', 35, 1, 'maxhp,75', 'strength,75'), + (20, 'Angel''s Love', 40, 1, 'maxhp,100', 'strength,100'), + (21, 'Seraph''s Joy', 25, 1, 'maxmp,25', 'dexterity,25'), + (22, 'Seraph''s Rise', 30, 1, 'maxmp,50', 'dexterity,50'), + (23, 'Seraph''s Truth', 35, 1, 'maxmp,75', 'dexterity,75'), + (24, 'Seraph''s Love', 40, 1, 'maxmp,100', 'dexterity,100'), + (25, 'Ruby', 50, 1, 'maxhp,150', 'X'), + (26, 'Pearl', 50, 1, 'maxmp,150', 'X'), + (27, 'Emerald', 50, 1, 'strength,150', 'X'), + (28, 'Topaz', 50, 1, 'dexterity,150', 'X'), + (29, 'Obsidian', 50, 1, 'attackpower,150', 'X'), + (30, 'Diamond', 50, 1, 'defensepower,150', 'X'), + (31, 'Memory Drop', 5, 1, 'expbonus,10', 'X'), + (32, 'Fortune Drop', 5, 1, 'goldbonus,10', 'X')` + if err := sqlitex.ExecuteTransient(conn, dropsSQL, nil); err != nil { + return fmt.Errorf("failed to populate drops table: %w", err) + } + fmt.Println("✓ Drops table populated") + + itemsSQL := `INSERT INTO items VALUES + (1, 1, 'Stick', 10, 2, 'X'), + (2, 1, 'Branch', 30, 4, 'X'), + (3, 1, 'Club', 40, 5, 'X'), + (4, 1, 'Dagger', 90, 8, 'X'), + (5, 1, 'Hatchet', 150, 12, 'X'), + (6, 1, 'Axe', 200, 16, 'X'), + (7, 1, 'Brand', 300, 25, 'X'), + (8, 1, 'Poleaxe', 500, 35, 'X'), + (9, 1, 'Broadsword', 800, 45, 'X'), + (10, 1, 'Battle Axe', 1200, 50, 'X'), + (11, 1, 'Claymore', 2000, 60, 'X'), + (12, 1, 'Dark Axe', 3000, 100, 'expbonus,-5'), + (13, 1, 'Dark Sword', 4500, 125, 'expbonus,-10'), + (14, 1, 'Bright Sword', 6000, 100, 'expbonus,10'), + (15, 1, 'Magic Sword', 10000, 150, 'maxmp,50'), + (16, 1, 'Destiny Blade', 50000, 250, 'strength,50'), + (17, 2, 'Skivvies', 25, 2, 'goldbonus,10'), + (18, 2, 'Clothes', 50, 5, 'X'), + (19, 2, 'Leather Armor', 75, 10, 'X'), + (20, 2, 'Hard Leather Armor', 150, 25, 'X'), + (21, 2, 'Chain Mail', 300, 30, 'X'), + (22, 2, 'Bronze Plate', 900, 50, 'X'), + (23, 2, 'Iron Plate', 2000, 100, 'X'), + (24, 2, 'Magic Armor', 4000, 125, 'maxmp,50'), + (25, 2, 'Dark Armor', 5000, 150, 'expbonus,-10'), + (26, 2, 'Bright Armor', 10000, 175, 'expbonus,10'), + (27, 2, 'Destiny Raiment', 50000, 200, 'dexterity,50'), + (28, 3, 'Reed Shield', 50, 2, 'X'), + (29, 3, 'Buckler', 100, 4, 'X'), + (30, 3, 'Small Shield', 500, 10, 'X'), + (31, 3, 'Large Shield', 2500, 30, 'X'), + (32, 3, 'Silver Shield', 10000, 60, 'X'), + (33, 3, 'Destiny Aegis', 25000, 100, 'maxhp,50')` + if err := sqlitex.ExecuteTransient(conn, itemsSQL, nil); err != nil { + return fmt.Errorf("failed to populate items table: %w", err) + } + fmt.Println("✓ Items table populated") + + monstersSQL := `INSERT INTO monsters VALUES + (1, 'Blue Slime', 4, 3, 1, 1, 1, 1, 0), + (2, 'Red Slime', 6, 5, 1, 1, 2, 1, 0), + (3, 'Critter', 6, 5, 2, 1, 4, 2, 0), + (4, 'Creature', 10, 8, 2, 2, 4, 2, 0), + (5, 'Shadow', 10, 9, 3, 2, 6, 2, 1), + (6, 'Drake', 11, 10, 3, 2, 8, 3, 0), + (7, 'Shade', 12, 10, 3, 3, 10, 3, 1), + (8, 'Drakelor', 14, 12, 4, 3, 10, 3, 0), + (9, 'Silver Slime', 15, 100, 200, 30, 15, 1000, 2), + (10, 'Scamp', 16, 13, 5, 4, 15, 5, 0), + (11, 'Raven', 16, 13, 5, 4, 18, 6, 0), + (12, 'Scorpion', 18, 14, 6, 5, 20, 7, 0), + (13, 'Illusion', 20, 15, 6, 5, 20, 7, 1), + (14, 'Nightshade', 22, 16, 6, 6, 24, 8, 0), + (15, 'Drakemal', 22, 18, 7, 6, 24, 8, 0), + (16, 'Shadow Raven', 24, 18, 7, 6, 26, 9, 1), + (17, 'Ghost', 24, 20, 8, 6, 28, 9, 0), + (18, 'Frost Raven', 26, 20, 8, 7, 30, 10, 0), + (19, 'Rogue Scorpion', 28, 22, 9, 7, 32, 11, 0), + (20, 'Ghoul', 29, 24, 9, 7, 34, 11, 0), + (21, 'Magician', 30, 24, 10, 8, 36, 12, 0), + (22, 'Rogue', 30, 25, 12, 8, 40, 13, 0), + (23, 'Drakefin', 32, 26, 12, 8, 40, 13, 0), + (24, 'Shimmer', 32, 26, 14, 8, 45, 15, 1), + (25, 'Fire Raven', 34, 28, 14, 9, 45, 15, 0), + (26, 'Dybbuk', 34, 28, 14, 9, 50, 17, 0), + (27, 'Knave', 36, 30, 15, 9, 52, 17, 0), + (28, 'Goblin', 36, 30, 15, 10, 54, 18, 0), + (29, 'Skeleton', 38, 30, 18, 10, 58, 19, 0), + (30, 'Dark Slime', 38, 32, 18, 10, 62, 21, 0), + (31, 'Silver Scorpion', 30, 160, 350, 40, 63, 2000, 2), + (32, 'Mirage', 40, 32, 20, 11, 64, 21, 1), + (33, 'Sorceror', 41, 33, 22, 11, 68, 23, 0), + (34, 'Imp', 42, 34, 22, 12, 70, 23, 0), + (35, 'Nymph', 43, 35, 22, 12, 70, 23, 0), + (36, 'Scoundrel', 43, 35, 22, 12, 75, 25, 0), + (37, 'Megaskeleton', 44, 36, 24, 13, 78, 26, 0), + (38, 'Grey Wolf', 44, 36, 24, 13, 82, 27, 0), + (39, 'Phantom', 46, 38, 24, 14, 85, 28, 1), + (40, 'Specter', 46, 38, 24, 14, 90, 30, 0), + (41, 'Dark Scorpion', 48, 40, 26, 15, 95, 32, 1), + (42, 'Warlock', 48, 40, 26, 15, 100, 33, 1), + (43, 'Orc', 49, 42, 28, 15, 104, 35, 0), + (44, 'Sylph', 49, 42, 28, 15, 106, 35, 0), + (45, 'Wraith', 50, 45, 30, 16, 108, 36, 0), + (46, 'Hellion', 50, 45, 30, 16, 110, 37, 0), + (47, 'Bandit', 52, 45, 30, 16, 114, 38, 0), + (48, 'Ultraskeleton', 52, 46, 32, 16, 116, 39, 0), + (49, 'Dark Wolf', 54, 47, 36, 17, 120, 40, 1), + (50, 'Troll', 56, 48, 36, 17, 120, 40, 0), + (51, 'Werewolf', 56, 48, 38, 17, 124, 41, 0), + (52, 'Hellcat', 58, 50, 38, 18, 128, 43, 0), + (53, 'Spirit', 58, 50, 38, 18, 132, 44, 0), + (54, 'Nisse', 60, 52, 40, 19, 132, 44, 0), + (55, 'Dawk', 60, 54, 40, 19, 136, 45, 0), + (56, 'Figment', 64, 55, 42, 19, 140, 47, 1), + (57, 'Hellhound', 66, 56, 44, 20, 140, 47, 0), + (58, 'Wizard', 66, 56, 44, 20, 144, 48, 0), + (59, 'Uruk', 68, 58, 44, 20, 146, 49, 0), + (60, 'Siren', 68, 400, 800, 50, 10000, 50, 2), + (61, 'Megawraith', 70, 60, 46, 21, 155, 52, 0), + (62, 'Dawkin', 70, 60, 46, 21, 155, 52, 0), + (63, 'Grey Bear', 70, 62, 48, 21, 160, 53, 0), + (64, 'Haunt', 72, 62, 48, 22, 160, 53, 0), + (65, 'Hellbeast', 74, 64, 50, 22, 165, 55, 0), + (66, 'Fear', 76, 66, 52, 23, 165, 55, 0), + (67, 'Beast', 76, 66, 52, 23, 170, 57, 0), + (68, 'Ogre', 78, 68, 54, 23, 170, 57, 0), + (69, 'Dark Bear', 80, 70, 56, 24, 175, 58, 1), + (70, 'Fire', 80, 72, 56, 24, 175, 58, 0), + (71, 'Polgergeist', 84, 74, 58, 25, 180, 60, 0), + (72, 'Fright', 86, 76, 58, 25, 180, 60, 0), + (73, 'Lycan', 88, 78, 60, 25, 185, 62, 0), + (74, 'Terra Elemental', 88, 80, 62, 25, 185, 62, 1), + (75, 'Necromancer', 90, 80, 62, 26, 190, 63, 0), + (76, 'Ultrawraith', 90, 82, 64, 26, 190, 63, 0), + (77, 'Dawkor', 92, 82, 64, 26, 195, 65, 0), + (78, 'Werebear', 92, 84, 65, 26, 195, 65, 0), + (79, 'Brute', 94, 84, 65, 27, 200, 67, 0), + (80, 'Large Beast', 96, 88, 66, 27, 200, 67, 0), + (81, 'Horror', 96, 88, 68, 27, 210, 70, 0), + (82, 'Flame', 100, 90, 70, 28, 210, 70, 0), + (83, 'Lycanthor', 100, 90, 70, 28, 210, 70, 0), + (84, 'Wyrm', 100, 92, 72, 28, 220, 73, 0), + (85, 'Aero Elemental', 104, 94, 74, 29, 220, 73, 1), + (86, 'Dawkare', 106, 96, 76, 29, 220, 73, 0), + (87, 'Large Brute', 108, 98, 78, 29, 230, 77, 0), + (88, 'Frost Wyrm', 110, 100, 80, 30, 230, 77, 0), + (89, 'Knight', 110, 102, 80, 30, 240, 80, 0), + (90, 'Lycanthra', 112, 104, 82, 30, 240, 80, 0), + (91, 'Terror', 115, 108, 84, 31, 250, 83, 0), + (92, 'Blaze', 118, 108, 84, 31, 250, 83, 0), + (93, 'Aqua Elemental', 120, 110, 90, 31, 260, 87, 1), + (94, 'Fire Wyrm', 120, 110, 90, 32, 260, 87, 0), + (95, 'Lesser Wyvern', 122, 110, 92, 32, 270, 90, 0), + (96, 'Doomer', 124, 112, 92, 32, 270, 90, 0), + (97, 'Armor Knight', 130, 115, 95, 33, 280, 93, 0), + (98, 'Wyvern', 134, 120, 95, 33, 290, 97, 0), + (99, 'Nightmare', 138, 125, 100, 33, 300, 100, 0), + (100, 'Fira Elemental', 140, 125, 100, 34, 310, 103, 1), + (101, 'Megadoomer', 140, 128, 105, 34, 320, 107, 0), + (102, 'Greater Wyvern', 145, 130, 105, 34, 335, 112, 0), + (103, 'Advocate', 148, 132, 108, 35, 350, 117, 0), + (104, 'Strong Knight', 150, 135, 110, 35, 365, 122, 0), + (105, 'Liche', 150, 135, 110, 35, 380, 127, 0), + (106, 'Ultradoomer', 155, 140, 115, 36, 395, 132, 0), + (107, 'Fanatic', 160, 140, 115, 36, 410, 137, 0), + (108, 'Green Dragon', 160, 140, 115, 36, 425, 142, 0), + (109, 'Fiend', 160, 145, 120, 37, 445, 148, 0), + (110, 'Greatest Wyvern', 162, 150, 120, 37, 465, 155, 0), + (111, 'Lesser Devil', 164, 150, 120, 37, 485, 162, 0), + (112, 'Liche Master', 168, 155, 125, 38, 505, 168, 0), + (113, 'Zealot', 168, 155, 125, 38, 530, 177, 0), + (114, 'Serafiend', 170, 155, 125, 38, 555, 185, 0), + (115, 'Pale Knight', 175, 160, 130, 39, 580, 193, 0), + (116, 'Blue Dragon', 180, 160, 130, 39, 605, 202, 0), + (117, 'Obsessive', 180, 160, 135, 40, 630, 210, 0), + (118, 'Devil', 184, 164, 135, 40, 666, 222, 0), + (119, 'Liche Prince', 190, 168, 138, 40, 660, 220, 0), + (120, 'Cherufiend', 195, 170, 140, 41, 690, 230, 0), + (121, 'Red Dragon', 200, 180, 145, 41, 720, 240, 0), + (122, 'Greater Devil', 200, 180, 145, 41, 750, 250, 0), + (123, 'Renegade', 205, 185, 150, 42, 780, 260, 0), + (124, 'Archfiend', 210, 190, 150, 42, 810, 270, 0), + (125, 'Liche Lord', 210, 190, 155, 42, 850, 283, 0), + (126, 'Greatest Devil', 215, 195, 160, 43, 890, 297, 0), + (127, 'Dark Knight', 220, 200, 160, 43, 930, 310, 0), + (128, 'Giant', 220, 200, 165, 43, 970, 323, 0), + (129, 'Shadow Dragon', 225, 200, 170, 44, 1010, 337, 0), + (130, 'Liche King', 225, 205, 170, 44, 1050, 350, 0), + (131, 'Incubus', 230, 205, 175, 44, 1100, 367, 1), + (132, 'Traitor', 230, 205, 175, 45, 1150, 383, 0), + (133, 'Demon', 240, 210, 180, 45, 1200, 400, 0), + (134, 'Dark Dragon', 245, 215, 180, 45, 1250, 417, 1), + (135, 'Insurgent', 250, 220, 190, 46, 1300, 433, 0), + (136, 'Leviathan', 255, 225, 190, 46, 1350, 450, 0), + (137, 'Grey Daemon', 260, 230, 190, 46, 1400, 467, 0), + (138, 'Succubus', 265, 240, 200, 47, 1460, 487, 1), + (139, 'Demon Prince', 270, 240, 200, 47, 1520, 507, 0), + (140, 'Black Dragon', 275, 250, 205, 47, 1580, 527, 1), + (141, 'Nihilist', 280, 250, 205, 47, 1640, 547, 0), + (142, 'Behemoth', 285, 260, 210, 48, 1700, 567, 0), + (143, 'Demagogue', 290, 260, 210, 48, 1760, 587, 0), + (144, 'Demon Lord', 300, 270, 220, 48, 1820, 607, 0), + (145, 'Red Daemon', 310, 280, 230, 48, 1880, 627, 0), + (146, 'Colossus', 320, 300, 240, 49, 1940, 647, 0), + (147, 'Demon King', 330, 300, 250, 49, 2000, 667, 0), + (148, 'Dark Daemon', 340, 320, 260, 49, 2200, 733, 1), + (149, 'Titan', 360, 340, 270, 50, 2400, 800, 0), + (150, 'Black Daemon', 400, 400, 280, 50, 3000, 1000, 1), + (151, 'Lucifuge', 600, 600, 400, 50, 10000, 10000, 2)` + if err := sqlitex.ExecuteTransient(conn, monstersSQL, nil); err != nil { + return fmt.Errorf("failed to populate monsters table: %w", err) + } + fmt.Println("✓ Monsters table populated (sample data)") + + // News table + if err := sqlitex.ExecuteTransient(conn, + "INSERT INTO news (author, content) VALUES (1, 'Welcome to Dragon Knight! This is your first news post.')", + nil); err != nil { + return fmt.Errorf("failed to populate news table: %w", err) + } + fmt.Println("✓ News table populated") + + // Spells table + spellsSQL := `INSERT INTO spells VALUES + (1, 'Heal', 5, 10, 1), + (2, 'Revive', 10, 25, 1), + (3, 'Life', 25, 50, 1), + (4, 'Breath', 50, 100, 1), + (5, 'Gaia', 75, 150, 1), + (6, 'Hurt', 5, 15, 2), + (7, 'Pain', 12, 35, 2), + (8, 'Maim', 25, 70, 2), + (9, 'Rend', 40, 100, 2), + (10, 'Chaos', 50, 130, 2), + (11, 'Sleep', 10, 5, 3), + (12, 'Dream', 30, 9, 3), + (13, 'Nightmare', 60, 13, 3), + (14, 'Craze', 10, 10, 4), + (15, 'Rage', 20, 25, 4), + (16, 'Fury', 30, 50, 4), + (17, 'Ward', 10, 10, 5), + (18, 'Fend', 20, 25, 5), + (19, 'Barrier', 30, 50, 5)` + if err := sqlitex.ExecuteTransient(conn, spellsSQL, nil); err != nil { + return fmt.Errorf("failed to populate spells table: %w", err) + } + fmt.Println("✓ Spells table populated") + + // Towns table + townsSQL := `INSERT INTO towns VALUES + (1, 'Midworld', 0, 0, 5, 0, 0, '1,2,3,17,18,19,28,29'), + (2, 'Roma', 30, 30, 10, 25, 5, '2,3,4,18,19,29'), + (3, 'Bris', 70, -70, 25, 50, 15, '2,3,4,5,18,19,20,29.30'), + (4, 'Kalle', -100, 100, 40, 100, 30, '5,6,8,10,12,21,22,23,29,30'), + (5, 'Narcissa', -130, -130, 60, 500, 50, '4,7,9,11,13,21,22,23,29,30,31'), + (6, 'Hambry', 170, 170, 90, 1000, 80, '10,11,12,13,14,23,24,30,31'), + (7, 'Gilead', 200, -200, 100, 3000, 110, '12,13,14,15,24,25,26,32'), + (8, 'Endworld', -250, -250, 125, 9000, 160, '16,27,33')` + if err := sqlitex.ExecuteTransient(conn, townsSQL, nil); err != nil { + return fmt.Errorf("failed to populate towns table: %w", err) + } + fmt.Println("✓ Towns table populated") + + return nil +} + +func createDemoUser(conn *sqlite.Conn) error { + // Hash the password using argon2id + hashedPassword, err := password.Hash("Demo123!") + if err != nil { + return fmt.Errorf("failed to hash password: %w", err) + } + + stmt := `INSERT INTO users (username, password, email, verified, class_id, auth) + VALUES (?, ?, ?, 1, 1, 1)` + + if err := sqlitex.ExecuteTransient(conn, stmt, &sqlitex.ExecOptions{ + Args: []any{"demo", hashedPassword, "demo@demo.com"}, + }); err != nil { + return fmt.Errorf("failed to create demo user: %w", err) + } + + fmt.Println("✓ Demo user created") + return nil +} diff --git a/internal/password/password.go b/internal/password/password.go new file mode 100644 index 0000000..fbda652 --- /dev/null +++ b/internal/password/password.go @@ -0,0 +1,83 @@ +package password + +import ( + "crypto/rand" + "crypto/subtle" + "encoding/base64" + "fmt" + "strings" + + "golang.org/x/crypto/argon2" +) + +const ( + time = 1 + memory = 64 * 1024 + threads = 4 + keyLen = 32 +) + +// Hash creates an argon2id hash of the password +func Hash(password string) (string, error) { + salt := make([]byte, 16) + if _, err := rand.Read(salt); err != nil { + return "", err + } + + hash := argon2.IDKey([]byte(password), salt, time, memory, threads, keyLen) + + // Encode in the format: $argon2id$v=19$m=65536,t=1,p=4$$ + b64Salt := base64.RawStdEncoding.EncodeToString(salt) + b64Hash := base64.RawStdEncoding.EncodeToString(hash) + + encoded := fmt.Sprintf("$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s", + argon2.Version, memory, time, threads, b64Salt, b64Hash) + + return encoded, nil +} + +// Verify checks if a password matches the hash +func Verify(password, encodedHash string) (bool, error) { + parts := strings.Split(encodedHash, "$") + if len(parts) != 6 { + return false, fmt.Errorf("invalid hash format") + } + + if parts[1] != "argon2id" { + return false, fmt.Errorf("invalid hash variant") + } + + var version int + _, err := fmt.Sscanf(parts[2], "v=%d", &version) + if err != nil { + return false, err + } + if version != argon2.Version { + return false, fmt.Errorf("incompatible argon2 version") + } + + var m, t, p uint32 + _, err = fmt.Sscanf(parts[3], "m=%d,t=%d,p=%d", &m, &t, &p) + if err != nil { + return false, err + } + + salt, err := base64.RawStdEncoding.DecodeString(parts[4]) + if err != nil { + return false, err + } + + expectedHash, err := base64.RawStdEncoding.DecodeString(parts[5]) + if err != nil { + return false, err + } + + hash := argon2.IDKey([]byte(password), salt, t, m, uint8(p), uint32(len(expectedHash))) + + // Use constant-time comparison to prevent timing attacks + if subtle.ConstantTimeCompare(hash, expectedHash) == 1 { + return true, nil + } + + return false, nil +} \ No newline at end of file diff --git a/internal/server/server.go b/internal/server/server.go new file mode 100644 index 0000000..0645bcf --- /dev/null +++ b/internal/server/server.go @@ -0,0 +1,19 @@ +package server + +import ( + "fmt" + "log" + + "github.com/valyala/fasthttp" +) + +func Start() error { + // Simple fasthttp server for now + requestHandler := func(ctx *fasthttp.RequestCtx) { + ctx.SetContentType("text/html; charset=utf-8") + fmt.Fprintf(ctx, "

Dragon Knight

Server is running!

") + } + + log.Println("Server starting on :8080") + return fasthttp.ListenAndServe(":8080", requestHandler) +} \ No newline at end of file diff --git a/main.go b/main.go new file mode 100644 index 0000000..f071fc7 --- /dev/null +++ b/main.go @@ -0,0 +1,40 @@ +package main + +import ( + "fmt" + "log" + "os" + + "dk/internal/install" + "dk/internal/server" +) + +func main() { + if len(os.Args) < 2 { + startServer() + return + } + + switch os.Args[1] { + case "install": + if err := install.Run(); err != nil { + log.Fatalf("Installation failed: %v", err) + } + case "serve": + startServer() + default: + fmt.Fprintf(os.Stderr, "Unknown command: %s\n", os.Args[1]) + fmt.Fprintln(os.Stderr, "Available commands:") + fmt.Fprintln(os.Stderr, " install - Install the database") + fmt.Fprintln(os.Stderr, " serve - Start the server") + fmt.Fprintln(os.Stderr, " (no command) - Start the server") + os.Exit(1) + } +} + +func startServer() { + fmt.Println("Starting Dragon Knight server...") + if err := server.Start(); err != nil { + log.Fatalf("Server failed: %v", err) + } +} \ No newline at end of file