イース6オンライン極限攻略

Ys VI Online Extremely Strategy Information

古代文献の読み書き

コメント(0)

iOS 版古代文献の解読と記述について説明します。

パッケージの内容

iMazing などを利用して入手した .ipa ファイルの拡張子を zip に変更して展開する。もしくは Apple Silicon 搭載 macOS へインストールの場合は次の位置に。

  • アプリ本体 …… /Application/イース6.app/Wrapper/ios.app
  • 更新パッケージ …… ~/Library/Containers/(GUID)/Data/Library/Caches
└── ios.app
    ├── Data
    │   ├── Managed    C# 関連ファイル
    │   ├── Raw        オープニング動画
    │   ├── Resources  Unity 用
    │   └── resource   リソース本体
    ├── Frameworks     各種フレームワーク
    └── そのほか       いろいろ

大部分の情報は resource ディレクトリにある。静的解析に更新パッケージは必須ではなく、アプリ本体にファイル一式が含まれている。

リソース本体

gameconfig ディレクトリ以外は Unity の .bundle ファイル形式で配置されており、内容は AssetStudioMod で閲覧できる。これらのディレクトリ構造は《わくふぁん》とほぼ同一である。

resource
├── TestServer
├── actor
├── anim
├── animation
├── audio                   音声/SE/BGM
├── avatar
├── effect
├── gameconfig
│   ├── lua                 LuaJIT バイトコード
│   ├── multiple
│   ├── table               テーブルデータ
│   └── task
├── live                    Live2D モデル
├── miscellaneous
├── multiple                多言語対応
├── scene
├── shader
├── sprite                  2D画像
├── timeline
├── ui
├── video
└── voxel

gameconfig/lua ディレクトリ以下には Lua スクリプトが LuaJIT のバイトコード形式で格納されており LuaJIT Decompiler v2 で復元できる。

gameconfig/table ディレクトリのデータの解読方法は、可読性を復元した Lua スクリプトに書かれている。ImHex でパターン (後述) を書けば解読できる。テーブル数が多いので、類型を見極めて解読処理の自動生成を検討できると応用が利くだろう。なお、データは配列型を含む表形式になっている。

LuaJIT バイトコード

Lua は組込に特化したプロトタイプベースのオブジェクト指向のスクリプト言語である。重要な処理は C# 側やサーバー側で隠蔽されている……はず。Lua 特有の機能はあまり使われていないため、おおまかな制御の流れを追うのは容易である。

外部ライブラリと思われるものを除いて、特徴的なファイル名が採用されている。

lua
├── csvtable                      テーブルのモデル定義
│   ├── gacsvrequires.lua         全読込用
│   ├── gacsvtablemanager.lua     管理タイプ Csv
│   ├── gacsvtableposmanager.lua  管理タイプ Pos
│   └── gacsv*.lua                各テーブルのモデル定義
├── network                       ゲームサーバーとの通信関連
│   ├── galuanetwork.lua          通信処理
│   ├── gaprotodefine.lua         定数風   Luaテーブル定義
│   ├── gaprotoenum.lua           列挙型風 Luaテーブル定義
│   ├── gaprotohandler.lua        フラット化
│   └── gaprotoregister.lua       ID と メッセージ定義の対応付け
├── protobuf                      Protocol Buffers 関連
│   └── protos                    .proto ファイル
└── gagameclient.lua              Lua 側の処理の起点

lua/protobuf/protos ディレクトリには Protocol Buffers (以下 ProtoBuf) 用の定義ファイルが格納されている。これを用いて通信の解読と記述が可能である。

イースターエッグ

隠し要素があたかもしれない。

function uilogin.onbutton_notice(arg_18_0)
    uinotice:SetShow(true)

    if CS_GetPlatformId() == EPlatformType.EPlatformType_QuickSDKYS or CS_GetPlatformId() == EPlatformType.EPlatformType_RestarYS then
        CS_ShowGameNotice()
    end

    var_0_0._noticeButtonClickCount = var_0_0._noticeButtonClickCount + 1

    if var_0_0._noticeButtonClickCount > 20 then
        GAServerList.bShowTest = true
    end

    if CS_GetPlatformId() == EPlatformType.EPlatformType_Zlong then
        GAGameManager.doSdkCommand(ESdkCommand.LogEvent, "zlong|clickannouncements")
    end
end

ログイン画面でお知らせボタンが押されたときの処理である。

  1. プラットフォームが QuickSDKYS か RestarYS だったら お知らせ表示
  2. お知らせボタンクリック数を +1 する
  3. お知らせボタンクリック数が 20 より多かったら
    サーバーリストのフラグを立てる
  4. プラットフォームが Zlong だったらログに記録

察しが付くように、ほかの地域のパブリッシャー向けのコードも含まれている。

ちなみに、フラグによってサーバーリストに追加表示される対象は、通常のインターネットからは到達不能のようで、なにか支障のある事ではないと思われる。

通信の内容

プレイ中の主な通信先である AWS との通信内容を説明する。

ここで「電文」はアプリケーション層の単位データを、「メッセージ」は ProtoBuf 用語の message を指す。

パケットキャプチャの仕方や通信経路の工夫の仕方については割愛する。

電文の構造

  • 2バイト …… 電文の長さ
  • 2バイト …… メッセージ ID
  • (電文の長さ – 4) バイト …… ProtoBuf のデータ。0バイトの場合あり

サーバーとは TCP 接続1本で、送受信共にこの電文の繰り返しである。状況により、一つの TCP パケットに複数の電文が含まれていたり、一つの電文が複数の TCP パケットに跨がっていたりする。

通信内容は ProtoBuf でシリアライズされた平文である。ProtoBuf の守備範囲外となるメッセージ ID とメッセージの対応は lua/network/gaprotoregister.lua で定義されている。Wireshark は ProtoBuf にも対応しているので、これらを踏まえて Lua で Dissector を書けば、通信内容をリアルタイムに見ることも可能である。また、TCP リレーを仕掛ければ、バケツリレーをしながら柔軟な処理も可能となる。

仕組みとして通信内容の改竄やパケットインジェクションへの備えは見られないが、状況にそぐわない通信の際には AWS から接続をぶち切られるだろう。

定義ファイル

ProtoBuf の定義ファイルは次の要領で分割されている。簡体字の注釈なども見られ、各所で《わくふぁん》等を含む経緯が伺える。

  • *_protocol.proto …… 通信で用いるメッセージの構造体
  • *_define.proto …… 各メッセージの中で用いる構造体

メッセージ名

メッセージ名は VersionReqC2A のように「CamelCase+種別+送受信」の形式になっている。

種別は3種類。

  • Req (Request) …… 応答を要する通信
  • Ack (Acknowledge) …… リクエストへの応答
  • Ntf (Notify) …… 応答不要の通知

送受信は9種類。おそらく次の略記。

  • C2A …… Client to Account-server
  • A2C …… Account-server to Client
  • C2G …… Client to Game-server
  • G2C …… Game-server to Client
  • C2M …… Client to Manage-server
  • M2C …… Manage-server to Client
  • C2O …… Client to crOss-server
  • O2C …… crOss-server to Client
  • S2C …… Server to Client
 

メッセージ内容

メッセージ内容は、多くの項目が optional (省略可能) と repeated (0個を含む配列) であり、JSON ライクなツリー状になっている。定義元と行き来できる IDE で閲覧すれば把握は容易だろう。

メッセージ中の文字列型には、次のいずれかで格納されている。

  • Base64 エンコードされた UTF-8 文字列
  • Base64 エンコードされた GB2312 (中国語) 文字列
  • UTF-8 文字列

テーブルの内容

各テーブルは主に次のファイルで構成される。データはビッグエンディアンで格納されている。

  • table/*.data …… データ本体
  • table/*.text …… データ中のテキスト
  • table/*.pos …… 索引情報
  • lua/csvtable/gacsv*.lua …… モデル定義

共通的する依存処理等。

  • table/table.data …… 管理タイプ Csv 詰合せデータ
  • table/tablepos.data …… 管理タイプ Pos 詰合せデータ
  • lua/csvtable/gacsvtablemanager.lua …… 管理タイプ Csv 初期化処理
  • lua/csvtable/gacsvtableposmanager.lua …… 管理タイプ Pos 初期化処理
  • lua/common/gastreamdata.lua …… バッファ読込クラス
  • lua/common/gastringfile.lua …… テキスト読込クラス

フィールド名プリフィックス。

  • i64 …… int64
  • n64 …… uint64
  • i …… int32
  • f …… float
  • b …… bool
  • str …… string
  • listI …… int32[]
  • listF …… float[]
  • listStr …… string[]

配列構造。

  • 2バイト …… uint16 配列の要素数
  • 以下、各要素

文字列構造。

  • 2バイト …… uint16 文字列のバイト数
  • 以下、バイト列

ImHex 用パターンのサンプル

ImHex はドキュメントが寂しいのが難点。crystal.data 用のサンプルを載せる。

#pragma endian big
import type.base64;

fn base64_value(auto input) {
  return type::impl::decode_base64(input.value);
};

fn value(auto input) {
  return input.value;
};

struct String {
  u16 length [[hidden]];
  char value[length];
} [[sealed, format("value")]];

struct StringBase64 {
  u16 length [[hidden]];
  char value[length];
} [[sealed, format("base64_value")]];

struct ListInt32 {
  u16 length [[hidden]];
  u32 value[length];
};

struct ListFloat {
  u16 length [[hidden]];
  float value[length];
};

struct ListStringBase64 {
  u16 length [[hidden]];
  StringBase64 value[length];
};

struct AddrIndex {
  u32 addr;
  u16 index;
};

struct CsvManager {
  u8 signature[16];
  u32 data_length;
  StringBase64 filename;
  u16 item_count;
  u32 addr1[item_count];
  AddrIndex addr2[item_count];
};

struct Crystal {
  u32 length;
  s32 iID;
  s32 iType;
  String strName;
  s32 iQuality;
  StringBase64 strQualityIcon;
  s32 iSlotGroup;
  StringBase64 strIcon;
  StringBase64 strImage;
  StringBase64 strUiLive2D;
  ListFloat listFUiLive2DOffset;
  float fUIScale;
  StringBase64 strUiLive2DBG;
  float fUIScalePrizedraw;
  ListFloat listFUiLive2DOffsetPrizedraw;
  StringBase64 strUiLive2DPrizedraw;
  StringBase64 strUiLive2DBGPrizedraw;
  bool bSkillUpJudge;
  s32 iShapeShiftID;
  s32 iSkillComposeID;
  s32 iComposeShapeShiftID;
  s32 iServerLevel;
  s32 iOrder;
  bool bCanRecast;
  s32 iRecastCost;
  s32 iRecastCostNum;
  s32 iLockCost;
  s32 iLockMax;
  s32 iRateUpNeedTimes;
  s32 iFightPower;
  ListStringBase64 listStrAttr;
  ListInt32 listIAttrValue;
  s32 iSingleTowerAttr;
  s32 iNewCrystalType;
  s32 iNewCrystalRank;
  s32 iIllusionCrystalSkill;
};

CsvManager manager @ 0;
Crystal items[manager.item_count] @ (manager.addr1[0] + 19);

最後の謎のオフセット 19 の正体は

  • +16 = ファイル冒頭のシグネチャ (16バイト分)
  • +4 = データの長さ (=ファイルサイズ – 20) の記録領域 (4バイト分)
  • – 1 = オリジン補正。Lua と違って先頭を 0 番目と数えるため

s32 や u32 より int32 や uint32 と書きたい場合はエイリアスを定義できる。

using int32 = s32;
using uint32 = u32;
using int64 = s64;
using uint64 = u64;

Live2D の内容

AssetStudioMod の [File] – [Load folder] で resource/live を開いて [Export] – [Live2d Cubism models] – [All models] で抽出される。

Live2D の Cubism 5 Viewer for Unity で [ Load Json ] ボタンから *.model3.json を開けば表示されるだろう。

個人的には daibola_idle のまばたきを [ AutoEffect ] – [ EyeBlink ] で止めて [ Parameters ] – [ ParamEyeROpen ] を 0.00 にすると良いのではないかと。巴芳が「えっちなのはいけないと思います!」と言ったかは兎も角として、中国本国での表現規制に沿うようナーフされて daibola の姿となった可能性はあたかも……。

コメントを書き込む


Protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

まだコメントがありません。

×