frostbitten 0.1.0
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/.document +5 -0
- data/Gemfile +20 -0
- data/Gemfile.lock +84 -0
- data/Guardfile +24 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +45 -0
- data/Rakefile +51 -0
- data/VERSION +1 -0
- data/ext/.DS_Store +0 -0
- data/ext/frostbitten/.DS_Store +0 -0
- data/ext/frostbitten/Makefile +213 -0
- data/ext/frostbitten/extconf.rb +4 -0
- data/ext/frostbitten/frostbitten.h +13 -0
- data/ext/frostbitten/header.c +194 -0
- data/ext/frostbitten/header.h +30 -0
- data/ext/frostbitten/message.c +202 -0
- data/ext/frostbitten/message.h +17 -0
- data/ext/frostbitten/native.c +15 -0
- data/ext/frostbitten/packet.c +20 -0
- data/ext/frostbitten/packet.h +11 -0
- data/ext/frostbitten/word.c +60 -0
- data/ext/frostbitten/word.h +17 -0
- data/frostbitten.gemspec +106 -0
- data/lib/frostbitten.rb +37 -0
- data/lib/frostbitten/client.rb +50 -0
- data/lib/frostbitten/connection.rb +100 -0
- data/lib/frostbitten/map.rb +38 -0
- data/lib/frostbitten/methods/admin.rb +29 -0
- data/lib/frostbitten/methods/normal.rb +47 -0
- data/lib/frostbitten/player.rb +54 -0
- data/lib/frostbitten/score.rb +21 -0
- data/lib/frostbitten/server.rb +27 -0
- data/lib/frostbitten/version.rb +15 -0
- data/spec/frostbitten_spec.rb +8 -0
- data/spec/lib/frostbitten_client_spec.rb +14 -0
- data/spec/lib/frostbitten_server_spec.rb +48 -0
- data/spec/spec_helper.rb +7 -0
- data/test/helper.rb +15 -0
- data/test/test_frostbitten.rb +7 -0
- metadata +252 -0
@@ -0,0 +1,17 @@
|
|
1
|
+
#include "frostbitten.h"
|
2
|
+
#include "word.h"
|
3
|
+
#include "header.h"
|
4
|
+
|
5
|
+
#ifndef message_h
|
6
|
+
#define message_h
|
7
|
+
void message_init();
|
8
|
+
extern VALUE c_frostbitten_message;
|
9
|
+
// Frostbitten::Message
|
10
|
+
typedef struct _fb_message {
|
11
|
+
VALUE header; // Frostbitten::Header
|
12
|
+
VALUE words; // Array
|
13
|
+
} fb_message;
|
14
|
+
void frostbitten_message_deallocate(fb_message *message);
|
15
|
+
VALUE frostbitten_message_allocate (VALUE klass);
|
16
|
+
char** frostbitten_message_generate(fb_message *message, uint32_t *message_size);
|
17
|
+
#endif
|
@@ -0,0 +1,15 @@
|
|
1
|
+
// Include the Ruby headers and goodies
|
2
|
+
#include "frostbitten.h"
|
3
|
+
#include "word.h"
|
4
|
+
#include "header.h"
|
5
|
+
#include "message.h"
|
6
|
+
|
7
|
+
|
8
|
+
VALUE m_frostbitten;
|
9
|
+
// // The initialization method for this module
|
10
|
+
void Init_native() {
|
11
|
+
m_frostbitten = rb_define_module("Frostbitten");
|
12
|
+
header_init();
|
13
|
+
message_init();
|
14
|
+
word_init();
|
15
|
+
}
|
@@ -0,0 +1,20 @@
|
|
1
|
+
#include "packet.h"
|
2
|
+
|
3
|
+
fb_packet_buffer* read_buffer_from_io(FILE *fp) {
|
4
|
+
fb_packet_buffer *buffer = malloc(sizeof(fb_packet_buffer));
|
5
|
+
fread((void*)&buffer->header, sizeof(uint32_t), 1, fp);
|
6
|
+
fread((void*)&buffer->packetSize, sizeof(uint32_t), 1, fp);
|
7
|
+
fread((void*)&buffer->wordCount, sizeof(uint32_t), 1, fp);
|
8
|
+
return buffer;
|
9
|
+
}
|
10
|
+
|
11
|
+
bool is_packet_valid(fb_packet_buffer *buffer) {
|
12
|
+
if ( !&buffer->header || !&buffer->packetSize ) {
|
13
|
+
return false;
|
14
|
+
}
|
15
|
+
|
16
|
+
if ( buffer->packetSize > 16384 ) {
|
17
|
+
return false;
|
18
|
+
}
|
19
|
+
return true;
|
20
|
+
}
|
@@ -0,0 +1,11 @@
|
|
1
|
+
#include "frostbitten.h"
|
2
|
+
#ifndef packet_h
|
3
|
+
#define packet_h
|
4
|
+
typedef struct _fb_packet_buffer {
|
5
|
+
uint32_t header;
|
6
|
+
uint32_t packetSize;
|
7
|
+
uint32_t wordCount;
|
8
|
+
} fb_packet_buffer;
|
9
|
+
fb_packet_buffer* read_buffer_from_io(FILE *fp);
|
10
|
+
bool is_packet_valid(fb_packet_buffer *buffer);
|
11
|
+
#endif
|
@@ -0,0 +1,60 @@
|
|
1
|
+
#include "word.h"
|
2
|
+
VALUE c_frostbitten_word;
|
3
|
+
void frostbitten_word_deallocate(fb_word *word) {
|
4
|
+
word->len = 0;
|
5
|
+
memset(&word->str[0],0,sizeof(&word->str));
|
6
|
+
word->term = 0x0;
|
7
|
+
ruby_xfree(word);
|
8
|
+
}
|
9
|
+
|
10
|
+
VALUE frostbitten_word_read_from_io(VALUE self, VALUE io) {
|
11
|
+
fb_word *word;
|
12
|
+
Data_Get_Struct(self, fb_word, word);
|
13
|
+
|
14
|
+
rb_io_t *fptr;
|
15
|
+
GetOpenFile(io, fptr);
|
16
|
+
rb_io_check_readable(fptr);
|
17
|
+
|
18
|
+
FILE *fp = rb_io_stdio_file(fptr);
|
19
|
+
fread((void*)&word->len, sizeof(uint32_t),1, fp);
|
20
|
+
fread((void*)&word->str, sizeof(char),word->len, fp);
|
21
|
+
fread((void*)&word->term, sizeof(char), 1, fp);
|
22
|
+
printf("%s", word->str);
|
23
|
+
return self;
|
24
|
+
}
|
25
|
+
|
26
|
+
VALUE frostbitten_word_cls_read_from_io(VALUE self, VALUE io) {
|
27
|
+
return self;
|
28
|
+
}
|
29
|
+
|
30
|
+
VALUE frostbitten_word_write_to_io(VALUE self, VALUE io) {
|
31
|
+
fb_word *word;
|
32
|
+
Data_Get_Struct(self, fb_word, word);
|
33
|
+
|
34
|
+
rb_io_t *fptr;
|
35
|
+
GetOpenFile(io, fptr);
|
36
|
+
rb_io_check_writable(fptr);
|
37
|
+
|
38
|
+
FILE *fp = rb_io_stdio_file(fptr);
|
39
|
+
// fwrite((const void*)&val,sizeof(uint32_t),1,fp);
|
40
|
+
|
41
|
+
rb_io_bufwrite(io, (const void*)&word->len, sizeof(uint32_t));
|
42
|
+
rb_io_bufwrite(io, (const void*)&word->str, sizeof(uint32_t));
|
43
|
+
rb_io_bufwrite(io, (const void*)&word->len, sizeof(uint32_t));
|
44
|
+
return self;
|
45
|
+
}
|
46
|
+
|
47
|
+
VALUE frostbitten_word_allocate (VALUE klass) {
|
48
|
+
fb_word *word = ruby_xmalloc(sizeof(fb_word));
|
49
|
+
return Data_Wrap_Struct(klass, NULL, frostbitten_word_deallocate, word);
|
50
|
+
}
|
51
|
+
|
52
|
+
void word_init() {
|
53
|
+
c_frostbitten_word = rb_define_class_under(m_frostbitten, "Word", rb_cString);
|
54
|
+
rb_define_alloc_func(c_frostbitten_word, frostbitten_word_allocate);
|
55
|
+
|
56
|
+
rb_define_method(c_frostbitten_word, "read", frostbitten_word_read_from_io, 1);
|
57
|
+
rb_define_method(c_frostbitten_word, "write", frostbitten_word_write_to_io, 1);
|
58
|
+
rb_define_singleton_method(c_frostbitten_word, "read", frostbitten_word_cls_read_from_io, 1);
|
59
|
+
|
60
|
+
}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
#include "frostbitten.h"
|
2
|
+
|
3
|
+
#ifndef word_h
|
4
|
+
#define word_h
|
5
|
+
|
6
|
+
extern VALUE c_frostbitten_word;
|
7
|
+
void word_init();
|
8
|
+
typedef struct _fb_word {
|
9
|
+
uint32_t len;
|
10
|
+
char str[INT_MAX];
|
11
|
+
char term;
|
12
|
+
} fb_word;
|
13
|
+
|
14
|
+
void frostbitten_word_deallocate(fb_word *word);
|
15
|
+
VALUE frostbitten_word_allocate (VALUE klass);
|
16
|
+
|
17
|
+
#endif
|
data/frostbitten.gemspec
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "frostbitten"
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Emil Palm"]
|
12
|
+
s.date = "2013-02-22"
|
13
|
+
s.description = "Gem that provides RCON commands for frostbite 2 engine games. BF3, BFBC2 and so on"
|
14
|
+
s.email = "emil.palm@x86.nu"
|
15
|
+
s.extensions = ["ext/frostbitten/extconf.rb"]
|
16
|
+
s.extra_rdoc_files = [
|
17
|
+
"LICENSE.txt",
|
18
|
+
"README.rdoc"
|
19
|
+
]
|
20
|
+
s.files = [
|
21
|
+
".document",
|
22
|
+
"Gemfile",
|
23
|
+
"Gemfile.lock",
|
24
|
+
"Guardfile",
|
25
|
+
"LICENSE.txt",
|
26
|
+
"README.rdoc",
|
27
|
+
"Rakefile",
|
28
|
+
"VERSION",
|
29
|
+
"ext/.DS_Store",
|
30
|
+
"ext/frostbitten/.DS_Store",
|
31
|
+
"ext/frostbitten/Makefile",
|
32
|
+
"ext/frostbitten/extconf.rb",
|
33
|
+
"ext/frostbitten/frostbitten.h",
|
34
|
+
"ext/frostbitten/header.c",
|
35
|
+
"ext/frostbitten/header.h",
|
36
|
+
"ext/frostbitten/message.c",
|
37
|
+
"ext/frostbitten/message.h",
|
38
|
+
"ext/frostbitten/native.c",
|
39
|
+
"ext/frostbitten/packet.c",
|
40
|
+
"ext/frostbitten/packet.h",
|
41
|
+
"ext/frostbitten/word.c",
|
42
|
+
"ext/frostbitten/word.h",
|
43
|
+
"frostbitten.gemspec",
|
44
|
+
"lib/frostbitten.rb",
|
45
|
+
"lib/frostbitten/client.rb",
|
46
|
+
"lib/frostbitten/connection.rb",
|
47
|
+
"lib/frostbitten/map.rb",
|
48
|
+
"lib/frostbitten/methods/admin.rb",
|
49
|
+
"lib/frostbitten/methods/normal.rb",
|
50
|
+
"lib/frostbitten/player.rb",
|
51
|
+
"lib/frostbitten/score.rb",
|
52
|
+
"lib/frostbitten/server.rb",
|
53
|
+
"lib/frostbitten/version.rb",
|
54
|
+
"spec/frostbitten_spec.rb",
|
55
|
+
"spec/lib/frostbitten_client_spec.rb",
|
56
|
+
"spec/lib/frostbitten_server_spec.rb",
|
57
|
+
"spec/spec_helper.rb",
|
58
|
+
"test/helper.rb",
|
59
|
+
"test/test_frostbitten.rb"
|
60
|
+
]
|
61
|
+
s.homepage = "http://github.com/mrevilme/frostbitten"
|
62
|
+
s.licenses = ["MIT"]
|
63
|
+
s.require_paths = ["lib"]
|
64
|
+
s.rubygems_version = "1.8.24"
|
65
|
+
s.summary = "Gem that provides RCON commands for frostbite 2 engine games. BF3, BFBC2 and so on"
|
66
|
+
|
67
|
+
if s.respond_to? :specification_version then
|
68
|
+
s.specification_version = 3
|
69
|
+
|
70
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
71
|
+
s.add_runtime_dependency(%q<hashie>, [">= 0"])
|
72
|
+
s.add_development_dependency(%q<shoulda>, [">= 0"])
|
73
|
+
s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
|
74
|
+
s.add_development_dependency(%q<bundler>, [">= 0"])
|
75
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.8.4"])
|
76
|
+
s.add_development_dependency(%q<rspec>, [">= 0"])
|
77
|
+
s.add_development_dependency(%q<rb-fsevent>, [">= 0"])
|
78
|
+
s.add_development_dependency(%q<guard-rspec>, [">= 0"])
|
79
|
+
s.add_development_dependency(%q<terminal-notifier-guard>, [">= 0"])
|
80
|
+
s.add_development_dependency(%q<rake-compiler>, [">= 0"])
|
81
|
+
else
|
82
|
+
s.add_dependency(%q<hashie>, [">= 0"])
|
83
|
+
s.add_dependency(%q<shoulda>, [">= 0"])
|
84
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
85
|
+
s.add_dependency(%q<bundler>, [">= 0"])
|
86
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
|
87
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
88
|
+
s.add_dependency(%q<rb-fsevent>, [">= 0"])
|
89
|
+
s.add_dependency(%q<guard-rspec>, [">= 0"])
|
90
|
+
s.add_dependency(%q<terminal-notifier-guard>, [">= 0"])
|
91
|
+
s.add_dependency(%q<rake-compiler>, [">= 0"])
|
92
|
+
end
|
93
|
+
else
|
94
|
+
s.add_dependency(%q<hashie>, [">= 0"])
|
95
|
+
s.add_dependency(%q<shoulda>, [">= 0"])
|
96
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
97
|
+
s.add_dependency(%q<bundler>, [">= 0"])
|
98
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
|
99
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
100
|
+
s.add_dependency(%q<rb-fsevent>, [">= 0"])
|
101
|
+
s.add_dependency(%q<guard-rspec>, [">= 0"])
|
102
|
+
s.add_dependency(%q<terminal-notifier-guard>, [">= 0"])
|
103
|
+
s.add_dependency(%q<rake-compiler>, [">= 0"])
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
data/lib/frostbitten.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'frostbitten/native'
|
2
|
+
require 'frostbitten/version'
|
3
|
+
require 'frostbitten/connection'
|
4
|
+
require 'frostbitten/client'
|
5
|
+
require 'frostbitten/player'
|
6
|
+
require 'frostbitten/server'
|
7
|
+
require 'frostbitten/score'
|
8
|
+
require 'frostbitten/map'
|
9
|
+
|
10
|
+
# Author:: Emil Palm (mailto:emil@x86.nu)
|
11
|
+
# Copyright:: Copyright (c) 2013 Emil Palm
|
12
|
+
# License:: Distributes under the same terms as Ruby
|
13
|
+
|
14
|
+
# This module will give you a shorthand to a client object.
|
15
|
+
# very useful if your only connecting to one server
|
16
|
+
|
17
|
+
module Frostbitten
|
18
|
+
# Setup the module with a URI 'fbrcon://password@ip:port'
|
19
|
+
# Also accepts options { :keepalive = true/false, :socket_timeout => 1 }
|
20
|
+
def self.setup(uri,options={})
|
21
|
+
@client = Frostbitten::Client.new(uri,options)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Method to return the client object if raw access is needed
|
25
|
+
def self.conn
|
26
|
+
return @client
|
27
|
+
end
|
28
|
+
|
29
|
+
# :nodoc:
|
30
|
+
def self.method_missing(meth, *args, &block)
|
31
|
+
if @client and @client.respond_to?(meth)
|
32
|
+
@client.public_send(meth, *args, &block)
|
33
|
+
else
|
34
|
+
super
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
require 'frostbitten/methods/admin'
|
3
|
+
require 'frostbitten/methods/normal'
|
4
|
+
|
5
|
+
# Author:: Emil Palm (mailto:emil@x86.nu)
|
6
|
+
# Copyright:: Copyright (c) 2013 Emil Palm
|
7
|
+
# License:: Distributes under the same terms as Ruby
|
8
|
+
|
9
|
+
# This class will give you a shorthand to a Server object.
|
10
|
+
# and give you a easy way to execute methods on server
|
11
|
+
# that are predefined in this class.
|
12
|
+
|
13
|
+
module Frostbitten
|
14
|
+
class Client
|
15
|
+
# Access to the server object.
|
16
|
+
attr_accessor :connection
|
17
|
+
|
18
|
+
# Initialize class with URI 'fbrcon://password@ip:port'
|
19
|
+
# Also accept options
|
20
|
+
# { :keepalive = true/false, :socket_timeout => 1 }
|
21
|
+
# will setup connection object.
|
22
|
+
def initialize(uri=nil, options={})
|
23
|
+
if uri
|
24
|
+
self.connection = Frostbitten::Connection.new(uri,options)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Will create Frostbitten::Header and Frostbitten::Message
|
29
|
+
# objects for given commands.
|
30
|
+
# Accepts either a string or a array of strings to send.
|
31
|
+
# Returns a list of words returned by server.
|
32
|
+
def send(commands)
|
33
|
+
return unless self.connection
|
34
|
+
|
35
|
+
if commands.is_a? String
|
36
|
+
commands = [commands]
|
37
|
+
end
|
38
|
+
|
39
|
+
header = Frostbitten::Header.new(:origin => :client, :response => false)
|
40
|
+
msg = Frostbitten::Message.new(:words => commands, :header => header)
|
41
|
+
message = connection.send(msg)
|
42
|
+
return message.words[1..-1]
|
43
|
+
end
|
44
|
+
|
45
|
+
include Frostbitten::Methods::AdminCommands
|
46
|
+
include Frostbitten::Methods::NormalCommands
|
47
|
+
# :include:methods/admin.rb
|
48
|
+
# :include:methods/normal.rb
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'uri/generic'
|
2
|
+
require 'socket'
|
3
|
+
module URI
|
4
|
+
class FrostbiteRCON < Generic
|
5
|
+
COMPONENT = [:scheme,:host,:port]
|
6
|
+
end
|
7
|
+
@@schemes['fbrcon'] = FrostbiteRCON
|
8
|
+
end
|
9
|
+
|
10
|
+
module Frostbitten
|
11
|
+
class Connection
|
12
|
+
DEFAULTS = {
|
13
|
+
# connect/read/write timeout for socket operations
|
14
|
+
:socket_timeout => 0.5,
|
15
|
+
|
16
|
+
:keepalive => true
|
17
|
+
}
|
18
|
+
|
19
|
+
attr_accessor :hostname, :port, :password
|
20
|
+
attr_reader :sock, :sequence, :timeout
|
21
|
+
|
22
|
+
# Initialize class with URI 'fbrcon://password@ip:port'
|
23
|
+
# Also accept options
|
24
|
+
# { :keepalive = true/false, :socket_timeout => 1 }
|
25
|
+
|
26
|
+
def initialize(uri, options={})
|
27
|
+
server_parsed = URI.parse(uri)
|
28
|
+
self.hostname = server_parsed.host
|
29
|
+
self.password = server_parsed.user
|
30
|
+
self.port = server_parsed.port ||= 47200
|
31
|
+
|
32
|
+
@sock = nil
|
33
|
+
@sequence = options[:sequence] ||= 0
|
34
|
+
|
35
|
+
@options = DEFAULTS.merge(options)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns if the socket is alive of closed
|
39
|
+
def alive?
|
40
|
+
return true if @sock
|
41
|
+
return false
|
42
|
+
end
|
43
|
+
|
44
|
+
# Connects socket
|
45
|
+
def connect
|
46
|
+
return if alive?
|
47
|
+
@sock = TCPSocket.open(self.hostname, self.port);
|
48
|
+
if @options[:socket_timeout]
|
49
|
+
secs = Integer(@options[:socket_timeout])
|
50
|
+
usecs = Integer((@options[:socket_timeout] - secs) * 1_000_000)
|
51
|
+
optval = [secs, usecs].pack("l_2")
|
52
|
+
@sock.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval
|
53
|
+
@sock.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval
|
54
|
+
end
|
55
|
+
|
56
|
+
if @options[:keepalive]
|
57
|
+
@sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Close socket
|
62
|
+
def close
|
63
|
+
return unless alive?
|
64
|
+
@sock.close
|
65
|
+
@sock = nil
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
# Accepts a Frostbitten::Mesage and sends it over socket
|
70
|
+
# updates message.header.sequence with a incremented version
|
71
|
+
# of server sequence counter.
|
72
|
+
# can raise NameError, ArgumentError or StandardError based on return data
|
73
|
+
# from server.
|
74
|
+
def send(message)
|
75
|
+
connect unless self.alive?
|
76
|
+
unless message.header.is_response?
|
77
|
+
@sequence += 1
|
78
|
+
end
|
79
|
+
|
80
|
+
message.header.sequence = self.sequence
|
81
|
+
message.write @sock
|
82
|
+
|
83
|
+
m = Message.new()
|
84
|
+
m.read @sock
|
85
|
+
|
86
|
+
unless m.words.first == "OK"
|
87
|
+
m.words.first.tap do |word|
|
88
|
+
if word == "UnknownCommand"
|
89
|
+
raise NameError, "UnknownCommand raised by server"
|
90
|
+
elsif word == "InvalidArguments"
|
91
|
+
raise ArgumentError, "InvalidArguments raised by server"
|
92
|
+
else
|
93
|
+
raise StandardError, "Server returned #{word}"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
return m
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|