Skip to content
This repository has been archived by the owner on Nov 21, 2018. It is now read-only.

Minecraft protocol library #7

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 40 additions & 2 deletions lib/component.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,43 @@
module Component
def self.build(&block)
Component::Builder.new(&block).to_component
class << self
def build(&block)
Component::Builder.new(&block).to_component
end

def from_json(json)
Component::Base.json_create(json)
end

def from_legacy_text(legacy, **args)
extra = []
ChatUtils.formatted_spans(legacy) do |text, formats|
c = {}
formats.each do |format|
if format.color?
c[:color] = format.name.downcase
else
case format
when ChatColor::BOLD
c[:bold] = true
when ChatColor::ITALIC
c[:italic] = true
when ChatColor::UNDERLINED
c[:underlined] = true
when ChatColor::STRIKETHROUGH
c[:strikethrough] = true
when ChatColor::OBFUSCATED
c[:obfuscated] = true
end
end
end
extra << Component::Text.new(text, **c)
end

if extra.size == 1 && args.empty?
extra[0]
else
Component::Text.new('', **args.merge(extra: [*extra, *args[:extra]]))
end
end
end
end
19 changes: 15 additions & 4 deletions lib/component/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,21 @@ class << self
def assert_flag(flag)
FLAGS.include?(flag) or raise ArgumentError, "Unknown formatting flag '#{flag}'"
end

def json_create(json)
if json.is_a?(String)
Component.from_legacy_text(json)
else
args = json_create_args(json)
if text = json['text']
Component.from_legacy_text(text, **args)
elsif translate = json['translate']
Translate.new(translate: translate, with: json['with'] || [], **args)
else
new(**args)
end
end
end
end

def get_flag(flag)
Expand Down Expand Up @@ -42,10 +57,6 @@ def as_json(*)
@json ||= full_json.freeze
end

def self.json_create(json)
new(**json_create_args(json))
end

protected

def full_json
Expand Down
File renamed without changes.
8 changes: 6 additions & 2 deletions lib/ext/ocn/mass_assignment_security.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ module DocumentExtensions
# This is missing from Mongoid's configuration, so we have to do it this way.
# See #ActiveModel::MassAssignmentSecurity::RavenSanitizer
included do
self.mass_assignment_sanitizer = if ['production', 'test'].include? Rails.env
self.mass_assignment_sanitizer = if Object::const_defined?(:Raven) && ['production', 'test'].include?(Rails.env)
RavenSanitizer.new(self)
else
:strict
Expand All @@ -92,7 +92,11 @@ def sanitize_for_mass_assignment(attributes, role = nil)

# For whatever reason, Devise tries to mass-assign the email field after a failed login,
# and generates lots of pointless errors. This is the best solution I could think of.
if self.is_a?(User) && attributes.key?('email') && !caller.grep(%r[devise/sessions_controller]).empty?
if Object::const_defined?(:Devise) &&
self.is_a?(Devise::Models::Confirmable) &&
attributes.key?('email') &&
!caller.grep(%r[devise/sessions_controller]).empty?

attributes.delete('email')
end

Expand Down
26 changes: 15 additions & 11 deletions lib/ext/raven.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
module Ext
module Raven
class HttpInterface < ::Raven::HttpInterface
name 'request'
if Object::const_defined? :Raven
class HttpInterface < ::Raven::HttpInterface
name 'request'

def from_rack(env)
# ActionDispatch::ShowExceptions changes PATH_INFO to the error page
# before it gets here, so we need to change it back.
if original_path = env['action_dispatch.original_path']
env = env.merge('PATH_INFO' => original_path)
def from_rack(env)
# ActionDispatch::ShowExceptions changes PATH_INFO to the error page
# before it gets here, so we need to change it back.
if original_path = env['action_dispatch.original_path']
env = env.merge('PATH_INFO' => original_path)
end
super(env)
end
super(env)
end
end

Expand Down Expand Up @@ -58,7 +60,9 @@ def send_event_async(event)
end
end

::Raven.extend(::Ext::Raven::ClassMethods)
if Object::const_defined? :Raven
::Raven.extend(::Ext::Raven::ClassMethods)

# This should replace the one already registered
::Raven.register_interface http: ::Ext::Raven::HttpInterface
# This should replace the one already registered
::Raven.register_interface http: ::Ext::Raven::HttpInterface
end
111 changes: 111 additions & 0 deletions lib/minecraft/protocol.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
require_dependency 'minecraft/protocol/packet'

module Minecraft
module Protocol

VERSION = 316
PROTOCOLS = [:handshaking, :status, :login, :play]

class ServerInfo
attr :json,
:version, :protocol,
:max_players, :online_players,
:description, :icon,
:map_name, :map_icon,
:participants, :observers

def decode_icon(uri)
if uri && uri =~ %r{^data:image/png;base64,(.*)$}m
Base64.decode64($1)
end
end

def initialize(json)
@json = json
if version = json['version']
@version = version['name']
@protocol = version['protocol']
end
if players = json['players']
@max_players = players['max']
@online_players = players['online']
end
@description = json['description']
if icon = json['favicon']
@icon = decode_icon(icon)
end
if pgm = json['pgm']
@participants = pgm['participants'].to_i
@observers = pgm['observers'].to_i

if map = pgm['map']
@map_name = map['name']
@map_icon = decode_icon(map['icon'])
end
end
end

def pgm?
json.key?('pgm')
end
end

class Client
def initialize(host:, port: 25565)
@host = host
@port = port
@io = TCPSocket.new(host, port)
@protocol = :handshaking
end

def read
packet = Packet.read(@io, @protocol, :clientbound)
puts "<<< #{packet.inspect}"
packet
end

def write(packet)
puts ">>> #{packet.inspect}"
packet.write(@io)
if packet.is_a?(Packet::Handshaking::In::SetProtocol)
@protocol = PROTOCOLS[packet.next_state]
end
end

def handshake(protocol)
write Packet::Handshaking::In::SetProtocol.new(
protocol_version: VERSION,
server_address: @host,
server_port: @port,
next_state: PROTOCOLS.index(protocol)
)
end

def ping(payload = nil)
handshake(:status)
write(Packet::Status::In::Ping.new(payload: payload || Time.now.to_i))
response = read.payload
@io.close
response
end

def status
handshake(:status)
write(Packet::Status::In::Start.new)
json = JSON.parse(read.json)
@io.close
ServerInfo.new(json)
end
end

class << self
def ping(host:, port: 25565, payload: nil)
Client.new(host: host, port: port).ping(payload)
end

def status(host:, port: 25565)
Client.new(host: host, port: port).status
end
end
end
end
108 changes: 108 additions & 0 deletions lib/minecraft/protocol/data.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
module Minecraft
module Protocol
class Transcoder
def values
@values ||= []
end

def initialize(io)
@io = io
end

def pack(length, format)
raise NoMethodError
end

def byte
pack(1, 'c')
end

def ubyte
pack(1, 'C')
end

def short
pack(2, 's>')
end

def ushort
pack(2, 'S>')
end

def integer
pack(4, 'i>')
end

def long
pack(8, 'q>')
end

def float
pack(4, 'g')
end

def double
pack(8, 'G')
end
end

class Decoder < Transcoder
def pack(length, format)
values << @io.read(length).unpack(format)[0]
end

def varnum(len)
n = v = 0
loop do
b = @io.read(1).ord
v |= (b & 0x7f) << (7 * n)
break if b & 0x80 == 0
n += 1
raise "VarInt too long" if n > len
end
values << v
end

def varint
varnum(5)
end

def varlong
varnum(10)
end

def string
varint
values << @io.read(values.pop)
end
end

class Encoder < Transcoder
def pack(length, format)
@io.write([values.shift].pack(format))
end

def varint
v = values.shift % 0x1_0000_0000
loop do
b = v & 0x7f
v >>= 7
b |= 0x80 unless v == 0
@io.putc(b)
break if v == 0
end
end

def varlong
varint
end

def string
v = values.shift
values.unshift(v.size)
varint
@io.write(v)
end
end
end
end
Loading