RGSS3 TCP/IP implementation

Started by orochii, May 01, 2014, 12:24:42 am

Previous topic - Next topic

orochii

Hello! Now, I was reading a little here and there, mostly stood upon this thing and I got pretty fueled: http://lthzelda.wordpress.com/2010/04/28/rm-4-tcp-sockets-in-rpg-maker-vx/
I've always wanted to try out and make a game with some online capabilities. Not an MMO, just connect to a friend, and do something, like battles a-la-RPG style. Akin to Pokémon games I suppose.

So I started messing up with the code in that article. But then I found some nasty stuff I still don't get x'D, and I got pretty confused.

Take into account, I'm using the code the guy... umm... took from RMX-OS. I'll put it here for reference.
#==============================================================================
# ** Module Win32 - Handles numerical based data.
#------------------------------------------------------------------------------
# Author    Ruby
# Version   1.8.1
#==============================================================================

module Win32

  #----------------------------------------------------------------------------
  # ● Retrieves data from a pointer.
  #----------------------------------------------------------------------------
  def copymem(len)
    buf = "\0" * len
    Win32API.new('kernel32', 'RtlMoveMemory', 'ppl', '').call(buf, self, len)
    buf
  end
 
end

# Extends the numeric class.
class Numeric
  include Win32
end

# Extends the string class.
class String
  include Win32
end

#==============================================================================
# ** Module Winsock - Maps out the functions held in the Winsock DLL.
#------------------------------------------------------------------------------
# Author    Ruby
# Version   1.8.1
#==============================================================================

module Winsock

  DLL = 'ws2_32'

  #----------------------------------------------------------------------------
  # * Accept Connection
  #----------------------------------------------------------------------------
  def self.accept(*args)
    Win32API.new(DLL, 'accept', 'ppl', 'l').call(*args)
  end
  #----------------------------------------------------------------------------
  # * Bind
  #----------------------------------------------------------------------------
  def self.bind(*args)
    Win32API.new(DLL, 'bind', 'ppl', 'l').call(*args)
  end
  #----------------------------------------------------------------------------
  # * Close Socket
  #----------------------------------------------------------------------------
  def self.closesocket(*args)
    Win32API.new(DLL, 'closesocket', 'p', 'l').call(*args)
  end 
  #----------------------------------------------------------------------------
  # * Connect
  #----------------------------------------------------------------------------
  def self.connect(*args)
    Win32API.new(DLL, 'connect', 'ppl', 'l').call(*args)
  end   
  #----------------------------------------------------------------------------
  # * Get host (Using Adress)
  #----------------------------------------------------------------------------
  def self.gethostbyaddr(*args)
    Win32API.new(DLL, 'gethostbyaddr', 'pll', 'l').call(*args)
  end
  #----------------------------------------------------------------------------
  # * Get host (Using Name)
  #----------------------------------------------------------------------------
  def self.gethostbyname(*args)
    Win32API.new(DLL, 'gethostbyname', 'p', 'l').call(*args)
  end
  #----------------------------------------------------------------------------
  # * Get host's Name
  #----------------------------------------------------------------------------
  def self.gethostname(*args)
    Win32API.new(DLL, 'gethostname', 'pl', '').call(*args)
  end
  #----------------------------------------------------------------------------
  # * Get Server (Using Name)
  #----------------------------------------------------------------------------
  def self.getservbyname(*args)
    Win32API.new(DLL, 'getservbyname', 'pp', 'p').call(*args)
  end
  #----------------------------------------------------------------------------
  # * HT OnL
  #----------------------------------------------------------------------------
  def self.htonl(*args)
    Win32API.new(DLL, 'htonl', 'l', 'l').call(*args)
  end
  #----------------------------------------------------------------------------
  # * HT OnS
  #----------------------------------------------------------------------------
  def self.htons(*args)
    Win32API.new(DLL, 'htons', 'l', 'l').call(*args)
  end
  #----------------------------------------------------------------------------
  # * Inet Adress
  #----------------------------------------------------------------------------
  def self.inet_addr(*args)
    Win32API.new(DLL, 'inet_addr', 'p', 'l').call(*args)
  end
  #----------------------------------------------------------------------------
  # * Inet NtOA
  #----------------------------------------------------------------------------
  def self.inet_ntoa(*args)
    Win32API.new(DLL, 'inet_ntoa', 'l', 'p').call(*args)
  end 
  #----------------------------------------------------------------------------
  # * Listen
  #----------------------------------------------------------------------------
  def self.listen(*args)
    Win32API.new(DLL, 'listen', 'pl', 'l').call(*args)
  end
  #----------------------------------------------------------------------------
  # * Recieve
  #----------------------------------------------------------------------------
  def self.recv(*args)
    Win32API.new(DLL, 'recv', 'ppll', 'l').call(*args)
  end
  #----------------------------------------------------------------------------
  # * Select
  #----------------------------------------------------------------------------
  def self.select(*args)
    Win32API.new(DLL, 'select', 'lpppp', 'l').call(*args)
  end
  #----------------------------------------------------------------------------
  # * Send
  #----------------------------------------------------------------------------
  def self.send(*args)
    Win32API.new(DLL, 'send', 'ppll', 'l').call(*args)
  end
  #----------------------------------------------------------------------------
  # * Set Socket Options
  #----------------------------------------------------------------------------
  def self.setsockopt(*args)
    Win32API.new(DLL, 'setsockopt', 'pllpl', 'l').call(*args)
  end 
  #----------------------------------------------------------------------------
  # * Shutdown
  #----------------------------------------------------------------------------
  def self.shutdown(*args)
    Win32API.new(DLL, 'shutdown', 'pl', 'l').call(*args)
  end
  #----------------------------------------------------------------------------
  # * Socket
  #----------------------------------------------------------------------------
  def self.socket(*args)
    Win32API.new(DLL, 'socket', 'lll', 'l').call(*args) 
  end
  #----------------------------------------------------------------------------
  # * Get Last Error
  #----------------------------------------------------------------------------
  def self.WSAGetLastError(*args)
    Win32API.new(DLL, 'WSAGetLastError', '', 'l').call(*args)
  end

end

#==============================================================================
# ** Socket - Creates and manages sockets.
#------------------------------------------------------------------------------
# Author    Ruby
# Version   1.8.1
#==============================================================================

class Socket

  #----------------------------------------------------------------------------
  # ● Constants
  #----------------------------------------------------------------------------
  AF_UNSPEC                 = 0 
  AF_UNIX                   = 1
  AF_INET                   = 2
  AF_IPX                    = 6
  AF_APPLETALK              = 16

  PF_UNSPEC                 = 0 
  PF_UNIX                   = 1
  PF_INET                   = 2
  PF_IPX                    = 6
  PF_APPLETALK              = 16

  SOCK_STREAM               = 1
  SOCK_DGRAM                = 2
  SOCK_RAW                  = 3
  SOCK_RDM                  = 4
  SOCK_SEQPACKET            = 5
 
  IPPROTO_IP                = 0
  IPPROTO_ICMP              = 1
  IPPROTO_IGMP              = 2
  IPPROTO_GGP               = 3
  IPPROTO_TCP               = 6
  IPPROTO_PUP               = 12
  IPPROTO_UDP               = 17
  IPPROTO_IDP               = 22
  IPPROTO_ND                = 77
  IPPROTO_RAW               = 255
  IPPROTO_MAX               = 256

  SOL_SOCKET                = 65535
 
  SO_DEBUG                  = 1
  SO_REUSEADDR              = 4
  SO_KEEPALIVE              = 8
  SO_DONTROUTE              = 16
  SO_BROADCAST              = 32
  SO_LINGER                 = 128
  SO_OOBINLINE              = 256
  SO_RCVLOWAT               = 4100
  SO_SNDTIMEO               = 4101
  SO_RCVTIMEO               = 4102
  SO_ERROR                  = 4103
  SO_TYPE                   = 4104
  SO_SNDBUF                 = 4097
  SO_RCVBUF                 = 4098
  SO_SNDLOWAT               = 4099
 
  TCP_NODELAY               = 1
 
  MSG_OOB                   = 1
  MSG_PEEK                  = 2
  MSG_DONTROUTE             = 4
 
  IP_OPTIONS                = 1
  IP_DEFAULT_MULTICAST_LOOP = 1
  IP_DEFAULT_MULTICAST_TTL  = 1
  IP_MULTICAST_IF           = 2
  IP_MULTICAST_TTL          = 3
  IP_MULTICAST_LOOP         = 4
  IP_ADD_MEMBERSHIP         = 5
  IP_DROP_MEMBERSHIP        = 6
  IP_TTL                    = 7
  IP_TOS                    = 8
  IP_MAX_MEMBERSHIPS        = 20

  EAI_ADDRFAMILY            = 1
  EAI_AGAIN                 = 2
  EAI_BADFLAGS              = 3
  EAI_FAIL                  = 4
  EAI_FAMILY                = 5
  EAI_MEMORY                = 6
  EAI_NODATA                = 7
  EAI_NONAME                = 8
  EAI_SERVICE               = 9
  EAI_SOCKTYPE              = 10
  EAI_SYSTEM                = 11
  EAI_BADHINTS              = 12
  EAI_PROTOCOL              = 13
  EAI_MAX                   = 14

  AI_PASSIVE                = 1
  AI_CANONNAME              = 2
  AI_NUMERICHOST            = 4
  AI_MASK                   = 7
  AI_ALL                    = 256
  AI_V4MAPPED_CFG           = 512
  AI_ADDRCONFIG             = 1024
  AI_DEFAULT                = 1536
  AI_V4MAPPED               = 2048
 
  #----------------------------------------------------------------------------
  # ● Returns the associated IP address for the given hostname.
  #---------------------------------------------------------------------------- 
  def self.getaddress(host)
    gethostbyname(host)[3].unpack('C4').join('.')
  end
  #----------------------------------------------------------------------------
  # ● Returns the associated IP address for the given hostname.
  #---------------------------------------------------------------------------- 
  def self.getservice(serv)
    case serv
    when Numeric
      return serv
    when String
      return getservbyname(serv)
    else
      raise 'Please us an interger or string for services.'
    end
  end
  #----------------------------------------------------------------------------
  # ● Returns information about the given hostname.
  #----------------------------------------------------------------------------
  def self.gethostbyname(name)
    raise SocketError::ENOASSOCHOST if (ptr = Winsock.gethostbyname(name)) == 0
    host = ptr.copymem(16).unpack('iissi')
    [host[0].copymem(64).split("\0")[0], [], host[2], host[4].copymem(4).unpack('l')[0].copymem(4)]
  end
  #----------------------------------------------------------------------------
  # ● Returns the user's hostname.
  #---------------------------------------------------------------------------- 
  def self.gethostname
    buf = "\0" * 256
    Winsock.gethostname(buf, 256)
    buf.strip
  end
  #----------------------------------------------------------------------------
  # ● Returns information about the given service.
  #----------------------------------------------------------------------------
  def self.getservbyname(name)
    case name
    when /echo/i
      return 7
    when /daytime/i
      return 13
    when /ftp/i
      return 21
    when /telnet/i
      return 23
    when /smtp/i
      return 25
    when /time/i
      return 37
    when /http/i
      return 80
    when /pop/i
      return 110
    else
      raise 'Service not recognized.'
    end
  end
  #----------------------------------------------------------------------------
  # ● Creates an INET-sockaddr struct.
  #---------------------------------------------------------------------------- 
  def self.sockaddr_in(port, host)
    begin
      [AF_INET, getservice(port)].pack('sn') + gethostbyname(host)[3] + [].pack('x8')
    rescue
    end
  end
  #----------------------------------------------------------------------------
  # ● Creates a new socket and connects it to the given host and port.
  #---------------------------------------------------------------------------- 
  def self.open(*args)
    socket = new(*args)
    if block_given?
      begin
        yield socket
      ensure
        socket.close
      end
    end
    nil
  end
  #----------------------------------------------------------------------------
  # ● Creates a new socket.
  #---------------------------------------------------------------------------- 
  def initialize(domain, type, protocol)
    SocketError.check if (@fd = Winsock.socket(domain, type, protocol)) == -1
    @fd
  end
  #----------------------------------------------------------------------------
  # ● Accepts incoming connections.
  #---------------------------------------------------------------------------- 
  def accept(flags = 0)
    buf = "\0" * 16
    SocketError.check if Winsock.accept(@fd, buf, flags) == -1
    buf
  end
  #----------------------------------------------------------------------------
  # ● Binds a socket to the given sockaddr.
  #---------------------------------------------------------------------------- 
  def bind(sockaddr)
    SocketError.check if (ret = Winsock.bind(@fd, sockaddr, sockaddr.size)) == -1
    ret
  end
  #----------------------------------------------------------------------------
  # ● Closes a socket.
  #---------------------------------------------------------------------------- 
  def close
    SocketError.check if (ret = Winsock.closesocket(@fd)) == -1
    ret
  end
  #----------------------------------------------------------------------------
  # ● Connects a socket to the given sockaddr.
  #---------------------------------------------------------------------------- 
  def connect(sockaddr)
    SocketError.check if (ret = Winsock.connect(@fd, sockaddr, sockaddr.size)) == -1
    ret
  end
  #----------------------------------------------------------------------------
  # ● Listens for incoming connections.
  #---------------------------------------------------------------------------- 
  def listen(backlog)
    SocketError.check if (ret = Winsock.listen(@fd, backlog)) == -1
    ret
  end
  #----------------------------------------------------------------------------
  # ● Checks waiting data's status.
  #---------------------------------------------------------------------------- 
  def select(timeout)
    SocketError.check if (ret = Winsock.select(1, [1, @fd].pack('ll'), 0, 0, [timeout, timeout * 1000000].pack('ll'))) == -1
    ret
  end
  #----------------------------------------------------------------------------
  # ● Checks if data is waiting.
  #---------------------------------------------------------------------------- 
  def ready?
    not select(0) == 0
  end 
  #----------------------------------------------------------------------------
  # ● Reads data from socket.
  #---------------------------------------------------------------------------- 
  def read(len)
    buf = "\0" * len
    Win32API.new('msvcrt', '_read', 'lpl', 'l').call(@fd, buf, len)
    buf
  end
  #----------------------------------------------------------------------------
  # ● Returns recieved data.
  #---------------------------------------------------------------------------- 
  def recv(len, flags = 0)
    buf = "\0" * len
    SocketError.check if Winsock.recv(@fd, buf, buf.size, flags) == -1
    buf
  end
  #----------------------------------------------------------------------------
  # ● Sends data to a host.
  #---------------------------------------------------------------------------- 
  def send(data, flags = 0)
    SocketError.check if (ret = Winsock.send(@fd, data, data.size, flags)) == -1
    ret
  end
  #----------------------------------------------------------------------------
  # ● Writes data to socket.
  #---------------------------------------------------------------------------- 
  def write(data)
    Win32API.new('msvcrt', '_write', 'lpl', 'l').call(@fd, data, 1)
  end

end

#==============================================================================
# ** TCPSocket - Creates and manages TCP sockets.
#------------------------------------------------------------------------------
# Author    Ruby
# Version   1.8.1
#==============================================================================

class TCPSocket < Socket

  #----------------------------------------------------------------------------
  # ● Creates a new socket and connects it to the given host and port.
  #---------------------------------------------------------------------------- 
  def self.open(*args)
    socket = new(*args)
    if block_given?
      begin
        yield socket
      ensure
        socket.close
      end
    end
    nil
  end
  #----------------------------------------------------------------------------
  # ● Creates a new socket and connects it to the given host and port.
  #---------------------------------------------------------------------------- 
  def initialize(host, port)
    super(AF_INET, SOCK_STREAM, IPPROTO_TCP)
    connect(Socket.sockaddr_in(port, host))
  end
 
end

#==============================================================================
# ** SocketError
#------------------------------------------------------------------------------
# Default exception class for sockets.
#==============================================================================

class SocketError < StandardError
 
  ENOASSOCHOST = 'getaddrinfo: no address associated with hostname.'
 
  def self.check
    errno = Winsock.WSAGetLastError
    raise Errno.const_get(Errno.constants.detect { |c| Errno.const_get(c).new.errno == errno })
  end
 
end


I started by getting an error in this line.
SocketError.check if (ret = Winsock.connect(@fd, sockaddr, sockaddr.size)) == -1

Basically, it said that sockaddr was nil, so "size" didn't exist. I got deeper, following the trace...
def self.sockaddr_in(port, host)
    begin
      [AF_INET, getservice(port)].pack('sn') + gethostbyname(host)[3] + [].pack('x8')
    rescue
     
    end
  end

I then "removed" the rescue, just to know what error was I getting there.
It sent me to this part:
#----------------------------------------------------------------------------
  # ● Returns information about the given hostname.
  #----------------------------------------------------------------------------
  def self.gethostbyname(name)
    raise SocketError::ENOASSOCHOST if (ptr = Winsock.gethostbyname(name)) == 0
    host = ptr.copymem(16).unpack('iissi')
    [host[0].copymem(64).split("\0")[0], [], host[2], host[4].copymem(4).unpack('l')[0].copymem(4)]
  end

Was getting something about an invalid UTF-8 string. I read somewhere that Ruby 1.9.2 (the version that RGSS3 uses) strings are compatible with nul-ended strings (C chars and the such), so I just changed this accordingly, no need to cut the null char at the end.
[host[0].copymem(64), [], host[2], host[4].copymem(4).unpack('l')[0].copymem(4)]


But then, it came to a dead end. I don't know what the error I get means x'D. Tried the original code on both XP and VX projects, and none of them seemed to work either.


This is the error I get on VX/XP.


And this is the one I get on my RGSS3 XP game (pretty sure the same happens on a VXA project).

I somehow think that both errors are the same, but what changes is the nil handling. But I don't really know.

Again, no pressure. This is a feature I have zillions of seconds to develop. But wanted to try and ask if someone has any idea. Right now, I just have a small headache xD.

Salutations to all of you, ladies and gentlemen.

Blizzard

The main problem is how the C-API handles data. In this the thing is that the C-API actually allocated enough memory for some sort of structure, but it ends up being destroyed so the entire memory block is copied. It might not be the very best way to about things, but it is good (and systematic) enough to make things work well. Long story short: This is needed to ensure the memory stays allocated.
Check out Daygames and our games:

King of Booze 2      King of Booze: Never Ever
Drinking Game for Android      Never have I ever for Android
Drinking Game for iOS      Never have I ever for iOS


Quote from: winkioI do not speak to bricks, either as individuals or in wall form.

Quote from: Barney StinsonWhen I get sad, I stop being sad and be awesome instead. True story.

orochii

Hmmm... well, that's the kind of things I was afraid of doing wrong. Thanks Blizz.

BTW, I gambled and continued the tutorial ignoring the error. Got a connection working, little hurray for me.

(Tutorial says "don't use Eclipse better use this and that", but already had Eclipse, so... easier for me).

I want to try making some tests with send and receive. Maybe a tic-tac-toe.

orochii

Ummm okay, sorry for doubleposting. It seems my little happiness didn't last for so long  :facepalm:. Thing is, since it will be communication from game instance to game instance, I need to set one of them as host and the other as client. But to make the host, I think I need to make the TCPServer class. I've been diving into Ruby's source and other stuff, and I don't understand a shit x'DDD.

I'm sorry, but if anyone could give me a little pointers... I feel as bad as the Power Glove.

matmatmat

I know that this point is really old, but I was looking at the same tutorial as you and getting the same error, I did a bit of googling and by pure luck I found out what the problem was.

It turns in C, errno returns a value of 0 if no error has occurred. To solve this was extremely simple:

def self.check
    errno = Winsock.WSAGetLastError
   
    if errno == 0
      return
    end
   
    constName = Errno.constants.detect {|c| Errno.const_get(c).new.errno == errno }   
   
    if constName
      raise Errno.const_get(constName)
    else
      raise "Unknown network error code: #{errno}"
    end
  end