#!/usr/bin/ruby -w
#
# RagnarokOnline Login monitor
# Author: Tatsuki Sugiura <sugi@nemui.org>
# License: GPL v2
#
require 'socket'

class ROJoinMonitor < Hash
  class ServerError
    class Unknown < StandardError; end
    class ConnectionDied < StandardError; end
    class LastLoginDataRemaind < StandardError; end
    class InvalidUserID < StandardError; end
  end

  Version       = "1.10"
  @@loginheader = "\x64\x00\x06\x00\x00\x00"
  @@loginfooter = "\x03"
  @@successcode = "\x69\x00"
  @@userlength  = 24
  @@passlength  = 24

  def initialize(user, pass)
    @user        = user
    @pass        = pass
    @loginserv   = "61.215.212.5"
    @loginport   = 6900
    @tryconnect  = 3
    @tryerror    = 5
    @sleepsec    = 3
  end

  def check
    try = @tryerror
    begin
      parse_response(get_serv_response)
    rescue ROJoinMonitor::ServerError::LastLoginDataRemaind,
	ROJoinMonitor::ServerError::ConnectionDied
      if try > 0
	try -= 1
	sleep @sleepsec
	retry
      else 
	raise
      end
    end

  end

  def parse_response(ret)
    unless ret.length > 4 && ret[0..1] == @@successcode
      if ret[0..2] == "\x81\x00\x08"
	raise ServerError::LastLoginDataRemaind, 
	  "Last login data is remaind. Try after a few minutes."
      elsif ret[0..3] == "\x6a\x00\x00\x00"
	raise ServerError::InvalidUserID, "Invalid User ID."
      else 
	raise ServerError::Unknown,
	  "Unknown error code '#{ret.unpack('H*').to_s.gsub(/(..)(?=.)/){"#{$1} "}}'"
      end
    end

    self["_timestamp"] = ret[20..45].unpack('A*').to_s
    offset = 47
    while ( offset < ret.length )
      temp = ret[offset .. offset+32].unpack("b42 a11 b72 S")
      # b42 = unknown, a11 = server name +?, b72 = unknown,
      # S = num of join

      serv = temp[1].sub(/\x00.*$/, '')
      num  = temp[3]

      self[serv] = num
      
      offset += 32
    end
   
  end

  def get_serv_response
    try = @tryconnect
    begin
      socket = TCPSocket.new(@loginserv, @loginport)
    rescue
      if try > 0
	try -= 1
	sleep @sleepsec
	retry
      else 
	raise
      end
    end

    socket.sync = true
    socket.write(loginstr)

    begin
      ret = socket.read(2)
      if ret == nil
        socket.close
        raise ServerError::ConnectionDied,
          "Connection Died."
      end

      ret = ret.to_s

      if ret == @@successcode
	tmpbuf = socket.read(2)
	ret += tmpbuf.to_s
	len  = tmpbuf.unpack("S").to_s.to_i
	ret += socket.read(len-4)
      else
	while ret += socket.read(1).to_s do; end;
      end
    rescue Errno::ECONNRESET # ????? fixme ?????
      # Why login server reset connection?
      # Is some string as end of connect required?
    end

    socket.close

    ret.to_s
  end
  

  def [](serv)
    check if self.empty?
    super
  end

  def loginstr
    @@loginheader +
      @user.to_a.pack("a#{@@userlength}") +
      @pass.to_a.pack("a#{@@passlength}") +
      @@loginfooter
  end
  
  attr_reader   :Version
  attr_accessor :user, :pass, :loginserv, :loginport, :reconnect,
    :tryconnect, :tryerror, :sleepsec, :loginheader, :loginfooter,
    :userlength, :passlength
end


###

if __FILE__ == $0
  #
  # Go sample!
  #
  File.open(".joinmonitor.conf") { |f|
    f.each { |s|
      eval(s)
    }
  }

  mon = ROJoinMonitor.new($USER, $PASS)

  print "[#{Time.now.strftime("%Y-%m-%d %H:%M:%S")}] "
  begin
    print [mon["chaos"], mon["loki"], mon["iris"], mon["fenrir"] ].join(","),
      " ", mon["_timestamp"].tr(" ", "_"), "\n"
  rescue ROJoinMonitor::ServerError::LastLoginDataRemaind,
  	ROJoinMonitor::ServerError::ConnectionDied,
	ROJoinMonitor::ServerError::Unknown, Errno::ECONNREFUSED,
	Errno::ETIMEDOUT, Errno::EHOSTUNREACH
    puts "0,0,0,0 0000-00-00_00:00:00.000 Error: #{$!}"
    exit 1
  end
end
