読者です 読者をやめる 読者になる 読者になる

Mokosoft開発者ブログ

こんなブログ読んでも時間の無駄ですよ

JBなしでiPhone版ドラクエ8のセーブデータを解析・改造してみた

スマホ版ドラクエ8を発売日に購入してプレイしていますが、素晴らしい出来ですね。

Unityを使って作られているもようで、PS2版にも全然劣らないと思います。

 

 

さて、そんなドラクエ8のセーブデータを早速改造してみました。

そのうち対策されると思いますし、決して真似はしないでください。

 

 

セーブデータを取り出す

まずはiFunboxをつかってごにょごにょしますと、それっぽいデータを発見できます。

これはJailBreakなしでもたどり着けます。

f:id:mad_ochi:20131219105353p:plain

詳しくは調べてないのでわかりませんが、savedata.binファイルはタイトル画面で設定できる音量設定の情報などが入っていました。

残りの0〜5が、冒険の書に該当すると思われるのですが、ひとまず1つめの冒険の書がsavedata0.binに該当することだけはわかりました。

冒険の書は3つまでなので、3〜5はなんでしょうね。おそらくは、今回セーブをしていない場合に中断箇所から再開できる機能があるので、それのための保持データではないかと思われます。

 

f:id:mad_ochi:20131219105843p:plain

バイナリエディタで開いてみました。

macには使いやすいバイナリエディタがなかったので、これはWindowsのtsxbinというソフトです。

なんだか暗号化されてる感じでもないし、Assembly-CSharpという文字をみるとなんとなく・・・。

もしかすると、これはC#のBinaryFomatterでシリアライズ化したデータなのではないでしょうか。

 

解析してみる

というわけで実際にプログラムを組んでBinaryFomatterが読めるかを試してみました。

長くなるので結果だけから先にいうと、ダメでした。

ただ、構造はある程度解析することができました。

 

BinaryFomatterのデータ構造はこちらに詳しく書かれていました。

それを利用したAnalyzerプログラムを書いている人がいたので、そちらを参考にして、このファイルを読み込んでみたところ、歯抜けながらもデータ構造を復元することができました。

 

変数名などもBinaryFormatterから拾えたのですが、一部おかしいものがあったので、おそらくこんな感じというデータ構造が下記。


[Serializable]
public class CDqSaveData{
    public Int32 m_saveMapNo;
    public Single m_envTime;
    public UInt16 m_fileAttr;
    public UInt64 m_playTimeSec;
    public Boolean m_empty;
    public MAP_FLAG_INFO m_mapFlag;
    public CScenario m_scenario;
    public CUserData m_userData;
    public CSaveBattleData m_battleData;
    public CMonsterTeamData m_monsterTeam;
    public CRenkinData m_renkinData;
    public CASINO_SAVE_DATA m_casinoData;
    public CFUSION_ITEM_SELL m_fusionShopData;
    public CUiOption m_uiOption;
    public CSuspendData m_suspendData;
    public CUsedPresentCode m_usedPresentCode;
    public UInt32 m_bitFlag;
    public Int16
 m_shortFlag;
    public UInt32 m_rulaFlag;
    public UInt32
 m_rulaDisableFlag;
}


[Serializable]
public class MAP_FLAG_INFO{
    public UInt32 PartsOnOffFlag;
    public UInt32
 drop_iflag;
    public UInt32 navi_map_flag;
    public UInt32
 wmap_bit_data;
    public UInt32 padding_2;
    public UInt32
 area_cord;
}


[Serializable]
public class CScenario{
    public Int32 Progress;
    public UInt16 Chapter;
    public UInt16
 MapCounter;
    public UInt32 BitFlag;
    public UInt32
 KeyFlag;
    public UInt32 TreasureFlag;
    public UInt16
 ShortCount;
}


[Serializable]
public class CUserData{
    public string m_playerName;
    public Int32 m_money;
    public UInt16 m_partyBit;
    public Int16 m_partyOrder;
    public Int32
 m_partyNpc;
    public DQ_PERSON_STATUS_PARAM m_paramData;
    public CPartyItemData m_partyItemData;
    public SByte
 m_battleTactics;
    public Int32 m_bankMoney;
    public Single m_shipPosition;
    public  string m_shipBlock;
    public Int32
 m_remainSkillPoint;
    public CPersistItem m_persistItem;
    public UInt32 m_getItemBit;
    public Byte
 m_sellItemCount;
    public LEARN_SKILL_INFO _GetCharaParamSkill_sts_skill;
    public LEARN_SKILL_INFO
 _GetCharaParamSkill_skl;
}

[Serializable]
public class LEARN_SKILL_INFO{
}

[Serializable]
public class CSaveBattleData{
    public Int32 m_battle_encount_num;
    public Int32 m_battle_kill_total_num;
    public Int32 m_battle_oiharatta_num;
    public Int32 m_battle_victory_num;
    public Int32 m_battle_husensyou_num;
    public Int32 m_battle_escape_num;
    public Int32 m_battle_all_dead_num;
    public Int32 m_battle_kaishin_num;
    public Int32 m_battle_first_attack_num;
    public UInt32 m_battle_get_money;
    public Single field_walk_dist;
    public Single field_mithia_walk;
    public Int32 padding_i;
    public DMG_PERFORMANCE dmg_performance;
    public MONSTER_DATA m_monster_data;
    public Int32
 dead_count;
    public BTL_SCORE_TENSION btl_tension;
    public Int32 m_battle_escape_none_dead_num;
    public Int32 odokasu_nige_num;
    public Int32 odokasu_sukumi_num;
    public Int32 odokasu_btl_num;
    public Int32 odokasu_noreact_num;
    public Int32 dmy_i;
    public Int32
 flag_chk_val_i;
    public Int16 flag_chk_val_s;
    public UInt32 metal_exp;
    public Int32
 dmy;
    public Int32 touch_val_i;
}


[Serializable]
public class CMonsterTeamData{
    public string
 TeamName;
    public MONSTER_TEAM_MEMBER_INFO StockTeamMate;
    public Int16
 NowTeamID;
    public Int16 padding_s;
    public Int32
 padding_i;
    public Int32 team_action_used;
    public Int32
 team_action_enable;
}


[Serializable]
public class CRenkinData{
    public SByte bit_data;
    public RENKIN_MAKINF maked_info;
    public Single maked_dist;
    public Int32
 renkin_num;
    public RENKIN_MIX_ST maked_status;
    public RENKIN_RECIPE_INFO recipe_info;
}

[Serializable]
public class RENKIN_MIX_ST{

}

[Serializable]
public class CASINO_SAVE_DATA{
    public Int32 coin;
}


[Serializable]
public class CFUSION_ITEM_SELL{
    public UInt16
 exchange_num;
    public Byte progress;
}


[Serializable]
public class CUiOption{
    public Int32 m_controlPanel;
}


[Serializable]
public class CSuspendData{
    public  String m_MapName;
    public FIELD_AREA m_areaType;
    public SnowLevel m_mapSnowLevel;
    public Boolean m_killerPanther;
    public Single m_characterData;
    public Int32 m_bgmNo;
    public Boolean m_bgmPlay;
    public Boolean m_suspendFlag;
}


[Serializable]
public class CUsedPresentCode{
    public Char
 m_code;
}


[Serializable]
public class CPartyItemData{
    public USER_PERSON_ITEM_DATA user_person_item_data;
    public USER_ITEM_DATA hukuro;
    public System.Int16[,] equip_list;
}


[Serializable]
public class CPersistItem{
    public Int32 m_genkiEX;
    public Int32 m_genkiGold;
    public Int32 m_toherosu;
    public Int32 m_shinobi;
    public Int32 m_seisui;
    public Int32 m_superSeisui;
}


[Serializable]
public class DMG_PERFORMANCE{
    public Int32 id;
    public Int32 max;
    public Int32
 padding;
}

 
[Serializable]
public class MONSTER_DATA{
    public SAVE_MONSTER_DATA mArray;
    public SAVE_MONSTER_DATA
 _items;
    public Int32 _size;
    public Int32 _version;
}


[Serializable]
public class BTL_SCORE_TENSION{
    public Single child;
    public Int32 mother;
    public Int32 btlend_tame;
    public Int32 dmy;
}


[Serializable]
public class RENKIN_MAKINF{
    public Int32 item_no;
    public Int32
 recipe_item_no;
    public Int32 recipe_item_who;
}


[Serializable]
public class DQ_PERSON_STATUS_PARAM{
    public COMMON_GAGE_INT HP;
    public COMMON_GAGE_INT MP;
    public Int16 Level;
    public UInt32 exp;
    public UInt32 status_info;
    public UInt64 magic_bit;
    public Int16
 st_param;
    public Int16 skill_param;
    public Int16 bonus_sp_count;
    public Int16 last_get_sp;
}


[Serializable]
public class MONSTER_TEAM_MEMBER_INFO{
    public Int32 id;
    public Int32 mp;
    public MOSMEMBER_STATUS status;
}


[Serializable]
public class RENKIN_RECIPE_INFO{
    public Byte data;
}


[Serializable]
public class USER_ITEM_DATA{
    public Int16 item_no;
    public Int16 num;
}


[Serializable]
public class SAVE_MONSTER_DATA{
    public Int32 kill_num;
    public Int32 oiharatta_num;
    public Int16
 get_item_num;
    public SByte kill_lv;
    public SByte padding_c;
    public SByte
 padding_c2;
}


[Serializable]
public class USER_PERSON_ITEM_DATA{
    public Int16 item_no;
}

配列になっているプリミティブ型変数は、もしかするとポインタなのかもしれません。

変数名を見ると、なんとなーく、どんな意味のデータなのかわかりそうな感じですよね。

 

 

本当は、BinaryFomatterで開いて、データを変更し、再度書き出しができればよかったのですが、いくつか定義のされてないクラス(or構造体)の情報が取り出せなかったため、それは失敗しました。

ですが、データの型により格納サイズがわかったので、それを利用して、バイナリデータを直接いじることは成功しました。

 

例えばCUserDataクラスの中に

UInt16 m_partyBit;

というプロパティがありますが、ゲーム開始時点では3の数字が設定されていました。

もうわかりますよね。おそらく右から4つのビットの有無でパーティーメンバーがいるかどうかを判断しているんでしょうね。

というわけで、ヤンガスをいなくして、ゼシカをパーティーに加える意味で5(0101)にして保存し、再度iPhone内へ上書き保存。

 

チェックサムとかあると起動しないだろうなと思っていたのですが、あっさり起動しましたw

f:id:mad_ochi:20131219112249p:plain

 

各キャラクタのパラメーターはDQ_PERSON_STATUS_PARAMクラスに格納されている模様で、そこの数値を書き換えることで、パラメーターが変わります。

 

CPartyItemDataの中にhukuroという変数があり、中はitemo_noとnumという構造体クラスになっています。

これに対応して、データを書き換えてみると、

f:id:mad_ochi:20131219112557p:plain

たくさんアイテムがてにはいりました。

どのコードが何に対応するかまでは、調べてないですが、しょっぱなからラーミアで飛んでいけるようになりました。

 

最後に

あくまで興味本位にやっただけで、このデータではまともにプレイする気はありません。

ゲームバランスも崩れますし、皆さんも絶対に真似はしないでください。

そのうち、セーブデータは暗号化されることでしょう。