From 4ca2ba06ab438b01933aa97bf8029bc6022d6b75 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Fri, 30 Dec 2022 19:04:27 +0300 Subject: [PATCH] some minor refactoring --- .editorconfig | 9 ++ .env.example | 1 + .gitignore | 6 ++ .vscode/settings.json | 6 ++ Cakefile | 16 ++++ LICENSE | 21 ++++ README.md | 23 +++++ db/migrations/1_levels.sql | 47 +++++++++ db/migrations/2_users.sql | 49 ++++++++++ db/migrations/3_accounts.sql | 24 +++++ docs/crystal-gauntlet.jpg | Bin 0 -> 73736 bytes docs/exe-penis.txt | 5 + shard.lock | 22 +++++ shard.yml | 23 +++++ spec/crystal-gauntlet_spec.cr | 9 ++ spec/spec_helper.cr | 2 + src/accounts.cr | 31 ++++++ src/crystal-gauntlet.cr | 56 +++++++++++ src/endpoints/accounts/loginAccount.cr | 27 ++++++ src/endpoints/accounts/registerAccount.cr | 28 ++++++ src/endpoints/levels/downloadLevels.cr | 111 ++++++++++++++++++++++ src/endpoints/levels/getLevels.cr | 93 ++++++++++++++++++ src/endpoints/levels/uploadLevel.cr | 35 +++++++ src/endpoints/users/getUser.cr | 89 +++++++++++++++++ src/endpoints/users/updateUser.cr | 17 ++++ src/enums.cr | 63 ++++++++++++ src/format.cr | 50 ++++++++++ src/gjp.cr | 34 +++++++ src/hash.cr | 56 +++++++++++ 29 files changed, 953 insertions(+) create mode 100644 .editorconfig create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 .vscode/settings.json create mode 100644 Cakefile create mode 100644 LICENSE create mode 100644 README.md create mode 100644 db/migrations/1_levels.sql create mode 100644 db/migrations/2_users.sql create mode 100644 db/migrations/3_accounts.sql create mode 100644 docs/crystal-gauntlet.jpg create mode 100644 docs/exe-penis.txt create mode 100644 shard.lock create mode 100644 shard.yml create mode 100644 spec/crystal-gauntlet_spec.cr create mode 100644 spec/spec_helper.cr create mode 100644 src/accounts.cr create mode 100644 src/crystal-gauntlet.cr create mode 100644 src/endpoints/accounts/loginAccount.cr create mode 100644 src/endpoints/accounts/registerAccount.cr create mode 100644 src/endpoints/levels/downloadLevels.cr create mode 100644 src/endpoints/levels/getLevels.cr create mode 100644 src/endpoints/levels/uploadLevel.cr create mode 100644 src/endpoints/users/getUser.cr create mode 100644 src/endpoints/users/updateUser.cr create mode 100644 src/enums.cr create mode 100644 src/format.cr create mode 100644 src/gjp.cr create mode 100644 src/hash.cr diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..163eb75 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*.cr] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..023fdea --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +DATABASE_URL=sqlite3://./crystalgauntlet.db \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dfc6371 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +/lib/ +/bin/ +/.shards/ +*.dwarf +.env +crystalgauntlet.db \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..91884f0 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "crystal-lang.completion": true, + "crystal-lang.hover": true, + "crystal-lang.implementations": true, + "crystal-lang.mainFile": "${workspaceRoot}/src/crystal-gauntlet.cr" +} \ No newline at end of file diff --git a/Cakefile b/Cakefile new file mode 100644 index 0000000..1873b9c --- /dev/null +++ b/Cakefile @@ -0,0 +1,16 @@ +# todo: move inside executable + +require "log" +require "dotenv" +require "sqlite3" +require "migrate" + +Dotenv.load + +desc "Migrate database to the latest version" +task :dbmigrate do + migrator = Migrate::Migrator.new( + DB.open(ENV["DATABASE_URL"]) + ) + migrator.to_latest +end \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4888d00 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Jill "oatmealine" Monoids + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..ed41011 --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +# crystal-gauntlet + +among balls + +## build + +`shards build` + +you may need to head into `lib/` to fix deps. i'm Very sorry + +## setup + +copy `.env.example` to `.env` and fill it out + +run `cake db:migrate` (must have [cake](https://github.com/axvm/cake/)) + +**schemas are highly unstable so you will be offered 0 support in migrating databases for now**, however in the future you'll want to run this each time you update + +then `bin/crystal-gauntlet` (or `shards run`) + +### real + +![real](docs/crystal-gauntlet.jpg) \ No newline at end of file diff --git a/db/migrations/1_levels.sql b/db/migrations/1_levels.sql new file mode 100644 index 0000000..e9d656b --- /dev/null +++ b/db/migrations/1_levels.sql @@ -0,0 +1,47 @@ +-- +migrate up +CREATE TABLE levels ( + id SERIAL PRIMARY KEY, + created_at TEXT NOT NULL DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'now')), + modified_at TEXT NOT NULL DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'now')), + + name TEXT NOT NULL, + user_id INTEGER NOT NULL references users(id), + description TEXT NOT NULL DEFAULT "", + original INTEGER, + + game_version INTEGER NOT NULL, + binary_version INTEGER NOT NULL, + + password TEXT, + requested_stars INTEGER, + unlisted INTEGER NOT NULL DEFAULT 0, + + version INTEGER NOT NULL DEFAULT 0, + level_data BLOB NOT NULL, + extra_data BLOB NOT NULL, + level_info BLOB NOT NULL, + + -- checksums, presumably + wt1 TEXT NOT NULL, + wt2 TEXT NOT NULL, + + song_id INTEGER NOT NULL, + + length INTEGER NOT NULL, + objects INTEGER NOT NULL, + coins INTEGER NOT NULL DEFAULT 0, + has_ldm INTEGER NOT NULL DEFAULT 0, + two_player INTEGER NOT NULL DEFAULT 0, + + downloads INTEGER NOT NULL DEFAULT 0, + likes INTEGER NOT NULL DEFAULT 0, + difficulty INTEGER, + demon_difficulty INTEGER, + stars INTEGER, + featured INTEGER NOT NULL DEFAULT 0, + epic INTEGER NOT NULL DEFAULT 0, + rated_coins INTEGER NOT NULL DEFAULT 0 +); + +-- +migrate down +DROP TABLE levels; \ No newline at end of file diff --git a/db/migrations/2_users.sql b/db/migrations/2_users.sql new file mode 100644 index 0000000..5a3151e --- /dev/null +++ b/db/migrations/2_users.sql @@ -0,0 +1,49 @@ +-- +migrate up +CREATE TABLE users ( + id SERIAL PRIMARY KEY, + -- on a registered account, account_id refers to the + -- account ID - however, pre 2.0, instead udid referred + -- to the user's UUID or UDID, depending on platform. + -- UUID and UDID are unique ids assigned for green + -- username users + -- + -- in short, if `registered`, use account_id, else, use udid + udid TEXT, + account_id INTEGER references accounts(id), + registered INTEGER NOT NULL, + + username TEXT NOT NULL, + + stars INTEGER NOT NULL DEFAULT 0, + demons INTEGER NOT NULL DEFAULT 0, + coins INTEGER NOT NULL DEFAULT 0, + user_coins INTEGER NOT NULL DEFAULT 0, + diamonds INTEGER NOT NULL DEFAULT 0, + orbs INTEGER NOT NULL DEFAULT 0, + creator_points INTEGER NOT NULL DEFAULT 0, + + completed_levels INTEGER NOT NULL DEFAULT 0, + + icon_type INTEGER NOT NULL DEFAULT 0, -- icon to display in comments, etc + color1 INTEGER NOT NULL DEFAULT 0, + color2 INTEGER NOT NULL DEFAULT 3, + cube INTEGER NOT NULL DEFAULT 0, + ship INTEGER NOT NULL DEFAULT 0, + ball INTEGER NOT NULL DEFAULT 0, + ufo INTEGER NOT NULL DEFAULT 0, + wave INTEGER NOT NULL DEFAULT 0, + robot INTEGER NOT NULL DEFAULT 0, + spider INTEGER NOT NULL DEFAULT 0, + explosion INTEGER NOT NULL DEFAULT 0, + special INTEGER NOT NULL DEFAULT 0, + glow INTEGER NOT NULL DEFAULT 0, + + created_at TEXT NOT NULL DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'now')), + last_played TEXT NOT NULL DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'now')), + + is_banned INTEGER NOT NULL DEFAULT 0, + is_banned_upload INTEGER NOT NULL DEFAULT 0 +); + +-- +migrate down +DROP TABLE users; \ No newline at end of file diff --git a/db/migrations/3_accounts.sql b/db/migrations/3_accounts.sql new file mode 100644 index 0000000..dc68628 --- /dev/null +++ b/db/migrations/3_accounts.sql @@ -0,0 +1,24 @@ +-- +migrate up +CREATE TABLE accounts ( + id SERIAL PRIMARY KEY, + + username TEXT NOT NULL, + password TEXT NOT NULL, -- bcrypt hashed + gjp2 TEXT NOT NULL, + email TEXT NOT NULL, + + is_admin INTEGER NOT NULL DEFAULT 0, + + messages_enabled INTEGER NOT NULL DEFAULT 1, -- messages from non-friends enabled + friend_requests_enabled INTEGER NOT NULL DEFAULT 1, -- frs enabled + comments_enabled INTEGER NOT NULL DEFAULT 0, -- able to see user's comments + + youtube_url TEXT, + twitter_url TEXT, + twitch_url TEXT, + + created_at TEXT NOT NULL DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'now')) +); + +-- +migrate down +DROP TABLE accounts; \ No newline at end of file diff --git a/docs/crystal-gauntlet.jpg b/docs/crystal-gauntlet.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6b22d9745941bae28f5e2fd79d096f2c7a390bcf GIT binary patch literal 73736 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�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 literal 0 HcmV?d00001 diff --git a/docs/exe-penis.txt b/docs/exe-penis.txt new file mode 100644 index 0000000..562a961 --- /dev/null +++ b/docs/exe-penis.txt @@ -0,0 +1,5 @@ +http://www.boomlings.com/database -> +http://localhost:8080/asdfasdfasd + +aHR0cDovL3d3dy5ib29tbGluZ3MuY29tL2RhdGFiYXNl -> base64 http://localhost:8080/asdfasdfasd +aHR0cDovL2xvY2FsaG9zdDo4MDgwL2FzZGZhc2RmYXNk \ No newline at end of file diff --git a/shard.lock b/shard.lock new file mode 100644 index 0000000..07867a2 --- /dev/null +++ b/shard.lock @@ -0,0 +1,22 @@ +version: 2.0 +shards: + db: + git: https://github.com/crystal-lang/crystal-db.git + version: 0.6.0 + + dotenv: + git: https://github.com/gdotdesign/cr-dotenv.git + version: 0.1.0 + + migrate: + git: https://github.com/vladfaust/migrate.cr.git + version: 0.5.0 + + sqlite3: + git: https://github.com/crystal-lang/crystal-sqlite3.git + version: 0.13.0 + + time_format: + git: https://github.com/vladfaust/time_format.cr.git + version: 0.1.1 + diff --git a/shard.yml b/shard.yml new file mode 100644 index 0000000..a3c8ba1 --- /dev/null +++ b/shard.yml @@ -0,0 +1,23 @@ +name: crystal-gauntlet +version: 0.1.0 + +authors: + - Jill "oatmealine" Monoids + - winter + +targets: + crystal-gauntlet: + main: src/crystal-gauntlet.cr + +dependencies: + sqlite3: + github: crystal-lang/crystal-sqlite3 + migrate: + github: vladfaust/migrate.cr + version: ~> 0.5.0 + dotenv: + github: gdotdesign/cr-dotenv + +crystal: 1.6.2 + +license: MIT diff --git a/spec/crystal-gauntlet_spec.cr b/spec/crystal-gauntlet_spec.cr new file mode 100644 index 0000000..16847e9 --- /dev/null +++ b/spec/crystal-gauntlet_spec.cr @@ -0,0 +1,9 @@ +require "./spec_helper" + +describe Crystal::Gauntlet do + # TODO: Write tests + + it "works" do + false.should eq(true) + end +end diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr new file mode 100644 index 0000000..c8b2175 --- /dev/null +++ b/spec/spec_helper.cr @@ -0,0 +1,2 @@ +require "spec" +require "../src/crystal-gauntlet" diff --git a/src/accounts.cr b/src/accounts.cr new file mode 100644 index 0000000..9b7217e --- /dev/null +++ b/src/accounts.cr @@ -0,0 +1,31 @@ +require "uri" + +include CrystalGauntlet + +module CrystalGauntlet::Accounts + extend self + + def get_ext_id_from_params(params : URI::Params) : String + return "1" + if params.has_key?("udid") && params["udid"] != "" + # todo: numeric id check + params["udid"] + elsif params.has_key?("account_id") && params["account_id"] != "" && params["account_id"] != "0" + # todo: validate password + params["account_id"] + else + "-1" + end + end + + def get_user_id(username : String, ext_id : String) : Int32 + return 1 + DATABASE.query("select id from users where udid = ? or account_id = ?", ext_id, ext_id) do |rs| + if rs.column_count > 0 + return rs.read(Int32) + else + raise "no user associated with account?!" + end + end + end +end diff --git a/src/crystal-gauntlet.cr b/src/crystal-gauntlet.cr new file mode 100644 index 0000000..142432b --- /dev/null +++ b/src/crystal-gauntlet.cr @@ -0,0 +1,56 @@ +require "http/server" +require "uri" +require "sqlite3" +require "migrate" +require "dotenv" + +require "./enums" +require "./hash" +require "./format" +require "./accounts" +require "./gjp" + +Dotenv.load + +module CrystalGauntlet + VERSION = "0.1.0" + + APPEND_PATH = "asdfasdfasd/" + DATABASE = DB.open(ENV["DATABASE_URL"]) + + @@endpoints = Hash(String, (String -> String)).new + + def self.endpoints + @@endpoints + end + + def self.run() + server = HTTP::Server.new do |context| + # expunge trailing slashes + path = context.request.path.chomp("/") + + path = path.sub(APPEND_PATH, "") + body = context.request.body + + if !body + puts "no body :(" + elsif @@endpoints.has_key?(path) + func = @@endpoints[path] + value = func.call(body.gets_to_end) + context.response.content_type = "text/plain" + context.response.print value + puts "#{path} -> #{value}" + else + context.response.respond_with_status(404, "endpoint not found") + puts "#{path} -> 404" + end + end + + puts "Listening on http://127.0.0.1:8080" + server.listen(8080) + end +end + +require "./endpoints/**" + +CrystalGauntlet.run() diff --git a/src/endpoints/accounts/loginAccount.cr b/src/endpoints/accounts/loginAccount.cr new file mode 100644 index 0000000..634d62f --- /dev/null +++ b/src/endpoints/accounts/loginAccount.cr @@ -0,0 +1,27 @@ +require "uri" +require "base64" +require "crypto/bcrypt/password" + +include CrystalGauntlet + +CrystalGauntlet.endpoints["/accounts/loginGJAccount.php"] = ->(body : String): String { + params = URI::Params.parse(body) + puts params.inspect + + username = params["userName"] + password = params["password"] + result = DATABASE.query_all("select id, password from accounts", as: {Int32, String}) + if result.size > 0 + account_id, hash = result[0] + bcrypt = Crypto::Bcrypt::Password.new(hash) + + if bcrypt.verify(password) + user_id = Accounts.get_user_id(username, account_id.to_s) + "#{account_id},#{user_id}" + else + return "-12" + end + else + return "-1" + end +} diff --git a/src/endpoints/accounts/registerAccount.cr b/src/endpoints/accounts/registerAccount.cr new file mode 100644 index 0000000..8d153bf --- /dev/null +++ b/src/endpoints/accounts/registerAccount.cr @@ -0,0 +1,28 @@ +require "uri" +require "base64" +require "crypto/bcrypt/password" + +include CrystalGauntlet + +CrystalGauntlet.endpoints["/accounts/registerGJAccount.php"] = ->(body : String): String { + params = URI::Params.parse(body) + puts params.inspect + + username = params["userName"] + password = params["password"] + email = params["email"] + + username_exists = DATABASE.scalar "select count(*) from accounts where username = ?", username + if username_exists != 0 + return "-2" + end + + password_hash = Crypto::Bcrypt::Password.create(password, cost: 10).to_s + gjp2 = CrystalGauntlet::GJP.hash(password) + next_id = (DATABASE.scalar("select max(id) from accounts").as(Int64 | Nil) || 0) + 1 + DATABASE.exec "insert into accounts (id, username, password, email, gjp2) values (?, ?, ?, ?, ?)", next_id, username, password_hash, email, gjp2 + + user_id = (DATABASE.scalar("select max(id) from users").as(Int64 | Nil) || 0) + 1 + DATABASE.exec "insert into users (id, account_id, username, registered) values (?, ?, ?, 1)", user_id, next_id, username + "1" +} diff --git a/src/endpoints/levels/downloadLevels.cr b/src/endpoints/levels/downloadLevels.cr new file mode 100644 index 0000000..0022f9b --- /dev/null +++ b/src/endpoints/levels/downloadLevels.cr @@ -0,0 +1,111 @@ +require "uri" +require "base64" + +include CrystalGauntlet + +CrystalGauntlet.endpoints["/downloadGJLevel22.php"] = ->(body : String): String { + params = URI::Params.parse(body) + puts params.inspect + + response = "" + + DATABASE.query("select levels.id, levels.name, levels.level_data, levels.extra_data, levels.level_info, levels.password, levels.user_id, levels.description, levels.original, levels.game_version, levels.requested_stars, levels.version, levels.song_id, levels.length, levels.objects, levels.coins, levels.has_ldm, levels.two_player, levels.downloads, levels.likes, levels.difficulty, levels.demon_difficulty, levels.stars, levels.featured, levels.epic, levels.rated_coins, users.username, users.udid, users.account_id, users.registered from levels join users on levels.user_id = users.id where levels.id = ?", params["levelID"].to_i32) do |rs| + if rs.move_next + id = rs.read(Int32) + name = rs.read(String) + level_data = rs.read(String) + extra_data = rs.read(String) + level_info = rs.read(String) + password = rs.read(String | Nil) + user_id = rs.read(Int32) + description = rs.read(String) + original = rs.read(Int32 | Nil) + game_version = rs.read(Int32) + requested_stars = rs.read(Int32 | Nil) + version = rs.read(Int32) + song_id = rs.read(Int32) + length = rs.read(Int32) + objects = rs.read(Int32) + coins = rs.read(Int32) + has_ldm = rs.read(Bool) + two_player = rs.read(Bool) + downloads = rs.read(Int32) + likes = rs.read(Int32) + difficulty_int = rs.read(Int32 | Nil) + difficulty = difficulty_int && LevelDifficulty.new(difficulty_int) + demon_difficulty_int = rs.read(Int32 | Nil) + demon_difficulty = demon_difficulty_int && DemonDifficulty.new(demon_difficulty_int) + stars = rs.read(Int32 | Nil) + featured = rs.read(Bool) + epic = rs.read(Bool) + rated_coins = rs.read(Bool) + + user_username = rs.read(String) + user_udid = rs.read(String | Nil) + user_account_id = rs.read(Int32 | Nil) + user_registered = rs.read(Bool) + + xor_pass = "0" + if !password + password = "0" + elsif params["gameVersion"].to_i >= 20 + xor_pass = GDBase64.encode(XorCrypt.encrypt_string(password, "26364")) + else + xor_pass = password + end + + # https://github.com/Cvolton/GMDprivateServer/blob/master/incl/levels/getGJLevels.php#L266 + response += CrystalGauntlet::Format.fmt_hash({ + 1 => id, + 2 => name, + 3 => Base64.encode(description).sub('/', '_').sub('+', '-').strip("\n"), + 4 => level_data, + 5 => version, + 6 => user_id, + 8 => 10, + 9 => difficulty ? difficulty.to_star_difficulty : 0, # 0=N/A 10=EASY 20=NORMAL 30=HARD 40=HARDER 50=INSANE 50=AUTO 50=DEMON + 10 => downloads, + 11 => 1, + 12 => song_id < 50 ? song_id : 0, + 13 => game_version, + 14 => likes, + 17 => difficulty && difficulty.demon?, + 43 => (demon_difficulty || DemonDifficulty::Hard).to_demon_difficulty, + 25 => difficulty && difficulty.auto?, + 18 => stars || 0, + 19 => featured, + 42 => epic, + 45 => objects, + 15 => length, + 30 => original || 0, + 31 => two_player, + 28 => "1", + 29 => "1", + 35 => song_id >= 50 ? song_id : 0, + 36 => extra_data, + 37 => coins, + 38 => rated_coins, + 39 => requested_stars || 0, + 46 => 1, + 47 => 2, + 40 => has_ldm, + 27 => xor_pass, + # 0 for n/a, 10 for easy, 20, for medium, ... + }) + + if params.has_key?("extras") + response += ":26:" + level_info + end + + response += "#" + Hashes.gen_solo(level_data) + + thing = [user_id, stars || 0, difficulty && difficulty.demon?, id, rated_coins, featured, password, 0].map! { |x| Format.fmt_value(x) } + puts thing.join(",") + response += "#" + Hashes.gen_solo_2(thing.join(",")) + else + response += "-1" + end + end + + response +} diff --git a/src/endpoints/levels/getLevels.cr b/src/endpoints/levels/getLevels.cr new file mode 100644 index 0000000..eec24c1 --- /dev/null +++ b/src/endpoints/levels/getLevels.cr @@ -0,0 +1,93 @@ +require "uri" +require "base64" + +include CrystalGauntlet + +CrystalGauntlet.endpoints["/getGJLevels21.php"] = ->(body : String): String { + params = URI::Params.parse(body) + puts params.inspect + + results = [] of String + users = [] of String + songs = [] of String + + hash_data = [] of Tuple(Int32, Int32, Bool) + + DATABASE.query "select levels.id, levels.name, levels.user_id, levels.description, levels.original, levels.game_version, levels.requested_stars, levels.version, levels.song_id, levels.length, levels.objects, levels.coins, levels.has_ldm, levels.two_player, levels.downloads, levels.likes, levels.difficulty, levels.demon_difficulty, levels.stars, levels.featured, levels.epic, levels.rated_coins, users.username, users.udid, users.account_id, users.registered from levels join users on levels.user_id = users.id" do |rs| + rs.each do + id = rs.read(Int32) + name = rs.read(String) + user_id = rs.read(Int32) + description = rs.read(String) + original = rs.read(Int32 | Nil) + game_version = rs.read(Int32) + requested_stars = rs.read(Int32 | Nil) + version = rs.read(Int32) + song_id = rs.read(Int32) + length = rs.read(Int32) + objects = rs.read(Int32) + coins = rs.read(Int32) + has_ldm = rs.read(Bool) + two_player = rs.read(Bool) + downloads = rs.read(Int32) + likes = rs.read(Int32) + difficulty_int = rs.read(Int32 | Nil) + difficulty = difficulty_int && LevelDifficulty.new(difficulty_int) + demon_difficulty_int = rs.read(Int32 | Nil) + demon_difficulty = demon_difficulty_int && DemonDifficulty.new(demon_difficulty_int) + stars = rs.read(Int32 | Nil) + featured = rs.read(Bool) + epic = rs.read(Bool) + rated_coins = rs.read(Bool) + + user_username = rs.read(String) + user_udid = rs.read(String | Nil) + user_account_id = rs.read(Int32 | Nil) + user_registered = rs.read(Bool) + + # https://github.com/Cvolton/GMDprivateServer/blob/master/incl/levels/getGJLevels.php#L266 + results << CrystalGauntlet::Format.fmt_hash({ + 1 => id, + 2 => name, + 5 => version, + 6 => user_id, + 8 => 10, + 9 => difficulty ? difficulty.to_star_difficulty : 0, # 0=N/A 10=EASY 20=NORMAL 30=HARD 40=HARDER 50=INSANE 50=AUTO 50=DEMON + 10 => downloads, + 12 => song_id < 50 ? song_id : 0, + 13 => game_version, + 14 => likes, + 17 => difficulty && difficulty.demon?, + 43 => (demon_difficulty || DemonDifficulty::Hard).to_demon_difficulty, + 25 => difficulty && difficulty.auto?, + 18 => stars || 0, + 19 => featured, + 42 => epic, + 45 => objects, + 3 => Base64.encode(description).sub('/', '_').sub('+', '-').strip("\n"), + 15 => length, + 30 => original || 0, + 31 => two_player, + 37 => coins, + 38 => rated_coins, + 39 => requested_stars || 0, + 46 => 1, + 47 => 2, + 40 => has_ldm, + 35 => song_id >= 50 ? song_id : 0, # 0 for n/a, 10 for easy, 20, for medium, ... + }) + + users << "#{user_id}:#{user_username}:#{user_registered ? user_account_id : user_udid}" + + hash_data << {id, stars || 0, rated_coins} + end + end + + # `:${offset}:${levelsPerPage}` + searchMeta = "#{results.size}:0:10" + + res = [results.join("|"), users.join("|"), songs.join("|"), searchMeta, CrystalGauntlet::Hashes.gen_multi(hash_data)].join("#") + puts res + + res +} diff --git a/src/endpoints/levels/uploadLevel.cr b/src/endpoints/levels/uploadLevel.cr new file mode 100644 index 0000000..e5c125f --- /dev/null +++ b/src/endpoints/levels/uploadLevel.cr @@ -0,0 +1,35 @@ +require "uri" + +include CrystalGauntlet + +# URI::Params{"gameVersion" => ["21"], "binaryVersion" => ["35"], "gdw" => ["0"], "accountID" => ["8369"], "gjp" => ["Vw9mW0FBUgN_VXtZ"], "userName" => ["oatmealine"], "levelID" => ["0"], "levelName" => ["security"], "levelDesc" => [""], "levelVersion" => ["1"], "levelLength" => ["1"], "audioTrack" => ["0"], "auto" => ["0"], "password" => ["0"], "original" => ["0"], "twoPlayer" => ["0"], "songID" => ["1050575"], "objects" => ["207"], "coins" => ["0"], "requestedStars" => ["0"], "unlisted" => ["0"], "wt" => ["709"], "wt2" => ["0"], "ldm" => ["0"], "extraString" => ["0_73_0_39_0_0_0_0_0_0_0_0_66_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0"], "seed" => ["SbpPMJ9vjn"], "seed2" => ["UFIKAAQCA1RTB1cABFEMUlBRDwFRUgMPAlAHVFYMUQFXAwEGCAAPDQ=="], "levelString" => ["H4sIAAAAAAAAC61Xy5HYIAxtyLuDJCTB5JQatgAKSAspPvxssC1lc8jBZngPhNAP-PVF6YASpWBhKlQkFoCCzAVwNNSbWD6gSIEQQtECBbj9UgklFfgNtcX6Uf2-mQ7udAhYxuhvRGRTRBszJvyTkLrfYusyBAE2Qe3_jSD-X4LEEXT8-gl0hNbwaGQ08ah_OaB1dECzSa35otx72P9DQid-xv4fbJ3dGzjCDzhAYzzwoHDQAbyA3IDYgdoD4qtL1HiQhmj91dU-ucIHNDbCTmIl8MB46JjZydxICHOq9rldlUAXLXlMCBVB7BMApjLViLtumDpdl8QmI8SuRlhMTEe1WRpLILbdxqnCpXGkKaQh0BCBU-xLzS7j4iuEXfM1oyr8IW3bH7TPPjcg-_Ktr6dFth0MkYNRWDuoqAZjfMPJwcWTPyXt8gdOb7zvWofzaNipxcnYdO5AMzoE2UyJHUm6mWpCp602u5xTL8NAyPaOANAj2COSQyB4RPQIfahJjkNqAHuE5ZJOGDvGsK_ysFnEhzLRs0D0LMCWBYa_gZGW-JGg3LefX8EEHF_ELAdh1IOKpFE88i2FZx-2RUScRYQ8IryIUT9A-WGiCWzLaXKkKjpEAo94W2ESL7uR9IIXljFmCRxbOdXN-a5_zSHbkxgc32MwfI8hbNQ9rBCcrEBwsgLBqmK9fIeH-uhkBaKTFYhGVkTdhMsqzw0lz0DkGYic5MDoGSJahuj-w1gLZ6Uwr_MUuSuKtUbK8ZEvTQc8CypRn86yYRuQ-R7cfbA88v8EZAHpyr4ecKh6P0D3PvZz8xlwacvXljwXpNeQjGPI00yZHTy98Sl6iFKTYp9Kb6rfbMBUgEJ0cH3jYWPydSNYi89FLL3mOjaltsoQbNX2a1jvizMue7adok1thnSbEp_K9h7QjgdCOx4I3_EgEpakRNtVM2wKoBstcy2bcqKFnGghJ1rIiJa5BPkxQX5MkBMTlLNTbirVyk3aLmtTVjTc1nE0ZI17sUGcwhxHRydYIzm4E7TRD9roR2Y04nmtsmroFN9i3BBinTu3bd85yuMVgfrZSOFP5A2OaMJsj1Z7dLqPPtVh6wA7OefUI07G3teMty_YSVJ2i_acYvqI_QxlJw3nVgyN2XcjG2f4NCcH08oMpk-Y7NHRHi326DxggNjef_sDjsS5VJA4tysy34hL1NtV4hQs8QuW-AVLjKp0Uu9aNi0gYdgr3Qxwkoh_IelvM8V0gyTTDZIfidRAdWqWOjXLe3GT9-Qm883dCetJO02pfp1T_9xW_3DWd82eZlEwraV2SVO7pKld0tQuafosaUv5daVRzU8P1IOvEmm-2Wo0jNszmijRwHv9qGrsGBtY2rBxmibQ_TT9A3vViWgxFQAA"], "levelInfo" => ["H4sIAAAAAAAACyXQyxHAIAgE0I6YAPJx7L-v7MIl5ImA-snRp--T0w_f8EFcIs8gB7WZGvSiY9CD68Stx35P5WM1QhM2y-JBzESEImaiSvI_d1cZcZkw-dDMN-Mz3Xegy0XNke8CR0wJ60EYUTro816yUhEuthV7KoIGMUcr8SQiBolMb-sWw8UUz8DeiqugH0LOuW2zJoXVH1ldIOdMAQAA"], "secret" => ["Wmfd2893gb7"]} + +CrystalGauntlet.endpoints["/uploadGJLevel21.php"] = ->(body : String): String { + params = URI::Params.parse(body) + puts params.inspect + + ext_id = Accounts.get_ext_id_from_params(params) + if ext_id == "-1" + return "-1" + end + user_id = Accounts.get_user_id(params["userName"], ext_id) + + song_id = params["songID"] == "0" ? params["audioTrack"] : params["songID"] + + description = params["levelDesc"] + if params["gameVersion"].to_i >= 20 # 2.0 + description = GDBase64.decode description + end + + if DATABASE.scalar("select count(*) from levels where id = ? and user_id = ?", params["levelID"], params["accountID"]).as(Int64) > 0 + # update existing level + raise "not implemented" + else + # create new level + next_id = (DATABASE.scalar("select max(id) from levels").as(Int64 | Nil) || 0) + 1 + + DATABASE.exec("insert into levels (id, name, user_id, description, original, game_version, binary_version, password, requested_stars, unlisted, version, level_data, extra_data, level_info, wt1, wt2, song_id, length, objects, coins, has_ldm, two_player) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", next_id, params["levelName"], user_id, description, params["original"].to_i32, params["gameVersion"].to_i32, params["binaryVersion"].to_i32, params["password"] == "0" ? nil : params["password"].to_i32, params["requestedStars"].to_i32, params["unlisted"].to_i32, params["levelVersion"].to_i32, params["levelString"], params["extraString"], params["levelInfo"], params["wt"], params["wt2"], song_id.to_i32, params["levelLength"].to_i32, params["objects"].to_i32, params["coins"].to_i32, params["ldm"].to_i32, params["twoPlayer"].to_i32) + + next_id.to_s + end +} diff --git a/src/endpoints/users/getUser.cr b/src/endpoints/users/getUser.cr new file mode 100644 index 0000000..79836c0 --- /dev/null +++ b/src/endpoints/users/getUser.cr @@ -0,0 +1,89 @@ +require "uri" + +include CrystalGauntlet + +# URI::Params{"gameVersion" => ["21"], "binaryVersion" => ["35"], "gdw" => ["0"], "accountID" => ["1"], "gjp" => ["XFZBX1NSW1xcUw=="], "targetAccountID" => ["1"], "secret" => ["Wmfd2893gb7"]} + +CrystalGauntlet.endpoints["/getGJUserInfo20.php"] = ->(body : String): String { + params = URI::Params.parse(body) + puts params.inspect + + DATABASE.query("select accounts.id, accounts.username, is_admin, messages_enabled, friend_requests_enabled, comments_enabled, youtube_url, twitter_url, twitch_url, accounts.created_at, users.id, stars, demons, coins, user_coins, diamonds, orbs, creator_points, icon_type, color1, color2, glow, cube, ship, ball, ufo, wave, robot, spider, explosion from accounts join users on accounts.id = users.account_id where accounts.id = ?", params["targetAccountID"]) do |rs| + if rs.move_next + id = rs.read(Int32) + username = rs.read(String) + is_admin = rs.read(Int32) + messages_enabled = rs.read(Int32) + friend_requests_enabled = rs.read(Int32) + comments_enabled = rs.read(Int32) + youtube_url = rs.read(String | Nil) + twitter_url = rs.read(String | Nil) + twitch_url = rs.read(String | Nil) + created_at = rs.read(String) + user_id = rs.read(Int32) + stars = rs.read(Int32) + demons = rs.read(Int32) + coins = rs.read(Int32) + user_coins = rs.read(Int32) + diamonds = rs.read(Int32) + orbs = rs.read(Int32) + creator_points = rs.read(Int32) + icon_type = rs.read(Int32) + color1 = rs.read(Int32) + color2 = rs.read(Int32) + glow = rs.read(Int32) + cube = rs.read(Int32) + ship = rs.read(Int32) + ball = rs.read(Int32) + ufo = rs.read(Int32) + wave = rs.read(Int32) + robot = rs.read(Int32) + spider = rs.read(Int32) + explosion = rs.read(Int32) + + return CrystalGauntlet::Format.fmt_hash({ + 1 => username, + 2 => user_id, + 13 => coins, + 17 => user_coins, + 10 => color1, + 11 => color2, + 3 => stars, + 46 => diamonds, + 4 => demons, + 8 => creator_points, + 18 => !messages_enabled, + 19 => !friend_requests_enabled, + 50 => !comments_enabled, + 20 => youtube_url || "", + 21 => cube, + 22 => ship, + 23 => ball, + 24 => ufo, + 25 => wave, + 26 => robot, + 28 => glow, + 43 => spider, + 47 => explosion, + 30 => 1, # rank; todo + 16 => id, + # 31 = isnt (0) or is (1) friend or (3) incoming request or (4) outgoing request + # todo + 31 => 0, + # also w/ friend requests: + # 32 => id, + # 35 => comment, + # 37 => date, + 44 => twitter_url || "", + 45 => twitch_url || "", + 29 => 1, + # badge, todo + 49 => 0 + }) + end + end + + "-1" + + # echo "1:".$user["userName"].":2:".$user["userID"].":13:".$user["coins"].":17:".$user["userCoins"].":10:".$user["color1"].":11:".$user["color2"].":3:".$user["stars"].":46:".$user["diamonds"].":4:".$user["demons"].":8:".$creatorpoints.":18:".$msgstate.":19:".$reqsstate.":50:".$commentstate.":20:".$accinfo["youtubeurl"].":21:".$user["accIcon"].":22:".$user["accShip"].":23:".$user["accBall"].":24:".$user["accBird"].":25:".$user["accDart"].":26:".$user["accRobot"].":28:".$user["accGlow"].":43:".$user["accSpider"].":47:".$user["accExplosion"].":30:".$rank.":16:".$user["extID"].":31:".$friendstate.":44:".$accinfo["twitter"].":45:".$accinfo["twitch"].":29:1:49:".$badge . $appendix; +} diff --git a/src/endpoints/users/updateUser.cr b/src/endpoints/users/updateUser.cr new file mode 100644 index 0000000..57109fc --- /dev/null +++ b/src/endpoints/users/updateUser.cr @@ -0,0 +1,17 @@ +require "uri" + +include CrystalGauntlet + +# URI::Params{"gameVersion" => ["21"], "binaryVersion" => ["35"], "gdw" => ["0"], "accountID" => ["1"], "gjp" => ["XFZBX1NSW1xcUw=="], "targetAccountID" => ["1"], "secret" => ["Wmfd2893gb7"]} + +CrystalGauntlet.endpoints["/updateGJUserScore22.php"] = ->(body : String): String { + params = URI::Params.parse(body) + puts params.inspect + + account_id = Accounts.get_ext_id_from_params(params) + user_id = Accounts.get_user_id(params["userName"], account_id) + + DATABASE.exec("update users set username=?, stars=?, demons=?, coins=?, user_coins=?, diamonds=?, icon_type=?, color1=?, color2=?, cube=?, ship=?, ball=?, ufo=?, wave=?, robot=?, spider=?, explosion=?, special=?, glow=?, last_played=? where id=?", params["userName"], params["stars"], params["demons"], params["coins"], params["userCoins"], params["diamonds"], params["iconType"], params["color1"], params["color2"], params["accIcon"], params["accShip"], params["accBall"], params["accBird"], params["accDart"], params["accRobot"], params["accSpider"], params["accExplosion"], params["special"], params["accGlow"], Time.utc.to_s("%Y-%m-%d %H:%M:%S"), user_id) + + user_id.to_s +} diff --git a/src/enums.cr b/src/enums.cr new file mode 100644 index 0000000..1d457f4 --- /dev/null +++ b/src/enums.cr @@ -0,0 +1,63 @@ +module CrystalGauntlet + enum LevelLength + Tiny + Short + Medium + Long + XL + end + + enum LevelDifficulty + Auto + Easy + Normal + Hard + Harder + Insane + Demon + + def to_star_difficulty + case self + when .auto? + 50 + when .easy? + 10 + when .normal? + 20 + when .hard? + 30 + when .harder? + 40 + when .insane? + 50 + when .demon? + 50 + end + end + end + + enum DemonDifficulty + Easy + Medium + Hard + Insane + Extreme + # unsafe + #Tarsorado + + def to_demon_difficulty + case self + when .easy? + 3 + when .medium? + 4 + when .hard? + 0 + when .insane? + 5 + when .extreme? + 6 + end + end + end +end diff --git a/src/format.cr b/src/format.cr new file mode 100644 index 0000000..241941e --- /dev/null +++ b/src/format.cr @@ -0,0 +1,50 @@ +module CrystalGauntlet::Format + extend self + + def fmt_value(v) : String + case v + when Bool + v ? "1" : "0" + when String + v + else + v.to_s + end + end + + def fmt_hash(hash) : String + hash.map_with_index{ |(i, v)| "#{i}:#{fmt_value(v)}" }.join(":") + end +end + +module CrystalGauntlet::GDBase64 + extend self + + def encode(v) + Base64.encode(v).sub('/', '_').sub('+', '-') + end + + def decode(v) + Base64.decode_string(v.sub('_', '/').sub('-', '+')) + end +end + +module CrystalGauntlet::XorCrypt + extend self + + def encrypt(x : Bytes, key : Bytes) : Bytes + result = Bytes.new(x.size) + x.each.with_index() do |chr, index| + result[index] = (chr ^ key[index % key.size]) + end + result + end + + def encrypt_string(x : String, key : String) : Bytes + result = Bytes.new(x.bytesize) + x.bytes.each.with_index() do |chr, index| + result[index] = (chr ^ key.byte_at(index % key.bytesize)) + end + result + end +end diff --git a/src/gjp.cr b/src/gjp.cr new file mode 100644 index 0000000..c65a51b --- /dev/null +++ b/src/gjp.cr @@ -0,0 +1,34 @@ +require "crypto/bcrypt/password" +require "base64" + +module CrystalGauntlet::GJP + extend self + + XOR_KEY = "37526" + + def decrypt(pass : String) + pwd = Base64.decode_string(pass.sub('_', '/').sub('-', '+')) + decrypted = "" + + pwd.each.with_index() do |chr, index| + decrypted += (chr ^ XOR_KEY.byte_at(index % XOR_KEY.bytesize)).unsafe_chr + end + + decrypted + end + + def encrypt(pass : String) + encrypted = Bytes.new(pass.bytesize) + + pass.bytes.each.with_index() do |chr, index| + encrypted[index] = chr ^ XOR_KEY.byte_at(index % XOR_KEY.bytesize) + end + + Base64.encode(encrypted).sub('/', '_').sub('+', '-') + end + + def hash(pass : String) + gjp2_hash = Digest::SHA1.hexdigest(pass + "mI29fmAnxgTs") + Crypto::Bcrypt::Password.create(gjp2_hash, cost: 10).to_s + end +end diff --git a/src/hash.cr b/src/hash.cr new file mode 100644 index 0000000..48569fe --- /dev/null +++ b/src/hash.cr @@ -0,0 +1,56 @@ +require "digest/sha1" +require "crypto/bcrypt" + +module CrystalGauntlet::Hashes + extend self + + def gen_multi(level_hash_data : Array(Tuple(Int32, Int32, Bool))) + Digest::SHA1.hexdigest do |ctx| + level_hash_data.each.with_index() do |val, index| + level_id, stars, coins = val + level_id_str = level_id.to_s + ctx.update "#{level_id_str[0]}#{level_id_str[-1]}#{stars}#{coins ? 1 : 0}" + end + + ctx.update "xI25fpAapCQg" + end + end + + def gen_solo(level_string : String) : String + hash = "" + divided : Int32 = (level_string.size / 40).to_i + i = 0 + k : Int32 = 0 + while k < level_string.size + if i > 39 + break + end + + hash += level_string.char_at(k) + i += 1 + k += divided + end + Digest::SHA1.hexdigest(hash.ljust(5, 'a') + "xI25fpAapCQg") + end + + def gen_solo_2(level_multi_string : String) : String + Digest::SHA1.hexdigest do |ctx| + ctx.update level_multi_string + ctx.update "xI25fpAapCQg" + end + end + + def gen_solo_3(level_multi_string : String) : String + Digest::SHA1.hexdigest do |ctx| + ctx.update level_multi_string + ctx.update "oC36fpYaPtdg" + end + end + + def gen_solo_4(level_multi_string : String) : String + Digest::SHA1.hexdigest do |ctx| + ctx.update level_multi_string + ctx.update "pC26fpYaQCtg" + end + end +end