wip executable patcher
This commit is contained in:
parent
7ec85e78b0
commit
44f2cc50ef
|
@ -26,10 +26,14 @@ require "./lib/creator_points"
|
||||||
require "./lib/versions"
|
require "./lib/versions"
|
||||||
require "./lib/ips"
|
require "./lib/ips"
|
||||||
|
|
||||||
|
require "./patch-exe.cr"
|
||||||
|
|
||||||
if File.exists?(".env")
|
if File.exists?(".env")
|
||||||
Dotenv.load
|
Dotenv.load
|
||||||
end
|
end
|
||||||
|
|
||||||
|
include CrystalGauntlet::PatchExe
|
||||||
|
|
||||||
module CrystalGauntlet
|
module CrystalGauntlet
|
||||||
VERSION = "0.1.0"
|
VERSION = "0.1.0"
|
||||||
|
|
||||||
|
@ -211,6 +215,8 @@ module CrystalGauntlet
|
||||||
|
|
||||||
migrate = false
|
migrate = false
|
||||||
calc_creator_points = false
|
calc_creator_points = false
|
||||||
|
patch_exe = false
|
||||||
|
patch_exe_location = nil
|
||||||
|
|
||||||
parser = OptionParser.new do |parser|
|
parser = OptionParser.new do |parser|
|
||||||
parser.banner = "Usage: crystal-gauntlet [command] [arguments]"
|
parser.banner = "Usage: crystal-gauntlet [command] [arguments]"
|
||||||
|
@ -223,6 +229,13 @@ module CrystalGauntlet
|
||||||
calc_creator_points = true
|
calc_creator_points = true
|
||||||
parser.banner = "Usage: crystal-gauntlet calc_creator_points [arguments]"
|
parser.banner = "Usage: crystal-gauntlet calc_creator_points [arguments]"
|
||||||
end
|
end
|
||||||
|
parser.on("patch_exe", "Patch Geometry Dash executables with your server URL (supports #{SUPPORTED_PATCH_PLATFORMS.join(", ")})") do
|
||||||
|
patch_exe = true
|
||||||
|
parser.banner = "Usage: crystal-gauntlet patch_exe <file>"
|
||||||
|
parser.unknown_args do |opt|
|
||||||
|
patch_exe_location = opt[0]?
|
||||||
|
end
|
||||||
|
end
|
||||||
parser.on("-h", "--help", "Show this help") do
|
parser.on("-h", "--help", "Show this help") do
|
||||||
puts parser
|
puts parser
|
||||||
exit
|
exit
|
||||||
|
@ -231,6 +244,17 @@ module CrystalGauntlet
|
||||||
|
|
||||||
parser.parse
|
parser.parse
|
||||||
|
|
||||||
|
if patch_exe
|
||||||
|
if !patch_exe_location
|
||||||
|
puts parser
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
check_server_length(true)
|
||||||
|
LOG.info { "Patching #{patch_exe_location}" }
|
||||||
|
patch_exe_file(patch_exe_location.not_nil!)
|
||||||
|
exit
|
||||||
|
end
|
||||||
|
|
||||||
migrator = Migrate::Migrator.new(
|
migrator = Migrate::Migrator.new(
|
||||||
DATABASE
|
DATABASE
|
||||||
)
|
)
|
||||||
|
@ -238,7 +262,10 @@ module CrystalGauntlet
|
||||||
if migrate
|
if migrate
|
||||||
LOG.info { "Migrating #{ENV["DATABASE_URL"].colorize(:white)}..." }
|
LOG.info { "Migrating #{ENV["DATABASE_URL"].colorize(:white)}..." }
|
||||||
migrator.to_latest
|
migrator.to_latest
|
||||||
elsif calc_creator_points
|
exit
|
||||||
|
end
|
||||||
|
|
||||||
|
if calc_creator_points
|
||||||
LOG.info { "updating creator points" }
|
LOG.info { "updating creator points" }
|
||||||
DATABASE.query_all("select id, username, creator_points from users", as: {Int32, String, Int32}).each() do |id, username, old_count|
|
DATABASE.query_all("select id, username, creator_points from users", as: {Int32, String, Int32}).each() do |id, username, old_count|
|
||||||
new_count = CreatorPoints.update_creator_points id
|
new_count = CreatorPoints.update_creator_points id
|
||||||
|
@ -246,50 +273,43 @@ module CrystalGauntlet
|
||||||
LOG.info { "#{username}: #{old_count} -> #{new_count}" }
|
LOG.info { "#{username}: #{old_count} -> #{new_count}" }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
|
||||||
if !migrator.latest?
|
|
||||||
LOG.fatal { "Database hasn\'t been migrated!! Please run #{"crystal-gauntlet migrate".colorize(:white)}" }
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
["songs", "levels", "saves"].each() { |v|
|
exit
|
||||||
Dir.mkdir_p(DATA_FOLDER / v)
|
|
||||||
}
|
|
||||||
|
|
||||||
server = HTTP::Server.new([
|
|
||||||
HTTP::LogHandler.new,
|
|
||||||
HTTP::StaticFileHandler.new("public/", fallthrough: true, directory_listing: false),
|
|
||||||
HTTP::StaticFileHandler.new((DATA_FOLDER / "songs").to_s, fallthrough: true, directory_listing: false),
|
|
||||||
CrystalGauntlet::GDHandler.new,
|
|
||||||
CrystalGauntlet::TemplateHandler.new
|
|
||||||
])
|
|
||||||
|
|
||||||
listen_on = URI.parse(ENV["LISTEN_ON"]? || "http://localhost:8080").normalize
|
|
||||||
|
|
||||||
case listen_on.scheme
|
|
||||||
when "http"
|
|
||||||
server.bind_tcp(listen_on.hostname.not_nil!, listen_on.port.not_nil!)
|
|
||||||
when "unix"
|
|
||||||
server.bind_unix(listen_on.to_s.sub("unix://",""))
|
|
||||||
end
|
|
||||||
|
|
||||||
full_server_path = config_get("general.hostname", "") + "/" + config_get("general.append_path", "")
|
|
||||||
robtop_server_path = "www.boomlings.com/database/"
|
|
||||||
if full_server_path.size != robtop_server_path.size
|
|
||||||
LOG.warn { "i think you made a mistake? length of full server path and default .exe location do not match" }
|
|
||||||
LOG.warn { " #{full_server_path}" }
|
|
||||||
LOG.warn { " #{robtop_server_path}" }
|
|
||||||
min_length = Math.min(full_server_path.size, robtop_server_path.size)
|
|
||||||
max_length = Math.max(full_server_path.size, robtop_server_path.size)
|
|
||||||
LOG.warn { " #{" " * min_length}#{"^" * (max_length - min_length)}"}
|
|
||||||
end
|
|
||||||
|
|
||||||
Reupload.init()
|
|
||||||
|
|
||||||
@@up_at = Time.utc
|
|
||||||
LOG.notice { "Listening on #{listen_on.to_s.colorize(:white)}" }
|
|
||||||
server.listen
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if !migrator.latest?
|
||||||
|
LOG.fatal { "Database hasn\'t been migrated!! Please run #{"crystal-gauntlet migrate".colorize(:white)}" }
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
|
||||||
|
["songs", "levels", "saves"].each() { |v|
|
||||||
|
Dir.mkdir_p(DATA_FOLDER / v)
|
||||||
|
}
|
||||||
|
|
||||||
|
server = HTTP::Server.new([
|
||||||
|
HTTP::LogHandler.new,
|
||||||
|
HTTP::StaticFileHandler.new("public/", fallthrough: true, directory_listing: false),
|
||||||
|
HTTP::StaticFileHandler.new((DATA_FOLDER / "songs").to_s, fallthrough: true, directory_listing: false),
|
||||||
|
CrystalGauntlet::GDHandler.new,
|
||||||
|
CrystalGauntlet::TemplateHandler.new
|
||||||
|
])
|
||||||
|
|
||||||
|
listen_on = URI.parse(ENV["LISTEN_ON"]? || "http://localhost:8080").normalize
|
||||||
|
|
||||||
|
case listen_on.scheme
|
||||||
|
when "http"
|
||||||
|
server.bind_tcp(listen_on.hostname.not_nil!, listen_on.port.not_nil!)
|
||||||
|
when "unix"
|
||||||
|
server.bind_unix(listen_on.to_s.sub("unix://",""))
|
||||||
|
end
|
||||||
|
|
||||||
|
check_server_length(false)
|
||||||
|
|
||||||
|
Reupload.init()
|
||||||
|
|
||||||
|
@@up_at = Time.utc
|
||||||
|
LOG.notice { "Listening on #{listen_on.to_s.colorize(:white)}" }
|
||||||
|
server.listen
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,96 @@
|
||||||
|
module CrystalGauntlet::PatchExe
|
||||||
|
extend self
|
||||||
|
|
||||||
|
SUPPORTED_PATCH_PLATFORMS = ["Windows"]
|
||||||
|
SUPPORTED_EXTENSIONS = ["exe"]
|
||||||
|
|
||||||
|
ROBTOP_SERVER_PATH = "http://www.boomlings.com/database"
|
||||||
|
|
||||||
|
def robtop_server_path
|
||||||
|
ROBTOP_SERVER_PATH
|
||||||
|
end
|
||||||
|
|
||||||
|
def full_server_path
|
||||||
|
"http://" + config_get("general.hostname", "") + "/" + config_get("general.append_path", "").chomp("/")
|
||||||
|
end
|
||||||
|
|
||||||
|
def replace(from : IO, to : IO, search : Array(UInt8), replace : Array(UInt8))
|
||||||
|
if search.size != replace.size
|
||||||
|
raise "Search and replacement does not match in size"
|
||||||
|
end
|
||||||
|
|
||||||
|
size = search.size
|
||||||
|
|
||||||
|
buffer = [] of UInt8
|
||||||
|
|
||||||
|
replacements = 0
|
||||||
|
|
||||||
|
from.each_byte do |byte|
|
||||||
|
if buffer.size >= size
|
||||||
|
insert_byte = buffer.shift
|
||||||
|
to.write_byte(insert_byte)
|
||||||
|
end
|
||||||
|
|
||||||
|
buffer << byte
|
||||||
|
|
||||||
|
if buffer == search
|
||||||
|
replace.each() { |b| to.write_byte(b) }
|
||||||
|
buffer = [] of UInt8
|
||||||
|
replacements += 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
buffer.each() { |b| to.write_byte(b) }
|
||||||
|
|
||||||
|
replacements
|
||||||
|
end
|
||||||
|
|
||||||
|
def patch_exe_file(location : String)
|
||||||
|
file_split = location.split(".")
|
||||||
|
extension = file_split.pop
|
||||||
|
patched_location = "#{file_split.join(".")}_patched.#{extension}"
|
||||||
|
File.open("#{location}", "r") do |from|
|
||||||
|
File.open(patched_location, "w") do |to|
|
||||||
|
start = Time.monotonic
|
||||||
|
|
||||||
|
amt = 0
|
||||||
|
|
||||||
|
case extension
|
||||||
|
when "exe"
|
||||||
|
gd_temp = File.tempfile("GeometryDash")
|
||||||
|
LOG.debug { " #{robtop_server_path.colorize(:dark_gray)} ->" }
|
||||||
|
LOG.debug { " #{full_server_path.colorize(:dark_gray)}" }
|
||||||
|
File.open(gd_temp.path, "w") do |tmp|
|
||||||
|
amt += replace(from, tmp, robtop_server_path.bytes, full_server_path.bytes)
|
||||||
|
end
|
||||||
|
LOG.debug { " #{Base64.strict_encode(robtop_server_path).colorize(:dark_gray)} ->" }
|
||||||
|
LOG.debug { " #{Base64.strict_encode(full_server_path).colorize(:dark_gray)}" }
|
||||||
|
File.open(gd_temp.path, "r") do |tmp|
|
||||||
|
amt += replace(tmp, to, Base64.strict_encode(robtop_server_path).bytes, Base64.strict_encode(full_server_path).bytes)
|
||||||
|
end
|
||||||
|
gd_temp.delete
|
||||||
|
else
|
||||||
|
LOG.error { "Unsupported extension #{extension.colorize(:white)} (supported: #{SUPPORTED_EXTENSIONS.join(", ")})" }
|
||||||
|
end
|
||||||
|
|
||||||
|
LOG.info { "Patched #{location} into #{patched_location} successfully" }
|
||||||
|
LOG.info { "#{amt} replacements done in #{(Time.monotonic - start).total_seconds.humanize(precision: 2, significant: false)}s" }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_server_length(exit_if_fail : Bool)
|
||||||
|
if full_server_path.size != robtop_server_path.size
|
||||||
|
LOG.warn { "i think you made a mistake? length of full server path and default .exe location do not match" }
|
||||||
|
LOG.warn { " #{full_server_path}" }
|
||||||
|
LOG.warn { " #{robtop_server_path}" }
|
||||||
|
min_length = Math.min(full_server_path.size, robtop_server_path.size)
|
||||||
|
max_length = Math.max(full_server_path.size, robtop_server_path.size)
|
||||||
|
LOG.warn { " #{" " * min_length}#{"^" * (max_length - min_length)}"}
|
||||||
|
|
||||||
|
if exit_if_fail
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue