chore: align ark-suac app and refresh appstore zip
This commit is contained in:
+4
@@ -0,0 +1,4 @@
|
||||
source 'https://rubygems.org'
|
||||
|
||||
gem 'slop', '= 4.10.1'
|
||||
gem 'iniparse', '= 1.5.0'
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
iniparse (1.5.0)
|
||||
slop (4.10.1)
|
||||
|
||||
PLATFORMS
|
||||
x86_64-linux-gnu
|
||||
|
||||
DEPENDENCIES
|
||||
iniparse (= 1.5.0)
|
||||
slop (= 4.10.1)
|
||||
|
||||
BUNDLED WITH
|
||||
2.5.0.dev
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
module AsaCtrl
|
||||
module Cli
|
||||
class CliInterface
|
||||
def initialize(opts)
|
||||
@opts = opts
|
||||
|
||||
print_help! if opts[:help]
|
||||
end
|
||||
|
||||
def print_help!
|
||||
raise "Help not implemented!"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
module AsaCtrl
|
||||
module Cli
|
||||
class ModsInterface < CliInterface
|
||||
def initialize(opts)
|
||||
super(opts)
|
||||
|
||||
execute!
|
||||
end
|
||||
|
||||
def execute!
|
||||
if @opts[:enable]
|
||||
enable_mod!
|
||||
end
|
||||
|
||||
exit! AsaCtrl::ExitCodes::OK
|
||||
end
|
||||
|
||||
def enable_mod!
|
||||
mod_id = @opts[:enable]
|
||||
AsaCtrl::Mods::Database.get_instance.enable_mod!(mod_id)
|
||||
|
||||
puts "Enabled mod id '#{mod_id}' successfully. The server will download the mod upon startup."
|
||||
rescue AsaCtrl::Errors::ModAlreadyEnabledError
|
||||
AsaCtrl::Cli.exit_with_error!("This mod is already enabled! Use 'asa-ctrl mods --list' to see what mods are currently enabled.",
|
||||
AsaCtrl::ExitCodes::MOD_ALREADY_ENABLED)
|
||||
end
|
||||
|
||||
def print_help!
|
||||
puts "Usage: asa-ctrl mods [--install] (--dry-run)"
|
||||
exit! AsaCtrl::ExitCodes::OK
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
module AsaCtrl
|
||||
module Cli
|
||||
class RconInterface < CliInterface
|
||||
def initialize(opts)
|
||||
super(opts)
|
||||
|
||||
execute!
|
||||
end
|
||||
|
||||
def execute!
|
||||
if @opts[:exec]
|
||||
run_command!
|
||||
end
|
||||
|
||||
exit! AsaCtrl::ExitCodes::OK
|
||||
end
|
||||
|
||||
def run_command!
|
||||
rcon_command = @opts[:exec]
|
||||
response = AsaCtrl::Rcon.exec_command!('127.0.0.1', AsaCtrl::Rcon.identify_port, rcon_command, AsaCtrl::Rcon.identify_password)
|
||||
|
||||
if response[:id] == AsaCtrl::Rcon::PacketTypes::RESPONSE_VALUE
|
||||
puts response[:body]
|
||||
else
|
||||
AsaCtrl::Cli.exit_with_error!("Rcon command execution failed: #{response}",
|
||||
AsaCtrl::ExitCodes::RCON_COMMAND_EXECUTION_FAILED)
|
||||
end
|
||||
rescue AsaCtrl::Errors::RconPasswordNotFoundError
|
||||
AsaCtrl::Cli.exit_with_error!("Could not read RCON password. Make sure it is properly configured, either as start parameter ?ServerAdminPassword=mypass or " \
|
||||
"in GameUserSettings.ini in the [ServerSettings] section as ServerAdminPassword=mypass", AsaCtrl::ExitCodes::RCON_PASSWORD_NOT_FOUND)
|
||||
rescue AsaCtrl::Errors::RconAuthenticationError
|
||||
AsaCtrl::Cli.exit_with_error!("Could not execute this RCON command. Authentication failed (wrong server password).",
|
||||
AsaCtrl::ExitCodes::RCON_PASSWORD_WRONG)
|
||||
end
|
||||
|
||||
def print_help!
|
||||
puts "Usage: asa-ctrl rcon [--exec]"
|
||||
exit! AsaCtrl::ExitCodes::OK
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
module AsaCtrl
|
||||
module Cli
|
||||
HELP_ARGUMENT = '--help'
|
||||
HELP_DESCRIPTION = 'Prints a help message'
|
||||
|
||||
def self.passed_command(args)
|
||||
if ARGV.size == 0
|
||||
[]
|
||||
else
|
||||
[ARGV[0]]
|
||||
end
|
||||
end
|
||||
|
||||
def self.print_usage
|
||||
puts "Usage: asa-ctrl [rcon] (--help)"
|
||||
end
|
||||
|
||||
def self.exit_with_error!(message, code)
|
||||
$stderr.puts "Error: #{message}"
|
||||
exit! code
|
||||
end
|
||||
end
|
||||
end
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
module AsaCtrl
|
||||
module Errors
|
||||
class BaseError < StandardError; end
|
||||
end
|
||||
end
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
require_relative './base_error.rb'
|
||||
require_relative './mod_already_enabled_error.rb'
|
||||
require_relative './rcon_authentication_error.rb'
|
||||
require_relative './rcon_password_not_found_error.rb'
|
||||
require_relative './rcon_port_not_found_error.rb'
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
module AsaCtrl
|
||||
module Errors
|
||||
class ModAlreadyEnabledError < BaseError; end
|
||||
end
|
||||
end
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
module AsaCtrl
|
||||
module Errors
|
||||
class RconAuthenticationError < BaseError; end
|
||||
end
|
||||
end
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
module AsaCtrl
|
||||
module Errors
|
||||
class RconPasswordNotFoundError < BaseError; end
|
||||
end
|
||||
end
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
module AsaCtrl
|
||||
module Errors
|
||||
class RconPortNotFoundError < BaseError; end
|
||||
end
|
||||
end
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
module AsaCtrl
|
||||
module ExitCodes
|
||||
OK = 0
|
||||
CORRUPTED_MODS_DATABASE = 1
|
||||
MOD_ALREADY_ENABLED = 2
|
||||
RCON_PASSWORD_NOT_FOUND = 3
|
||||
RCON_PASSWORD_WRONG = 4
|
||||
RCON_COMMAND_EXECUTION_FAILED = 5
|
||||
end
|
||||
end
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
require_relative './start_params_helper.rb'
|
||||
require_relative './ini_config_helper.rb'
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
module AsaCtrl
|
||||
module IniConfigHelper
|
||||
def self.game_user_settings_ini
|
||||
self.parse('/home/gameserver/server-files/ShooterGame/Saved/Config/WindowsServer/GameUserSettings.ini')
|
||||
end
|
||||
|
||||
def self.game_ini
|
||||
self.parse('/home/gameserver/server-files/ShooterGame/Saved/Config/WindowsServer/Game.ini')
|
||||
end
|
||||
|
||||
def self.parse(path)
|
||||
return unless File.exist?(path)
|
||||
|
||||
IniParse.parse(File.read(path))
|
||||
end
|
||||
end
|
||||
end
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
module AsaCtrl
|
||||
module StartParamsHelper
|
||||
def self.get_value(start_params, key)
|
||||
return unless start_params
|
||||
|
||||
value = ''
|
||||
offset = start_params.index("#{key}=")
|
||||
|
||||
return unless offset
|
||||
|
||||
offset += "#{key}=".length
|
||||
|
||||
start_params[offset..-1].each_char do |char|
|
||||
break if char == ' ' || char == '?'
|
||||
|
||||
value += char
|
||||
end
|
||||
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/ruby.ruby3.4
|
||||
require 'json'
|
||||
require 'slop'
|
||||
require 'iniparse'
|
||||
require 'socket'
|
||||
|
||||
if ENV['DEV'] == '1'
|
||||
require 'byebug'
|
||||
end
|
||||
|
||||
require_relative './exit_codes.rb'
|
||||
require_relative './errors/errors.rb'
|
||||
require_relative './helpers/helpers.rb'
|
||||
require_relative './mods/database.rb'
|
||||
require_relative './rcon/rcon.rb'
|
||||
require_relative './cli/utils.rb'
|
||||
require_relative './cli/interfaces/cli_interface.rb'
|
||||
require_relative './cli/interfaces/mods_interface.rb'
|
||||
require_relative './cli/interfaces/rcon_interface.rb'
|
||||
|
||||
main_args = Slop.parse(AsaCtrl::Cli.passed_command(ARGV)) do |args|
|
||||
args.on 'rcon', 'Interface for RCON command execution' do
|
||||
opts = Slop.parse(ARGV[1..-1]) do |opt|
|
||||
opt.string '--exec', 'An RCON command to execute'
|
||||
opt.bool AsaCtrl::Cli::HELP_ARGUMENT, AsaCtrl::Cli::HELP_DESCRIPTION
|
||||
end
|
||||
|
||||
AsaCtrl::Cli::RconInterface.new(opts)
|
||||
end
|
||||
|
||||
args.on AsaCtrl::Cli::HELP_ARGUMENT, AsaCtrl::Cli::HELP_DESCRIPTION do
|
||||
# handled once slop exits
|
||||
end
|
||||
end
|
||||
|
||||
AsaCtrl::Cli.print_usage
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
module AsaCtrl
|
||||
module Mods
|
||||
MOD_DATABASE_PATH = '/home/gameserver/server-files/mods.json'
|
||||
|
||||
class Database
|
||||
@@singleton_reference = nil
|
||||
|
||||
def initialize(database_path)
|
||||
@database_path = database_path
|
||||
|
||||
ensure_database_presence!
|
||||
load_database
|
||||
end
|
||||
|
||||
def self.get_instance
|
||||
return @@singleton_reference if @@singleton_reference
|
||||
@@singleton_reference = Database.new(MOD_DATABASE_PATH)
|
||||
end
|
||||
|
||||
def enable_mod!(mod_id)
|
||||
@database.each do |record|
|
||||
if record['mod_id'].to_i == mod_id.to_i
|
||||
raise AsaCtrl::Errors::ModAlreadyEnabledError if record['enabled']
|
||||
|
||||
record['enabled'] = true
|
||||
write_database!
|
||||
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
add_new_record!(mod_id, 'unknown', true, false)
|
||||
end
|
||||
|
||||
def add_new_record!(mod_id, name, enabled, scanned)
|
||||
@database << {
|
||||
mod_id: mod_id.to_i,
|
||||
name: name,
|
||||
enabled: enabled,
|
||||
scanned: scanned
|
||||
}
|
||||
|
||||
write_database!
|
||||
end
|
||||
|
||||
def write_database!
|
||||
File.write(@database_path, JSON.pretty_generate(@database))
|
||||
end
|
||||
|
||||
def ensure_database_presence!
|
||||
return if File.exist?(@database_path)
|
||||
|
||||
@database = []
|
||||
write_database!
|
||||
end
|
||||
|
||||
def load_database
|
||||
@database = JSON.parse(File.read(@database_path))
|
||||
rescue JSON::ParserError
|
||||
# we do not want to delete the file for the user, as they might want to save its content first
|
||||
AsaCtrl::Cli.exit_with_error!("mods.json file is corrupted and cannot be parsed, please delete this file " \
|
||||
"manually. It can be found in the server files root directory.", AsaCtrl::ExitCodes::CORRUPTED_MODS_DATABASE)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
module AsaCtrl
|
||||
module Rcon
|
||||
module PacketTypes
|
||||
RESPONSE_VALUE = 0
|
||||
EXEC_COMMAND = 2
|
||||
AUTH_RESPONSE = 2
|
||||
AUTH = 3
|
||||
end
|
||||
|
||||
Packet = Struct.new(:size, :id, :type, :body)
|
||||
|
||||
def self.exec_command!(server_ip, rcon_port, rcon_command, password)
|
||||
socket = TCPSocket.new(server_ip, rcon_port)
|
||||
raise AsaCtrl::Errors::RconAuthenticationError unless self.authenticate!(socket, password)
|
||||
|
||||
self.send_packet!(socket, rcon_command, PacketTypes::EXEC_COMMAND)
|
||||
end
|
||||
|
||||
def self.authenticate!(socket, password)
|
||||
response = self.send_packet!(socket, password, PacketTypes::AUTH)
|
||||
response[:id] != -1
|
||||
end
|
||||
|
||||
def self.send_packet!(socket, data, packet_id)
|
||||
packet = Packet.new(10+data.bytesize, 0, packet_id, data)
|
||||
|
||||
self.send_to(packet, socket)
|
||||
self.recv_from(socket)
|
||||
end
|
||||
|
||||
def self.send_to(packet, socket)
|
||||
szb = [packet[:size]].pack 'l<'
|
||||
idb = [packet[:id]].pack 'l<'
|
||||
type_b = [packet[:type]].pack 'l<'
|
||||
body_b = [packet[:body]].pack 'Z*'
|
||||
data = szb + idb + type_b + body_b + "\0"
|
||||
|
||||
socket.sendmsg(data)
|
||||
end
|
||||
|
||||
def self.recv_from(socket)
|
||||
msg_ary = socket.recvmsg
|
||||
msg = msg_ary[0]
|
||||
ary = msg.unpack('l<l<l<Z*')
|
||||
|
||||
Packet.new(ary[0], ary[1], ary[2], ary[3])
|
||||
end
|
||||
|
||||
def self.identify_password
|
||||
password = AsaCtrl::StartParamsHelper.get_value(ENV['ASA_START_PARAMS'], 'ServerAdminPassword')
|
||||
return password if password
|
||||
|
||||
config = AsaCtrl::IniConfigHelper.game_user_settings_ini
|
||||
return config['ServerSettings']['ServerAdminPassword'] if config['ServerSettings'] && config['ServerSettings']['ServerAdminPassword']
|
||||
|
||||
raise AsaCtrl::Errors::RconPasswordNotFoundError
|
||||
end
|
||||
|
||||
def self.identify_port
|
||||
port = AsaCtrl::StartParamsHelper.get_value(ENV['ASA_START_PARAMS'], 'RCONPort')
|
||||
return port.to_i if port
|
||||
|
||||
config = AsaCtrl::IniConfigHelper.game_user_settings_ini
|
||||
return config['ServerSettings']['RCONPort'].to_i if config['ServerSettings'] && config['ServerSettings']['RCONPort']
|
||||
|
||||
raise AsaCtrl::Errors::RconPortNotFoundError
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user