crystal-gauntlet/src/endpoints/levels/downloadLevel.cr

186 lines
7.1 KiB
Crystal

require "uri"
require "base64"
include CrystalGauntlet
CrystalGauntlet.endpoints["/downloadGJLevel22.php"] = ->(context : HTTP::Server::Context): String {
params = URI::Params.parse(context.request.body.not_nil!.gets_to_end)
LOG.debug { params.inspect }
response = [] of String
level_id = params["levelID"].to_i32
daily_num = nil # for hash checks
case level_id
when -1
# daily
level_id, _, idx = Dailies.fetch_current_level(false)
if !level_id || !idx
return "-1"
end
daily_num = idx
when -2
# weekly
level_id, _, idx = Dailies.fetch_current_level(true)
if !level_id || !idx
return "-1"
end
daily_num = idx + Dailies::WEEKLY_OFFSET
when -3
# events
raise "events?? what the hell"
end
level_exists = false
DATABASE.query("select levels.id, levels.name, 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.community_difficulty, levels.demon_difficulty, levels.stars, levels.featured, levels.epic, levels.rated_coins, levels.created_at, levels.modified_at, users.username, users.udid, users.account_id, users.registered, editor_time, editor_time_copies from levels join users on levels.user_id = users.id where levels.id = ?", level_id) do |rs|
if rs.move_next
id = rs.read(Int32)
name = 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)
set_difficulty_int = rs.read(Int32 | Nil)
set_difficulty = set_difficulty_int && LevelDifficulty.new(set_difficulty_int)
community_difficulty_int = rs.read(Int32 | Nil)
community_difficulty = community_difficulty_int && LevelDifficulty.new(community_difficulty_int)
difficulty = set_difficulty || community_difficulty
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)
created_at = rs.read(String)
updated_at = rs.read(String)
user_username = rs.read(String)
user_udid = rs.read(String | Nil)
user_account_id = rs.read(Int32 | Nil)
user_registered = rs.read(Bool)
editor_time = rs.read(Int32)
editor_time_copies = rs.read(Int32)
xor_pass = "0"
if !password
password = "0"
elsif Versions.parse(params["gameVersion"]? || "19") >= Versions::V2_0
xor_pass = Base64.urlsafe_encode(XorCrypt.encrypt_string(password, "26364"))
else
xor_pass = password
end
if Versions.parse(params["gameVersion"]? || "19") >= Versions::V2_0
description = Base64.urlsafe_encode(description)
end
level_data = Base64.urlsafe_encode(File.read(DATA_FOLDER / "levels" / "#{id}.lvl"))
# todo: deduplicate this with getLevels?
response << CrystalGauntlet::Format.fmt_hash({
1 => id,
2 => name,
3 => description,
4 => level_data,
5 => version,
6 => user_id,
# this is suppoused to be the amount of people who have
# voted on a level, but this is unused by the game, so
# we just always tell the game 10 people have voted
8 => 10,
# 0=N/A 10=EASY 20=NORMAL 30=HARD 40=HARDER 50=INSANE 50=AUTO 50=DEMON
# divided by above value, which is why its multiplied by 10
9 => (difficulty ? difficulty.to_star_difficulty : 0).not_nil! * 10,
10 => downloads + 1,
12 => !Songs.is_custom_song(song_id) ? song_id : 0,
13 => game_version,
# likes - dislikes
14 => likes,
# dislikes - likes
16 => -likes,
15 => length,
17 => difficulty && difficulty.demon?,
18 => stars || 0,
19 => featured,
25 => difficulty && difficulty.auto?,
27 => xor_pass,
28 => Time.parse(created_at, Format::TIME_FORMAT, Time::Location::UTC),
29 => Time.parse(updated_at, Format::TIME_FORMAT, Time::Location::UTC),
30 => original || 0,
31 => two_player,26 => params.has_key?("extras") ? level_info : nil,
35 => Songs.is_custom_song(song_id) ? song_id : 0,
36 => extra_data,
37 => coins,
38 => rated_coins,
39 => requested_stars || 0,
40 => has_ldm,
41 => daily_num,
42 => epic,
# 0 for n/a, 10 for easy, 20, for medium, ...
43 => (demon_difficulty || DemonDifficulty::Hard).to_demon_difficulty,
# todo
44 => false,
45 => objects,
46 => editor_time,
47 => editor_time_copies
})
response << Hashes.gen_solo(level_data)
thing = [user_id, stars || 0, (difficulty && difficulty.demon?) || 0, id, rated_coins, featured, password, daily_num || 0].map { |x| Format.fmt_value(x) }
response << Hashes.gen_solo_2(thing.join(","))
if daily_num
response << [user_id, user_username, user_account_id].join(":")
end
level_exists = true
else
response << "-1"
end
end
if level_exists
ip = IPs.get_real_ip(context.request)
if DATABASE.scalar("select count(*) from ip_actions where action = ? and value = ? and ip = ? limit 1", "download", level_id, ip).as?(Int64) == 0
DATABASE.exec("update levels set downloads = downloads + 1 where id = ?", level_id)
DATABASE.exec("insert into ip_actions (action, value, ip) values (?, ?, ?)", "download", level_id, ip)
downloads = DATABASE.scalar("select downloads from levels where id = ?", level_id).as(Int64)
if downloads == 300
# notify the creator of 300 downloads
account_id, level_name = DATABASE.query_one("select users.account_id, levels.name from levels left join users on users.id = levels.user_id where levels.id = ?", level_id, as: {Int32?, String})
if account_id
if DATABASE.scalar("select count(*) from notifications where type = \"download_milestone\" and target = ? and account_id = ?", level_id, account_id).as(Int64) == 0
Notifications.send_notification(account_id, "download_milestone", level_id, {
"level_name" => level_name,
"amount" => downloads
})
end
end
end
end
end
response.join("#")
}
CrystalGauntlet.endpoints["/downloadGJLevel20.php"] = CrystalGauntlet.endpoints["/downloadGJLevel22.php"]
CrystalGauntlet.endpoints["/downloadGJLevel19.php"] = CrystalGauntlet.endpoints["/downloadGJLevel22.php"]