gamespy_query 0.1.5 → 0.2.0pre

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore CHANGED
@@ -2,3 +2,8 @@
2
2
  .bundle
3
3
  Gemfile.lock
4
4
  pkg/*
5
+
6
+ /.idea/
7
+ /.yardoc/
8
+ /doc/
9
+ *.log
data/README.md CHANGED
@@ -1,4 +1,7 @@
1
- GamespyQuery
2
- =============
3
-
4
- This library provides access to GameSpy master server (through gslist utility) and to GameSpy enabled game servers directly through UDPSocket.
1
+ GamespyQuery
2
+ =============
3
+
4
+ This library provides access to GameSpy master server (through gslist utility) and to GameSpy enabled game servers directly through UDPSocket.
5
+
6
+ Requires the gslist utility for GamespyMaster operations: http://aluigi.org/papers.htm#gslist
7
+
data/Rakefile CHANGED
@@ -1 +1,15 @@
1
1
  require "bundler/gem_tasks"
2
+
3
+ require 'rake'
4
+ require 'rake/testtask'
5
+
6
+ desc "Run all our tests"
7
+ task :test do
8
+ Rake::TestTask.new do |t|
9
+ t.libs << "test"
10
+ t.pattern = "test/**/*_test.rb"
11
+ t.verbose = false
12
+ end
13
+ end
14
+
15
+ task :default => :test
data/bin/gamespy_query ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # This binary is mostly intended for testing gamespy_query implementation
4
+
5
+ require 'gamespy_query'
6
+
7
+ # Parse commandline arguments
8
+ options = GamespyQuery::Options.parse
@@ -18,7 +18,7 @@ Gem::Specification.new do |s|
18
18
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
19
  s.require_paths = ["lib"]
20
20
 
21
- # specify any dependencies here; for example:
22
- # s.add_development_dependency "rspec"
23
- # s.add_runtime_dependency "rest-client"
21
+ s.add_runtime_dependency "cri"
22
+ s.add_development_dependency "riot"
23
+ s.add_development_dependency "yard"
24
24
  end
data/lib/gamespy_query.rb CHANGED
@@ -1,11 +1,26 @@
1
1
  require_relative "gamespy_query/version"
2
- require_relative "gamespy_query/base"
3
- require_relative "gamespy_query/socket"
4
- require_relative "gamespy_query/socket_master"
5
- require_relative "gamespy_query/master"
6
2
 
3
+ # GamespyQuery provides access to GameSpy master server (through gslist utility)
4
+ # and to GameSpy enabled game servers directly through UDPSocket.
7
5
  module GamespyQuery
8
- # Your code goes here...
6
+ autoload :Base, "gamespy_query/base"
7
+ autoload :Funcs, "gamespy_query/base"
8
+ autoload :Tools, "gamespy_query/base"
9
+
10
+ autoload :Options, "gamespy_query/options"
11
+
12
+ autoload :Parser, "gamespy_query/parser"
13
+ autoload :Socket, "gamespy_query/socket"
14
+ autoload :MultiSocket, "gamespy_query/socket"
15
+ autoload :SocketMaster, "gamespy_query/socket_master"
16
+ autoload :Master, "gamespy_query/master"
17
+
18
+ module_function
19
+
20
+ # Retrieve full product version string
21
+ def product_version
22
+ "GamespyQuery version #{VERSION}"
23
+ end
9
24
  end
10
25
 
11
26
 
@@ -9,52 +9,75 @@ module GamespyQuery
9
9
 
10
10
  DEBUG = false
11
11
 
12
+ # Contains basic Tools set to work with logging, debugging, etc.
12
13
  module Tools
13
14
  STR_EMPTY = ""
14
15
  CHAR_N = "\n"
15
16
 
16
17
  module_function
18
+ # Provides access to the logger object
19
+ # Will use ActionController::Base.logger if available
17
20
  def logger
18
21
  @logger ||= if defined?(::Tools); ::Tools.logger; else; defined?(ActionController) ? ActionController::Base.logger || Logger.new("logger.log") : Logger.new("logger.log"); end
19
22
  end
20
23
 
24
+ # Create debug message from Exception
25
+ # @param [Exception] e Exception to create debug message from
21
26
  def dbg_msg(e)
22
- "#{e.class}: #{e.message if e.respond_to?(:backtrace)}
23
- BackTrace: #{e.backtrace.join(CHAR_N) unless !e.respond_to?(:backtrace) || e.backtrace.nil?}"
27
+ <<STR
28
+ #{e.class}: #{e.message if e.respond_to?(:backtrace)}
29
+ BackTrace: #{e.backtrace.join(CHAR_N) unless !e.respond_to?(:backtrace) || e.backtrace.nil?}
30
+ STR
24
31
  end
25
32
 
26
-
33
+ # Log exception
34
+ # @param [Exception] e Exception to log
35
+ # @param [Boolean] as_error Log the exception as error in the log
36
+ # @param [String] msg Include custom error message
27
37
  def log_exception(e, as_error = true, msg = "")
28
38
  if defined?(::Tools)
29
39
  ::Tools.log_exception(e, as_error, msg)
30
40
  else
31
- puts "Error: #{e.class} #{e.message}, #{e.backtrace.join("\n")}"
41
+ puts "Error: #{e.class} #{e.message}, #{e.backtrace.join("\n") unless e.backtrace.nil? }"
32
42
  logger.error "#{"#{msg}:" unless msg.empty?}#{e.class} #{e.message}" if as_error
33
43
  logger.debug dbg_msg(e)
34
44
  end
35
45
  end
36
46
 
47
+ # Log to debug log if DEBUG enabled
48
+ # @param [Block] block Block to yield string from
37
49
  def debug(&block)
38
50
  return unless DEBUG
39
- logger.debug yield
51
+ out = yield
52
+ logger.debug out
53
+ puts out
40
54
  rescue Exception => e
41
55
  puts "Error: #{e.class} #{e.message}, #{e.backtrace.join("\n")}"
42
56
  end
43
57
  end
44
58
 
59
+ # Contains basic Funcs used throughout the classes
45
60
  module Funcs
61
+ # Provides TimeOutError exception
46
62
  class TimeOutError < StandardError
47
63
  end
48
64
 
65
+ # Strips tags from string
66
+ # @param [String] str
49
67
  def strip_tags(str)
50
68
  # TODO: Strip tags!!
51
69
  str
52
70
  end
53
71
 
54
- RX_F = /\A\-?[0-9][0-9]*\.[0-9]*\Z/
55
- RX_I = /\A\-?[0-9][0-9]*\Z/
72
+ # Float Regex
73
+ RX_F = /\A\-?[0-9]+\.[0-9]*\Z/
74
+ # Integer Regex
75
+ RX_I = /\A\-?[0-9]+\Z/
76
+ # Integer / Float actually String Regex
56
77
  RX_S = /\A\-?0[0-9]+.*\Z/
57
78
 
79
+ # Clean value, convert if possible
80
+ # @param [String] value String to convert
58
81
  def clean(value) # TODO: Force String, Integer, Float etc?
59
82
  case value
60
83
  when STR_X0
@@ -68,39 +91,37 @@ BackTrace: #{e.backtrace.join(CHAR_N) unless !e.respond_to?(:backtrace) || e.bac
68
91
  end
69
92
  end
70
93
 
71
- def clean_string(str)
72
- str.encode("UTF-8", invalid: :replace, undef: :replace)
73
- end
74
-
94
+ # Handle char
95
+ # @param [Integer] number Integer to convert
75
96
  def handle_chr(number)
76
97
  number = ((number % 256)+256) if number < 0
77
98
  number = number % 256 if number > 255
78
99
  number
79
100
  end
80
101
 
81
- def get_string(*params)
82
- Tools.debug {"Getting string #{params}"}
83
- _get_string(*params)
102
+ # Convert string to UTF-8, stripping out all invalid/undefined characters
103
+ # @param [String] str String to convert
104
+ def encode_string(str)
105
+ #Tools.debug {"Getting string #{str}"}
106
+ _encode_string str
84
107
  end
85
108
 
86
109
  if RUBY_PLATFORM =~ /mswin32/
87
- include System::Net
88
- include System::Net::Sockets
89
-
90
- def get_string(str)
110
+ # Get UTF-8 string from string
111
+ # @param [String] str
112
+ def _encode_string(str)
91
113
  System::Text::Encoding.UTF8.GetString(System::Array.of(System::Byte).new(str.bytes.to_a)).to_s # # begin; System::Text::Encoding.USASCII.GetString(reply[0]).to_s; rescue nil, Exception => e; Tools.log_exception(e); reply[0].map {|e| e.chr}.join; end
92
114
  end
93
115
  else
94
- require 'socket'
95
- require 'timeout'
96
- def get_string(str)
97
- #(str + ' ').encode("UTF-8", :invalid => :replace, :undef => :replace)[0..-2]
98
- str
116
+ # Get UTF-8 string from string
117
+ # @param [String] str
118
+ def _encode_string(str)
119
+ (str + ' ').encode("UTF-8", invalid: :replace, undef: :replace)[0..-3]
99
120
  end
100
121
  end
101
122
  end
102
123
 
103
-
124
+ # Base class from which all others derrive
104
125
  class Base
105
126
  include Funcs
106
127
  end
@@ -1,6 +1,5 @@
1
- require_relative 'base'
2
-
3
1
  module GamespyQuery
2
+ # Provides access to the Gamespy Master browser
4
3
  class Master < Base
5
4
  PARAMS = [:hostname, :gamever, :gametype, :gamemode, :numplayers, :maxplayers, :password, :equalModRequired, :mission, :mapname,
6
5
  :mod, :signatures, :verifysignatures, :gamestate, :dedicated, :platform, :sv_battleeye, :language, :difficulty]
@@ -14,29 +13,33 @@ module GamespyQuery
14
13
  "\\\\"
15
14
  end
16
15
 
17
- def geoip_path
18
- return File.join(Dir.pwd, "config") unless defined?(Rails)
16
+ # Geo settings
17
+ attr_reader :geo
19
18
 
20
- case RUBY_PLATFORM
21
- when /-mingw32$/, /-mswin32$/
22
- File.join(Rails.root, "config").gsub("/", "\\")
23
- else
24
- File.join(Rails.root, "config")
25
- end
26
- end
19
+ # Game
20
+ attr_reader :game
27
21
 
22
+ # Initializes the instance
23
+ # @param [String] geo Geo string
24
+ # @param [String] game Game string
28
25
  def initialize(geo = nil, game = "arma2oapc")
29
26
  @geo, @game = geo, game
30
27
  end
31
28
 
29
+ # Convert the master browser data to hash
32
30
  def process list = self.read
33
31
  self.to_hash list
34
32
  end
35
33
 
34
+ # Gets list of PARAMS, delimited by {DELIMIT}
36
35
  def get_params
37
36
  PARAMS.clone.map{|e| "#{DELIMIT}#{e}"}.join("")
38
37
  end
39
38
 
39
+ # Gets list of server addressses and optionally data
40
+ # @param [String] list Specify list or nil to fetch the list
41
+ # @param [Boolean] include_data Should server info data from the master browser be included
42
+ # @param [String] geo Geo String
40
43
  def get_server_list list = nil, include_data = false, geo = nil
41
44
  addrs = []
42
45
  list = %x[gslist -p "#{geoip_path}"#{" #{geo}-X #{get_params}" if include_data} -n #{@game}] if list.nil?
@@ -50,6 +53,7 @@ module GamespyQuery
50
53
  addrs
51
54
  end
52
55
 
56
+ # Read the server list from gamespy
53
57
  def read
54
58
  geo = @geo ? @geo : "-Q 11 "
55
59
  unless geo.nil? || geo.empty? || File.exists?(File.join(geoip_path, "GeoIP.dat"))
@@ -59,13 +63,22 @@ module GamespyQuery
59
63
  get_server_list(nil, true, geo)
60
64
  end
61
65
 
66
+ # Handle reply data from gamespy master browser
67
+ # @param [String] reply Reply from gamespy
68
+ # @param [String] geo Geo String
62
69
  def handle_data(reply, geo = nil)
63
70
  reply = reply.gsub("\\\\\\", "") if geo
64
71
  reply.split("\n").select{|line| line =~ RX_ADDR_LINE }
65
72
  end
66
73
 
74
+
75
+ # Address and Data regex
67
76
  RX_H = /\A([\.0-9]*):([0-9]*) *\\(.*)/
77
+ # Split string
68
78
  STR_SPLIT = "\\"
79
+
80
+ # Convert array of data to hash
81
+ # @param [Array] ar Array to convert
69
82
  def to_hash(ar)
70
83
  list = Hash.new
71
84
  ar.each_with_index do |entry, index|
@@ -77,7 +90,7 @@ module GamespyQuery
77
90
  i = 0
78
91
  content.map! do |e|
79
92
  i += 1
80
- i % 2 == 0 ? e : clean_string(e)
93
+ i % 2 == 0 ? e : encode_string(e)
81
94
  end
82
95
  addr = "#{ip}:#{port}"
83
96
  if list.has_key?(addr)
@@ -86,6 +99,7 @@ module GamespyQuery
86
99
  e = Hash.new
87
100
  e[:ip] = ip
88
101
  e[:port] = port
102
+ e[:gamename] = @game
89
103
  list[addr] = e
90
104
  end
91
105
  if e[:gamedata]
@@ -96,6 +110,19 @@ module GamespyQuery
96
110
  end
97
111
  list
98
112
  end
113
+
114
+
115
+ # Get geoip_path
116
+ def geoip_path
117
+ return File.join(Dir.pwd, "config") unless defined?(Rails)
118
+
119
+ case RUBY_PLATFORM
120
+ when /-mingw32$/, /-mswin32$/
121
+ File.join(Rails.root, "config").gsub("/", "\\")
122
+ else
123
+ File.join(Rails.root, "config")
124
+ end
125
+ end
99
126
  end
100
127
  end
101
128
 
@@ -103,4 +130,4 @@ if $0 == __FILE__
103
130
  master = GamespyQuery::Master.new
104
131
  r = master.read
105
132
  puts r
106
- end
133
+ end
@@ -0,0 +1,140 @@
1
+ require 'cri'
2
+ require 'ostruct'
3
+
4
+ module GamespyQuery
5
+ # Handles commandline parameters for the Main tool
6
+ class Options
7
+ class <<self
8
+ # Parse given args
9
+ # @param [Array] args Parse given args
10
+ def parse args = ARGV
11
+ _parse.run(args)
12
+ end
13
+
14
+ # Defaults for options
15
+ # @param [Hash] opts Options
16
+ def setup_master_opts opts
17
+ opts = opts.clone
18
+ opts[:geo] ||= ""
19
+ opts[:game] ||= "arma2oapc"
20
+ opts
21
+ end
22
+
23
+ private
24
+ # Parser definition
25
+ def _parse
26
+ #root_command = Cri::Command.new_basic_root # Bug with h self.help -> cmd.help etc
27
+ root_command = Cri::Command.define do
28
+ name 'gamespy_query'
29
+ usage 'gamespy_query [options]'
30
+ summary 'Gamespy Protocol'
31
+ description 'This command provides the basic functionality'
32
+
33
+
34
+ option :h, :help, 'show help for this command' do |value, cmd|
35
+ puts cmd.help
36
+ exit 0
37
+ end
38
+
39
+ subcommand Cri::Command.new_basic_help
40
+
41
+ option nil, :version, 'Show version' do |value, cmd|
42
+ puts GamespyQuery.product_version
43
+ exit 0
44
+ end
45
+
46
+ flag :v, :verbose, 'Verbose'
47
+ end
48
+
49
+ root_command.define_command do
50
+ name 'sync'
51
+ usage 'sync ip:port [options]'
52
+ summary 'Sync data'
53
+ aliases :s
54
+
55
+ run do |opts, args, cmd|
56
+ puts "Running Sync, #{opts}, #{args}, #{cmd}"
57
+ if args.empty?
58
+ puts "Missing ip:port"
59
+ exit 1
60
+ end
61
+ host, port = if args.size > 1
62
+ args
63
+ else
64
+ args[0].split(":")
65
+ end
66
+ time_start = Time.now
67
+ g = GamespyQuery::Socket.new("#{host}:#{port}")
68
+ r = g.sync
69
+ time_taken = Time.now - time_start
70
+ puts "Took: #{time_taken}s"
71
+ exit unless r
72
+ puts r.to_yaml
73
+ end
74
+ end
75
+
76
+ root_command.add_command _parse_master_command
77
+
78
+ root_command
79
+ end
80
+
81
+ def _parse_master_command
82
+ master_command = Cri::Command.define do
83
+ name 'master'
84
+ usage 'master COMMAND [options]'
85
+ aliases :m
86
+
87
+ option :g, :game, 'Specify game', :argument => :required
88
+ option nil, :geo, 'Specify geo', :argument => :required
89
+
90
+ subcommand Cri::Command.new_basic_help
91
+
92
+ run do |opts, args, cmd|
93
+ puts "Running Master, #{opts}, #{args}, #{cmd}"
94
+ end
95
+ end
96
+
97
+ master_command.define_command do
98
+ name 'list'
99
+ usage 'list [options]'
100
+ aliases :l
101
+
102
+ run do |opts, args, cmd|
103
+ opts = GamespyQuery::Options.setup_master_opts opts
104
+ master = GamespyQuery::Master.new(opts[:geo], opts[:game])
105
+ list = master.read
106
+ puts list
107
+ end
108
+ end
109
+
110
+ master_command.define_command do
111
+ name 'process'
112
+ usage 'process [options]'
113
+ aliases :p
114
+
115
+ run do |opts, args, cmd|
116
+ opts = GamespyQuery::Options.setup_master_opts opts
117
+
118
+ master = GamespyQuery::Master.new(opts[:geo], opts[:game])
119
+ process = master.process
120
+ puts process
121
+ end
122
+ end
123
+
124
+ master_command.define_command do
125
+ name 'process_master'
126
+ usage 'process_master [options]'
127
+ aliases :m
128
+
129
+ run do |opts, args, cmd|
130
+ opts = GamespyQuery::Options.setup_master_opts opts
131
+ process = GamespyQuery::SocketMaster.process_master(opts[:game], opts[:geo])
132
+ puts process
133
+ end
134
+ end
135
+
136
+ master_command
137
+ end
138
+ end
139
+ end
140
+ end