This commit is contained in:
Jill 2023-01-05 15:57:26 +03:00
parent 7d84b1ddd2
commit 6285395adb
7 changed files with 202 additions and 30 deletions

View File

@ -59,6 +59,69 @@ shards_max = 2
keys_min = 0
keys_max = 1
[quests]
enabled = true
# in seconds
timer = 21600 # 6hr
# top slot quests
[[quests.tier_1]]
name = "Orb Finder"
required_type = "orb"
required_amt = 200
reward_diamonds = 5
[[quests.tier_1]]
name = "Star Finder"
required_type = "star"
required_amt = 5
reward_diamonds = 5
[[quests.tier_1]]
name = "Coin Finder"
required_type = "coin"
required_amt = 2
reward_diamonds = 5
# middle slot quests
[[quests.tier_2]]
name = "Orb Collector"
required_type = "orb"
required_amt = 500
reward_diamonds = 10
[[quests.tier_2]]
name = "Star Collector"
required_type = "star"
required_amt = 10
reward_diamonds = 10
[[quests.tier_2]]
name = "Coin Collector"
required_type = "coin"
required_amt = 4
reward_diamonds = 10
# bottom slot quests
[[quests.tier_3]]
name = "Orb Master"
required_type = "orb"
required_amt = 1000
reward_diamonds = 15
[[quests.tier_3]]
name = "Star Master"
required_type = "star"
required_amt = 15
reward_diamonds = 15
[[quests.tier_3]]
name = "Coin Master"
required_type = "coin"
required_amt = 6
reward_diamonds = 15
[voting]
# allow votes to influence a level's difficulty when it
# hasn't been set yet. when set to false, all unrated

View File

@ -0,0 +1,8 @@
-- +migrate up
CREATE TABLE quest_timer (
account_id INTEGER NOT NULL references accounts(id),
next_at TEXT NOT NULL DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'now'))
);
-- +migrate down
DROP TABLE quest_timer;

View File

@ -12,6 +12,7 @@ require "migrate"
require "./enums"
require "./lib/hash"
require "./lib/format"
require "./lib/xor"
require "./lib/accounts"
require "./lib/gjp"
require "./lib/clean"
@ -221,7 +222,7 @@ module CrystalGauntlet
else
if !migrator.latest?
LOG.fatal { "Database hasn\'t been migrated!! Please run #{"crystal-gauntlet migrate".colorize(:white)}" }
Process.exit(1)
return
end
server = HTTP::Server.new([

View File

@ -0,0 +1,99 @@
require "uri"
include CrystalGauntlet
private PAD_STR = "_____" # meaningless but necessary
private def get_chk_value(chk_str : String)
XorCrypt.encrypt_string(Base64.decode_string(chk_str[5..]), XorCrypt::QUESTS_XOR_KEY)
end
private def get_quest_time(account_id : Int32)
timer = config_get("quests.timer").as?(Int64) || 0
begin
next_str = DATABASE.query_one("select next_at from quest_timer where account_id = ?", account_id, as: {String})
rescue
next_at = (Time.utc + timer.seconds).to_s(Format::TIME_FORMAT)
DATABASE.exec("insert into quest_timer (account_id, next_at) values (?, ?)", account_id, next_at)
return 0
else
next_at = Time.parse(next_str, Format::TIME_FORMAT, Time::Location::UTC)
seconds_left = (next_at - Time.utc).total_seconds.to_i
if seconds_left <= 0
next_at = (Time.utc + timer.seconds).to_s(Format::TIME_FORMAT)
DATABASE.exec("update quest_timer set next_at = ? where account_id = ?", next_at, account_id)
end
return seconds_left
end
end
private def type_to_i(type : String?)
case type
when "orb", "orbs"
1
when "coin", "coins"
2
when "star", "stars"
3
else
1
end
end
private def rand_quest(tier : Int32)
pool = config_get("quests.tier_#{tier}").as?(Array)
if !pool
return ""
end
roll = rand(pool.size)
quest = pool[roll]?.as?(Hash)
if !quest
return ""
end
[
roll,
type_to_i(quest["required_type"]?.as?(String)),
quest["required_amt"]? || 0,
quest["reward_diamonds"]? || 0,
quest["name"]? || ""
].join(",")
end
CrystalGauntlet.endpoints["/getGJChallenges.php"] = ->(context : HTTP::Server::Context): String {
params = URI::Params.parse(context.request.body.not_nil!.gets_to_end)
LOG.debug { params.inspect }
if !config_get("quests.enabled").as?(Bool)
LOG.debug { "quests disabled" }
return "-1"
end
user_id, account_id = Accounts.auth(params)
if !(user_id && account_id)
return "-1"
end
time_left = get_quest_time(account_id)
resp = [
PAD_STR,
user_id,
String.new(get_chk_value(params["chk"])),
params["udid"],
account_id,
time_left,
rand_quest(1),
rand_quest(2),
rand_quest(3)
].join(":")
resp_str = Base64.urlsafe_encode(XorCrypt.encrypt_string(resp, XorCrypt::QUESTS_XOR_KEY))
return PAD_STR + resp_str + "|" + Hashes.gen_solo_3(resp_str)
}

View File

@ -2,14 +2,13 @@ require "uri"
include CrystalGauntlet
XOR_KEY = "59182"
PAD_STR = "_____" # meaningless but necessary
private PAD_STR = "_____" # meaningless but necessary
def get_chk_value(chk_str : String)
XorCrypt.encrypt_string(Base64.decode_string(chk_str[5..]), XOR_KEY)
private def get_chk_value(chk_str : String)
XorCrypt.encrypt_string(Base64.decode_string(chk_str[5..]), XorCrypt::CHEST_XOR_KEY)
end
def get_rand(type : String, large = false)
private def get_rand(type : String, large = false)
base = "chests.#{large ? "large" : "small"}.#{type}"
min = config_get("#{base}_min").as?(Int64) || 0
max = config_get("#{base}_max").as?(Int64) || 0
@ -18,9 +17,9 @@ def get_rand(type : String, large = false)
((Random.rand(min.to_f .. (max.to_f + 1)) / increment).floor() * increment).to_i
end
REWARD_TYPES = ["orbs", "diamonds", "shards", "keys"]
private REWARD_TYPES = ["orbs", "diamonds", "shards", "keys"]
def get_chest(account_id : Int32, large = false) : {Int32?, Int32?}
private def get_chest(account_id : Int32, large = false) : {Int32?, Int32?}
begin
total, next_at = DATABASE.query_one("select total_opened, next_at from #{large ? "large_chests" : "small_chests"} where account_id = ?", account_id, as: {Int32, String})
rescue
@ -30,7 +29,7 @@ def get_chest(account_id : Int32, large = false) : {Int32?, Int32?}
end
end
def claim_chest(account_id : Int32, prev_count : Int32, large = false)
private def claim_chest(account_id : Int32, prev_count : Int32, large = false)
table = large ? "large_chests" : "small_chests"
timer = config_get("chests.#{large ? "large" : "small"}.timer").as?(Int64) || 0
next_at = (Time.utc + timer.seconds).to_s(Format::TIME_FORMAT)
@ -92,7 +91,7 @@ CrystalGauntlet.endpoints["/getGJRewards.php"] = ->(context : HTTP::Server::Cont
params["rewardType"].to_i? || 0
].join(":")
resp_str = Base64.urlsafe_encode(XorCrypt.encrypt_string(resp, XOR_KEY))
resp_str = Base64.urlsafe_encode(XorCrypt.encrypt_string(resp, XorCrypt::CHEST_XOR_KEY))
return PAD_STR + resp_str + "|" + Hashes.gen_solo_4(resp_str)
}

View File

@ -91,23 +91,3 @@ module CrystalGauntlet::Format
hash.map_with_index{ |(i, v)| "#{i}~#{fmt_value(v, false, false, true)}" }.join("~")
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

22
src/lib/xor.cr Normal file
View File

@ -0,0 +1,22 @@
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
QUESTS_XOR_KEY = "19847"
CHEST_XOR_KEY = "59182"
end