some minor refactoring
This commit is contained in:
commit
4ca2ba06ab
|
@ -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
|
|
@ -0,0 +1 @@
|
||||||
|
DATABASE_URL=sqlite3://./crystalgauntlet.db
|
|
@ -0,0 +1,6 @@
|
||||||
|
/lib/
|
||||||
|
/bin/
|
||||||
|
/.shards/
|
||||||
|
*.dwarf
|
||||||
|
.env
|
||||||
|
crystalgauntlet.db
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"crystal-lang.completion": true,
|
||||||
|
"crystal-lang.hover": true,
|
||||||
|
"crystal-lang.implementations": true,
|
||||||
|
"crystal-lang.mainFile": "${workspaceRoot}/src/crystal-gauntlet.cr"
|
||||||
|
}
|
|
@ -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
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 Jill "oatmealine" Monoids <oatmealine@disroot.org>
|
||||||
|
|
||||||
|
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.
|
|
@ -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)
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
Binary file not shown.
After Width: | Height: | Size: 72 KiB |
|
@ -0,0 +1,5 @@
|
||||||
|
http://www.boomlings.com/database ->
|
||||||
|
http://localhost:8080/asdfasdfasd
|
||||||
|
|
||||||
|
aHR0cDovL3d3dy5ib29tbGluZ3MuY29tL2RhdGFiYXNl -> base64 http://localhost:8080/asdfasdfasd
|
||||||
|
aHR0cDovL2xvY2FsaG9zdDo4MDgwL2FzZGZhc2RmYXNk
|
|
@ -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
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
name: crystal-gauntlet
|
||||||
|
version: 0.1.0
|
||||||
|
|
||||||
|
authors:
|
||||||
|
- Jill "oatmealine" Monoids <oatmealine@disroot.org>
|
||||||
|
- winter <me@wint0r.zone>
|
||||||
|
|
||||||
|
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
|
|
@ -0,0 +1,9 @@
|
||||||
|
require "./spec_helper"
|
||||||
|
|
||||||
|
describe Crystal::Gauntlet do
|
||||||
|
# TODO: Write tests
|
||||||
|
|
||||||
|
it "works" do
|
||||||
|
false.should eq(true)
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,2 @@
|
||||||
|
require "spec"
|
||||||
|
require "../src/crystal-gauntlet"
|
|
@ -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
|
|
@ -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()
|
|
@ -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
|
||||||
|
}
|
|
@ -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"
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
Loading…
Reference in New Issue