Contents |
OpenKore 網路子系統粗略地包含了以下幾個類別:
這是 Connection Manager(連線管理器)。它管理 RO 伺服器的 TCP/IP socket 連線,以及處理像是連線、斷線、傳送資料至伺服器、由伺服器接受資料...等事務。但它不做太多其它事。
以圖表來說明,它長得像這樣:
關於連線到 RO 伺服器,它產生兩個物件:
這兩個是 Message parser 的類別。它們剖析了通過 Connection Manager 的 message,從 message 中萃取有用的資訊,然後存放這些資訊在全域變數裡。其它的 OpenKore 模組(像是 AI)使用全域變數裡的資訊來處理許多的事。
注意不管是 Connection Manager 類別或是 Message parser 類別都不應該包含有任何 AI 相關的程式碼。
這兩個是 message 傳送的類別。它們封存 RO 網路 message 到一個簡單、易於使用的功能函數,如此一來你就不必在 OpenKore 靜止時動手寫位元組控制的程式碼。
在 2003 年以前,事情很簡單:你只有 iRO(國際的)與 kRO(韓國的)可以選,而且就這樣了。但今天有超過 5 種官方的 RO 伺服器(巴西、菲律賓、歐洲、蘇俄等等)與上百個私服。所有的伺服器的通訊協定都有些微的不同。假如針對每一種伺服器的通訊協定來產生不同版本的 Kore 的話就太沒效率了,因為雖然通訊協定有所不同,它們並不會相差太遠。這就是為什麼 OpenKore 必須要能支援所有(至少有許多)不同的 RO 伺服器的通訊協定類型。
因此我們引入了 server type 的觀念。也許你曾經在 servers.txt 看過 "serverType" 這個選項。serverType 所指定的數字指出了伺服器是使用哪一種的通訊協定。舉例來說,這個時候(2006 年 12 月 20 日)我們有:
以上並非完整的伺服器類型列表。在撰寫本頁時,我們已經有 15 種伺服器類型了。
Network::Receive 與 Network::Receive::ServerType0 類別包含了剖析伺服器類型為 0 的通訊協定的程式碼。每個伺服器類型都有屬於 Network::Receive 的一個子類別。每個子類別都特化成專門用來剖析該類型的伺服器通訊協定。類別階系圖長得像這樣:
當 Connection Manager 連上伺服器後,它會去查閱(藉由使用 servers.txt 內的資訊)哪一個伺服器類型是與這個伺服器有關聯的,然後對這個伺服器使用符合的 Message parser 類別。
假如你在 Network::Receive 看到 'new' 這個方法,那麼你會看到一些像下面的東西:
$self{packet_list} = {
'0069' => ['account_server_info', 'x2 a4 a4 a4 x30 C1 a*', [qw(sessionID accountID sessionID2 accountSex serverInfo)]],
'006A' => ['login_error', 'C1', [qw(type)]],
...
$self{packet_list} 是個對映一個 message ID(有時也稱做 packet switch)至一個陣列的雜湊,該陣列擁有以下資訊:
在子類別中,你可以隨你的意願來修改雜湊。你也可以無視處理功能函數而去做其它的事,端視於你想要它對於該伺服器類型有什麼樣的表現。
以下的 arguments 會通過處理功能函數:
在上面的例子中,account_server_info 看起來會像這樣:
sub account_server_info {
my ($self, $args) = @_;
# $args->{sessionID} can be accessed.
# $args->{accountID} can be accessed.
# ...etc...
}
這個 message arguments 雜湊有兩個特別的項目:
舉例來說,我們假設 RO 伺服器現在加入了一個新的封包叫做 "你正在跳舞",這個 message 也告訴你你正在跳舞的確切位置與舞步的種類。假設這個 message 長這樣:
然後你可以加入以下細項至 $self{packet_list} 雜湊:
'1234' => ['dancing', 'v v C', qw(x y type)]
這裡的 'v v C' 告知了這個 message 包含了一個 16-bit 的整數 ('v'),緊跟著另一個 16-bit 的整數 ('v'),再緊跟著一個 1-byte 的字元 ('C')。'qw(x y type)' 這個部分告知了第一個定義域應該要用 'x' 做為它的名稱,第二個定義域應該要用 'y' 做為它的名稱,第三個定義域應該要用 'type' 做為它的名稱。
你也要加入以下的 message 處理功能函數至 Network/Receive.pm:
sub dancing {
my ($self, $args) = @_;
message "I am dancing on position ($args->{x}, $args->{y})! My dance type is $args->{type}\n";
}
這樣就好了!至於其它的事會由 framework 自動處理。
我們假設伺服器類型為 12 的伺服器對於 "你正在跳舞" 的封包有了些微的改變,它的 X 座標與 Y 座標被調換了。也就是說,Y 座標之後緊跟著的是 X 座標(而不是先 X 後 Y)。你可以修改 packet_list 雜湊以更新它的 message 結構。所以在 '新的' Network::Receive::ServerType12 功能函數中,你可以寫成這樣:
sub new {
my ($class) = @_;
my $self = $class->SUPER::new;
# BEGIN: YOUR CODE
$self->{packet_list}{1234}[2] = [qw(y x type)];
# END: YOUR CODE
return $self;
}
假如你的 Perl 不夠好:recall that $self->{packet_list}{1234} 看起來像是這樣:
['dancing', 'v v C', qw(x y type)]
然後你希望修改它的變數名稱列表 qw(x y type),此列表在整個陣列中的索引值為 2。所以你可以寫成
$self->{packet_list}{1234}[2] = [qw(y x type)];
現在,$self->{packet_list}{1234} 看起來會是這樣:
['dancing', 'v v C', qw(y x type)]
在 message sender 部分對多個伺服器類型的處理幾乎與 message parser 部分相同。類別的階系圖看起來像是這樣:
不同的是伺服器類型 0 的處理程式碼是在類別 Network::Send::ServerType0 中。所有其它的 Network::Send::ServerTypeX 子類別是從 Network::Send::ServerType0 繼承而來的。
有趣的是,伺服器通訊協定類型間大部分的相異處都在傳送的部分!我們從不同伺服器類型的伺服器所接收的 message 並不會相差太多,相較之下,我們要傳送到伺服器的 message 的差異性卻大得多了。
就像 message parser 物件一樣,message sender 物件也由 connection manager 所產生。這個物件是存放在全域變數的 $messageSender 裡。要傳送 message 至伺服器端,請寫:
$messageSender->sendFoo();
每一個 message sender 功能函數名稱都有一個前置詞 'send'。請參見 Network::Send::ServerType0 的傳送功能函數的列表。舉例來說,假如你想要傳送 "公頻" 的 message 到 RO 伺服器,那麼請寫:
$messageSender->sendChat("hello there");
本頁物件導向的 message sender 結構(如前所述)是在 2006 年 12 月 20 日被引入的。在較早期的 OpenKore 版本中要傳送 message 給伺服器的話,你得用以下其中一種方法來寫:
# OpenKore 1.6 與 1.9.0-1.9.2 的語法 sendFoo(\$remote_socket, args);
# OpenKore 1.9.0-1.9.2 的語法 sendFoo($net, args); $net->sendFoo(args);
但這些將無法繼續運作了。取代的寫法要寫成:
$messageSender->sendFoo(args);
或者,假如你的 plugin 必須要與 1.6 版相容的話請寫這樣:
if (defined $Globals::messageSender) {
$Globals::messageSender->sendFoo(args);
} else {
sendFoo(\$remote_socket, args);
}
message sender 類別提供了一些 hooks 可允許 plugins 處理這些 messages。
此處,$HANDLER_NAME 是處理功能函數的名字,詳載於 packet_list 雜湊中。舉例來說,"packet_pre/account_server_info"。
這個 hook 會在處理功能函數被呼叫前被呼叫。但這個 hook 只有在對於目前封包有處理功能函數時被呼叫。給予這個 hook 的 argument 是一個包含 message 定義域(the message arguments)的陣列。
這個 hook 會在處理功能函數被呼叫後被呼叫,或是對於目前的 message 沒有處理功能函數時被呼叫。它的 argument 是該 message 的 arguments。
Ragnarok Online 使用 TCP 做為它的傳輸通訊協定。每個 RO 伺服器傳送至客戶端的 message 有以下的格式:

一個 message 由最少一個定義域構成:message ID(message 識別碼)(也叫做 packet switch,但 "message ID" 比較容易理解)。這個 message ID告訴客戶端這是哪一種類型的 message。靠著 message ID,我們才知道要如何去解釋這個 message。
有兩種類型的 messages:
最後,我們有 message arguments。arguments 裡的確切內容依賴 message 本身。舉例來說,"某人發出一個表情符號" 的 message 擁有以下資訊在它的 message arguments 裡:
當我們經由 RO 伺服器的 socket 接收到資料時,我們無法假設每次我們從 socket 讀到的是否為確切的 1 個完整的 message。我們可能接受到部分的 message,或是兩個的 messages,或是一個完整的 message 及一個不完整的下一筆的 message。這就是為什麼我們必須把從 RO 伺服器接收過來的資料先放在一個 緩衝區。當我們確定我們已接收到至少一個完整的 message 時,我們會開始處理該 message 並把它從緩衝區移走。然後我們持續等待,直到我們已知我們又接收到了另一個完整的 message,依此類推。
但我們要怎麼知道一個 message 是否是完整的?為了要知道這件事,我們必須知道每一個 message 的確切長度。這就是 recvpackets.txt 的來由:它詳細敘述了哪一個 message 有哪個長度。舉例來說,recvpackets.txt 有一行是這樣的:
00C0 3
這表示有著識別碼 "00C0" 的 message 是一個固定長度的 message,且長度為 3。但有時你也會看到像這樣的情形:
00D4 0
0 表示長度是變動的,所以在這個例子裡它代表 00D4 這個 message 是一個變動長度的 message。如附錄 A 所提,變動長度的 message 會有一個 message length 的定義域,可告訴我們該 message 的長度是多少。