深入淺出SagaECO RPC篇(一) “序列爭戰”

約4年前,機緣巧合地,SagaECO的源代碼輾轉落到我這個不懂編程的閑人手上…
現在能跟大家一起解秘SagaECO,實是百感交集。詳解ECO私服運作的深入淺出SagaECO系列,在此出發~

以下討論基於SagaECO svn 900版本
源代碼可以從這裡下載

注意: 被分發SagaECO源代碼並不代表你被授權使用、修改、發布衍生的程式
換言之SagaECO團隊仍然保留一切權利(雖然SagaECO團隊早就消失了…

這篇的主角是Packet.cs,SagaECO中所有封包皆繼承自Packet類。

Defines the base class of a network packet. Packets are send back and forth between the client and server. Different types of packets are used for different purposes.

The general packet structure is: PACKET_SIZE (2 bytes), PACKET_ID (2 bytes), PACKET_DATA (x bytes).

The id bytes are considered to be part of the data bytes.

The size bytes are unencrypted, but the id bytes and all data following are encrypted.

SagaECO

Packet類提供各方法讀寫它的二進制數據,二進制數據的spec如下:

変数の型
(型は大文字で記述)
パケット内容として記述すると青で強調されます。(間違った型名の場合は赤で強調されます。)

基本型

– BYTE: 1バイト(8ビット)
– WORD: 2バイト(big-endian; 16ビット)
– DWORD: 4バイト(big-endian; 32ビット)
– QWORD: 8バイト(big-endian; 64ビット)

– TIME: (独自型) 時刻
– 4バイト(big-endian; 32ビット)
– 1970年1月1日 00:00:00からの秒数(関数 time(NULL)の戻り値)

– TSTR: (独自型) 文字列(長さと文字列)で以下を含む
– BYTE length; // 文字列の長さをあらわす (\0を含む)、拡張あり(0xfd)
– BYTE message[length]; // 文字列本体 (\0終端)

– XSTR: (独自型) 文字列(長さと文字列)で以下を含む (ABYTEと同じ意味)
– BYTE length; // 文字列の長さをあらわす (\0を含まない)
– BYTE message[length]; // 文字列本体 (\0を含まない)

配列型

– ABYTE: (独自型) BYTEの配列
– BYTE length; // 配列の要素数をあらわす
– BYTE element[length]; // 配列

– AWORD: (独自型) WORDの配列
– BYTE length; // 配列の要素数をあらわす
– WORD element[length]; // 配列

– ADWORD: (独自型) DWORDの配列
– BYTE length; // 配列の要素数をあらわす
– DWORD element[length]; // 配列

– AXSTR: (独自型) XSTRの配列
– BYTE length; // 配列の要素数をあらわす
– XSTR element[length]; // 配列

長さ/要素数の拡張
– 文字列型、配列型は、長さまたは要素数 length が 253以上の場合は次のように拡張する
– (なお、 配列型について、要素の length が 253以上の場合は次のように拡張する)
BYTE length_dummy = 253 (0xFD);
DWORD length;
YTE/WORD/DWORD/QWORD element[length];

Ecore 原文含有亂碼,經手動矯正QAQ
追記: 後來發現有2013的版本

消失的ECore與原SagaECO團隊估計是身經百戰的逆向工程(reverse-engineering)專家,現在無論怎樣努力也好也只能追逐他們的影子…
慨嘆自己以前沒有好好上dbeco wiki和ecore,如果有好好仔細地看可以省略不知道多少個小時的工夫…

整合版:
BYTE: 1Byte (0-255)
p.s: 遊戲內可能還夾雜了SBYTE(-128-127)

WORD: 2Byte(BE) (-65,536-65,535)
p.s: 之所以叫word是因為從前的機器是16bit的

DWORD: 4Byte(BE) (-2,147,483,648- 2,147,483,647)
p.s: 由於佔用Word的雙倍空間,於是被稱為Double Word

QWORD: 8Byte(BE) (總之很大(X)
p.s: Quad = 4, 等於4個WORD的意思
Ecore沒有記載的原因可能是因為他們年代太久遠了
追記: 比較新版的ecore有記載
後期的怪物血量和玩家所持金錢是QWORD

TIME: (自定義類型) 時間戳
– 4Byte(BE), 本質上是DWORD一個
– 從1970年1月1日 00:00:00起計的秒數(関数 time(NULL)の戻り値)
也就是unix timestamp
p.s: 到2038年有千年蟲問題,要玩ECO就趁現在!

TSTR: (自定義類型) 含有字串長度和字串
– BYTE length; // 字串長度
– BYTE message[length]; // 字串本體, UTF8編碼 末端帶null \0
p.s: 全名為Null-terminated String

ABYTE: (自定義類型) BYTE數組
– BYTE length; // 表示數組長度
– BYTE element[length]; // 數組

AWORD: (自定義類型) WORD數組
– BYTE length; // 表示數組長度
– WORD element[length]; // 數組

ADWORD: (自定義類型) DWORD數組
– BYTE length; // 表示數組長度
– DWORD element[length]; // 數組

AQWORD: (自定義類型) QWORD數組
– BYTE length; // 表示數組長度
– QWORD element[length]; // 數組

ASTR: (自定義類型) TSTR數組
– BYTE length; // 表示數組長度
– TSTR element[length]; // 數組


*沒有令人頭痛的浮點數類型
遊戲中的傷害,經驗值,座標,方向等全部都是整數(拍手

此外, 對於數組、元素長度超過253時…次のように拡張する
這句我也不知道是怎麼意思 以後再研究好了
追記: 我有印象… 在另一篇會再談及

這裡先介紹讀取的方法

可見無論是讀任何類型的第一個動作就是調整偏移量
而Packet偏偏把offset作為公用成員暴露出去… orz
因此當務之急是封裝成為屬性 存取子改成私有
順道統一更改offset的邏輯

眼睛銳利的話已經注意到如果-4 < index < 0這段神奇的區間的話,AdjustOffset是不會報錯的,這是因為我們只把終點的偏移量傳入,如果改成起點和終點各調用一次就沒有問題。不過到後面也會報錯所以問題不大~

不過倒是要說這個SagaECO很多地方用上了unsigned來省掉處理負數的問題,除了帶來casting上的不便之外,沒有與使用signed的客戶端配合也是一個潛在的問題。(負數面板攻擊力這點在其他遊戲也有見到過XD)

接下來對付這個被我備註成WTF的代碼。這裡是抄下了4個byte,然後反轉,至於為何需要反轉下面會詳述。一邊抄一邊反轉的執行效率雖然是比較快,但是最起碼也應該改成for-loop吧XD

顯而易見,透過改成 for-loop,抽象落在需要的數組長度上面。數組搬移的邏輯可以集中在 GetBytes方法裡。

是時候談一下反轉數組的原因。BitCoverter默認根據系統架構的endianness運作,歷史原因大家的PC上跑的x86在用LittleEndian (BitConverter.IsLittleEndian為True) ,而ECO封包裡的數字總是使用BigEndian。

這裡容易注意到GetBytes不符合應用規範。為甚麼? 這個API並不是以只讀取一個數字為前提的。讀取的bytes不一定只含有一個數字,盲目地在這個位置反轉數組只會弄巧成拙。以下是修正版,用上了LINQ,當時很可能還沒有LINQ哩。

以現今的角度去看老舊的代碼是很有趣的。硬件性能提升的結果就是可以用函數式風格的聲明式編程,”閱讀同時反轉數組”的最佳化可以先忘記掉。

(碎碎念: 最新版本的C#出現了叫作Span的東西可以用來做更底層的操作,不用使用unsafe代碼操作指針,作為JIT語言它的速度相當變態, .NET core能跑很快也能歸功於受益Span的CLR)

順理成章地解決寫入的部分
這裡卻好好有用上Array.Reverse,Array.copyTo等方法
特意優化閱讀的邏輯這點十分令人不解

下一篇會談談Packet類以外的部分。當然Packet.cs的故事仍未結束 >_<

2

6 Responses to “深入淺出SagaECO RPC篇(一) “序列爭戰”

  • Hello Kanade,

    我最近在Google搜索到這個blog, 並跟著這些日誌下了sagaeco的source code和visual studio 2019試著跑跑看。 今天剛到AccountCreator階段遇到了幾個問題, 希望能分享 and get some help.

    1. Form1.cs那邊con.Open()因為machine沒有native database的關係需要到https://dev.mysql.com/downloads/windows/visualstudio/
    下載VS用MySQL. 這是正常嗎? VS沒有把mysql bundle到VS installer有點意外

    2. new MySql.Data.MySqlClient.MySqlConnection 密碼需要另外的encryption
    到sql shell
    ALTER USER ‘root’@’localhost’ IDENTIFIED WITH mysql_native_password BY ‘password’;
    flush privileges;

    3. Connection 需要set CharSet
    “Server={1};Port={2};Uid={3};Pwd={4};Database={0};CharSet=utf8;”

    4. 需要create sagaeco database和login table
    這邊有個問題, 他們有創建一個.sql file去handle這些sql shell的creation嗎?

    希望可以早日解決env問題可以摸摸代碼(‘ω’)!

  • 不好意思 在看到文章後想說來試看看 但發現se-a的網站已經連不進去了 請問博主留有紀錄嗎 還有encore的2013版 感謝

  • 看到這個blog後也想試試看 發現se-a網站已經進不去了 請問up主留有紀錄嗎 還有ecore的2013版可以分享一下嗎 感謝

    • hoshinokanade
      2 years ago

      se-a的備份我這裡有,為免著作權問題不便公開,可以私下給

Trackbacks & Pings

  • 深入淺出SagaECO RPC篇(二) - AsAlma :

    […] 很遺憾地,代碼的版本更舊。我們這次要為它更新到最新版本。而且我們在上一篇也修改了Packet類呢。反覆向新版客戶端發送封包測試後,新增的DWORD被認為對應重播動作的速度。數值愈大,速度愈快。而unknown仍然被稱為unknown,它的存在就是一個謎… […]

    5 years ago

Leave a Reply

Your email address will not be published. Required fields are marked *