Thinking Skeever

Skyrim/The Witcher 3 Modについてのあれこれ。FoModの作り方、Mod導入時のトラブル事例などのニッチな話を書いていきます。a.k.a. BowmoreLover@nexusmods

Skyrim Mod導入トラブル事例:セーブデータのロード時に確定CTD(Papyrus文字列数上限65535問題)-2016/5/13追記

原因

もしかしたらPapyrus文字列数上限65535問題に該当しているかもしれません。
この問題に該当する場合、そのセーブデータは破損しており、絶対にロードすることはできません。

Skyrimのセーブデータ(.ESS)には、Papyrusスクリプトの使用する文字列を格納する文字列テーブルがあります。ファイル形式上、文字列テーブルの配列数はunsigned shortで表現されており、65535(0xffff)が上限となります。
Papyrus文字列数の上限を超過しても、ある程度はプレイ続行でき、セーブすることも可能ですが、一度ゲームを再起動した後にそのセーブデータをロードすると確定CTDします(必要な文字列がすべて含まれていない不正なセーブファイルとなるようです)。
上限を超過した状態でプレイを続行するのも問題があるようです(参考記事をざっと見た感じ、一見問題がないように見えるが内部的には文字列テーブルがおかしくなっている可能性ありとのこと)。
参考記事によると、実験の結果、Skyrimのエンジンそのものは65535を超える文字を取り扱えるようであり、セーブデータの形式だけの問題らしいとのこと。

bcspさんによる参考記事:The Nexus Forums : Corrupt Saves - StrCount > 0xFFFF - CTD on load


f:id:thinkingskeever:20151114030550p:plain

問題の切り分け方法

Save game script cleanerを使う方法(初期切り分け)

Save game script cleaner by Hadoramでセーブデータを開いたとき、ツールがアプリケーションエラーとなる場合、本問題に該当する可能性が高いです。

TESV ESS Files Editorを使う方法(正確に判断)

TESV ESS Files Editor by fubrusを使って文字列数を正確に把握できます。

ツールを起動し「Open」ボタンを押してセーブデータを開きます。

左のツリーから「Global data table 3」を展開し、その下位にある「2.Papyrus」をクリックします。

右のツリーに表示されるStrings(数字)が現在の文字列数を示しています。65535からこの数字を引いたものが上限までの猶予となります。


「Range check error」ダイアログが表示された場合、既に文字列数上限を超過しています。

文字列数は状況によって1,000~2,000くらい増減することがあるようです。文字列数が超過していなくても63,000以上であれば「以前気付かぬうちに上限を超していた可能性がある」と考えたほうがいいでしょう。

ロードCTDするのに文字列数に余裕がある場合、残念ながら文字列数が原因ではありません。次のブログ記事を参考に他の原因を探ってください。

拙作のStrCountToolを使う方法(まとめてチェック)

複数のセーブファイルを纏めてチェックできるツールを作成してみました。
Crash Fix v8の拡張形式にも対応しています。
ダウンロード:StrCountTool v0.1
解説ページ:Skyrim自作MOD: StrCountTool 利用ガイド (v0.1版) - Thinking Skeever

実行例:

ファイルが壊れている場合、赤枠のようにエラーメッセージが表示されます。
MS OfficeやOpen Officeなどの表計算ソフトを使って表示することを想定していますので列が揃いませんがご勘弁を。




f:id:thinkingskeever:20151114030550p:plain

対策方法

ベゼスダがファイル形式を改善するのが一番ですが、Skyrimでは期待できないでしょう。Fallout4でも同じ問題があるのか気になるところですね。

  • 使用するMODを減らす(特に会話・スクリプト・アニメーションを大量に追加するもの)
  • Dynamic Distant Objects LOD - DynDOLOD by Shesonのv1.47以前は文字列テーブルを大量に消費しますが、v1.48以上にアップグレードすることで消費を大幅に削減できます(下記に実績を書いておきました)。
  • SL 1.61は文字列テーブルを大量に消費しますが、markdf氏のJContainersパッチを使って消費を大幅に削減できるそうです(要JContainer3.3)。

2016/05/04追記
●meh321氏のMOD Crash Fix v8に文字列数超過時の対策が追加されました。

CrashFixPlugin.iniの説明の和訳:

; 情報:スクリプトが65535より多い異なる文字列を使うと、セーブデータは破損しロードできません。
;    これは文字数が65520より大きい場合のファイル保存形式をわずかに変更することにより修正します。
;    これは、このオプションを有効にして作成したセーブデータの形式が変更されるため、バニラのゲームやsave gameツールで開けなくなることを意味します!
;    逆もまた真なりで、文字数を65520以下にして再びセーブするとバニラの保存形式に戻ります。
; 概要:https://forums.nexusmods.com/index.php?/topic/3924850-corrupt-saves-strcount-0xffff-ctd-on-load/ のための修正
; アドレス:大体30箇所のコードにパッチを当てた。
StringCount32=1

文字列数が65520を超えて作成したセーブデータはバニラの形式とは異なるため、Crash Fix v8のインストールされていない環境やセーブデータを扱う各種ツールでロードできなくなることに注意してください。

●markdf氏によりRestringerの開発が進められています(テスト版あり)
・PEXファイル中の文字列をJsonファイルに吐き出し、Jsonファイル中の文字列を参照するようPEXファイルを書き換えることにより、文字数を減らすアプローチです。
・ENDPOINT(末端MOD)に対してパッチを当てることを想定。例えばCampfireは他のMODにより利用されるので末端ではなく、FrostfallやWearable Lanternは末端MODである。
・実行にはJavaが必要で、Mod Organizer使用時は32ビット版のJavaが必要とのこと。



f:id:thinkingskeever:20151114030550p:plain

詳細解説

以下、参考記事の和訳を交えて解説します。

文字列テーブルに格納される文字列

参考記事によると、文字列テーブルには次のものが格納されるとのことです。

  • 関数が利用するローカル変数の定義。通常、関数が終了した後にテーブルから削除されるが、MCM関連の変数の一部は保持され続けるようです。
  • スクリプトプロパティ。文字列テーブルの大半はプロパティのようです。(筆者注:スクリプトの文字列プロパティの代表例はMCMのページ名です。ゲーム進行途中でMCMページ名の訳を変更するとMCMページが表示されなくなるのはこれに起因します)
  • スクリプト/フラグメント名(.pex含まず)。これらのスクリプト名はプロパティと同様のようです。文字列テーブルに含まれるスクリプト名が極めて少ないことがこれを説明しています。

文字列がすべてセーブデータに保存されるわけではなく、現在のセーブで使われているスクリプトの文字列が保存されるようです。従って、インストールしたMODの数や種類によっても変化しますし、ゲームの進行状況に応じたスクリプト環境の変化によっても増減します。

どういうMODが文字列数に悪影響を及ぼすのか(参考記事の和訳)

  • 確実ではないですが、一般的な経験則によれば、スクリプトが多いほどプロパティの数も多くなります。これは大量に会話を追加するMOD(Inteesting NPCs)が大きな影響を及ぼす理由をうまく説明しています。私は会話系MODの専門化ではないですが、クエストステージを進めるためだけにスクリプトが必要となるようです。私に言わせれば、MOD制作者がロールプレイの会話を追加することで16ビットの制限を圧迫するベゼスダのやり方はとてもとてもマヌケに思えます。
  • 大規模で複雑なMCMを持つMOD。MCMの各項目(スライダー、トグルボタンなど)それぞれに対して変数定義が必要です。文字列テーブルの内容を見ると"OID"を含みMOD作者の慣例に従ったネーミング規則を持つ変数名が大量にありました。MODが無意味なMCMを追加するのを見て私はため息をつきます。シンプルなままでいいよ。
  • 沢山のプロパティが付いたスクリプト。プロパティは文字列テーブルの数に大きな影響を与えるようです。例えばですが、最近私は素晴らしい"Illustrious Cloaks Of Skyrim"をスクリプト版からクラフト版にダウングレードしました。スクリプト版ではスクリプト1つあたり230ほどのプロパティがあります。スクリプト版の方がはるかにエレガントで適切な実装ですが、クラフト版にはまったくスクリプトが含まれていません。
  • アニメーションMOD。特に「その手のサイト(訳注:Lovers Lab)」のユーザーで問題になります。アニメーション毎にいくつかの文字列が追加されることが報告されています。

さらに詳しい情報について

bcspさんによる記事:The Nexus Forums : Corrupt Saves - StrCount > 0xFFFF - CTD on loadでは、解決方法に関する議論や各MODの使用文字列数調査が継続して行われています。
OP(最初の書き込み)にはMODの使用文字列数をまとめたgoogleスプレッドシートへのリンクもありますので参考にしてください(うっかり更新しないようにご注意を)。


f:id:thinkingskeever:20151114030550p:plain

Crash Fix v8について

Crash Fix v8を使って実際に文字列数が65535を超過したセーブデータを作成し、その仕組みについて調査してみました。
Papyrusにおける文字管理方式、破損の仕組みを説明したあと、Crash Fix v8の仕組みについて説明します。

Papyrusにおける文字管理方式(PEXファイルの場合)

次のスクリプトコンパイルしてPEXファイルを作成したとします。

Scriptname TestScript extends ObjectReference
Event OnActivate(ObjectReference akActionRef)
	Debug.MessageBox("Not enough money")
EndEvent

PEXファイルには次のような情報が保存されます。保存形式はバイナリ形式ですし保存される情報はもっと多いですが、分かりやすいように簡略化しています。
文字列テーブル

文字列番号 文字列
0 testscript
1 ObjectReference
2 OnActivate
3 akActionRef
4 debug
5 MessageBox
6 Not enough money

スクリプト中間コード:()内が文字列番号

Scriptname (0) extends (1)
Functioon (2)((1) (3))
	(4).(5)((6))
EndFunction

このように、スクリプトコード中の文字列はすべて文字列番号に置き換えられ、文字列本体は文字列テーブルにまとめられます。同じ文字列がある場合、大文字小文字を無視して文字列テーブルの1エントリにまとめられます。

PEXファイルの文字列テーブルの上限は65535(0xffff)ですが、1つのスクリプトファイルで巨大なコードを書くことはないため、この時点では何も問題ありません。

セーブデータに保存される文字列

セーブデータには次の文字列が保存されます(ただし保存のトリガとなる一定の条件がある模様)。

  • スクリプト
    • ESP/ESMのプロパティで定義された文字列
    • オブジェクト名(pexファイル名から拡張子を取り除いたもの)
    • Papyrus広域変数の型名・変数名・初期値
    • Papyrus広域変数に動的に代入された文字列
  • アクティブスクリプトの場合

セーブデータ破損の仕組み

Skyrimゲームエンジンに読み込まれたスクリプトの文字列はすべて1つの文字列テーブルに保存されます。ゲームエンジンでは65,535を超える文字列テーブルを取り扱えるようですが、ファイルに保存する際に16ビットに切り詰められてしまいます。

例えば文字列数が65,600の場合、16進数に変換すると0x10040となりますが、セーブデータには下位16ビットの0x0040が文字列数として保存されます。
調査した範囲では文字列テーブルは65,600個分すべてがセーブデータに保存されるようなので、次回読み込み時に「文字列数が0x0040なのにそれ以上の文字列が入っている」状態となり、データ形式不正=CTDとなります。

Crash Fix v8 StringCount32機能の仕組み

CrashFixPlugin.iniのStringCount32を1にすることで、セーブデータ中の文字列数および文字列番号を拡張することができます。

; 情報:スクリプトが65535より多い異なる文字列を使うと、セーブデータは破損しロードできません。
;    これは文字数が65520より大きい場合のファイル保存形式をわずかに変更することにより修正します。
;    これは、このオプションを有効にして作成したセーブデータの形式が変更されるため、バニラのゲームやsave gameツールで開けなくなることを意味します!
;    逆もまた真なりで、文字数を65520以下にして再びセーブするとバニラの保存形式に戻ります。
; 概要:https://forums.nexusmods.com/index.php?/topic/3924850-corrupt-saves-strcount-0xffff-ctd-on-load/ のための修正
; アドレス:大体30箇所のコードにパッチを当てた。
StringCount32=1

実際に文字列数が超過したセーブデータを作成し調査したところ、セーブデータ中の文字列数(UInt16)および文字列番号(UInt16)が次のように保存されていました。

  • 数<=0xfff0の場合:UInt16形式(バニラと同じ)
  • 数>0xfff0の場合:0xff + 0xff + UInt32形式(Crash Fix拡張)

データ読み込みの際は、値が0xffffなら続けてUInt32の値を読み込めばいい訳です。とてもスマートですね!

このセーブデータで継続的にプレイした訳ではありませんが、パッチ箇所に漏れがなければ上記の問題点を完璧に解決するものだと思います。
とはいえ、文字列数が超過したセーブデータをセーブクリーナなどで読み込めないのは不便なので、あくまでも保険として利用するのが賢いと思います。



f:id:thinkingskeever:20151114030550p:plain

実例(私の体験談)

最近新しい環境を作成してニューゲームしたのですが、全ホールドの従士になってDawnguardクエストを開始しセラーナを救出した直後に本問題が発生しました。
タイミングがタイミングなので諦めきれず、グーグル先生を使ってやっと参考記事を見つけた次第です。
どの程度のMOD構成でどれくらいPapyrus文字列数を消費するかの目安になるかもしれないので、私の環境とPapyrus文字列数を晒しておきます。

MOD環境

カスタムボイスフォロワー、イマージョン系MOD、景観・遠景改善を中心に構築しました。SLなどのLovers Lab系MODは入れていません。
FPS(GTX760で28前後)でスクリプト遅延もひどく、あまり褒められた環境ではありません。他のブログ管理者様が「これを入れたら重くなる」というものがことごとく入っているはずなので注意してください(これでも大分減らしたんですが)。

Mod Organizerのplugin.txtの内容:

# This file was automatically generated by Mod Organizer.
Skyrim.esm
Update.esm
Unofficial Skyrim Patch.esp
Dawnguard.esm
Unofficial Dawnguard Patch.esp
HearthFires.esm
Unofficial Hearthfire Patch.esp
Dragonborn.esm
Unofficial Dragonborn Patch.esp
ApachiiHair.esm
ApachiiHairFemales.esm
ApachiiHairMales.esm
RaceCompatibility.esm
Campfire.esm
HighResTexturePack01.esp
HighResTexturePack02.esp
HighResTexturePack03.esp
Unofficial High Resolution Patch.esp
Bethesda_Hi-Res_Optimized.esp
SMIM-Merged-All.esp
NoWaterStreamBug.esp
RealShelter.esp
FISS.esp
SkyUI.esp
FNIS.esp
FNISspells.esp
Frostfall.esp
UIExtensions.esp
Atlas Legendary.esp
Atlas Compass Tweaks.esp
Mystical Illumination - Glowing Signs with Point the Way.esp
TheEyesOfBeauty.esp
The Eyes Of Beauty - Elves Edition.esp
EnhancedCharacterEdit.esp
CharacterMakingExtender.esp
USKP Patcher for RaceCompatibility.esp
Alternate Start - Live Another Life.esp
FollowerLivePackage.esp
Convenient Horses.esp
My Home Is Your Home.esp
sandboxcylinderheight.esp
dD - Realistic Ragdoll Force - Realistic.esp
LPBards.esp
Males of Skyrim.esp
IB - All-in-One.esp
SBF All In One + DLC.esp
Gyot_NPCs.esp
BTRH_Waifu.esp
Bijin Warmaidens.esp
BW Ria.esp
Bijin NPCs.esp
Bijin Wives.esp
Serana.esp
Valerica.esp
MyWarmaidens.esp
Eli's Coffee Mod.esp
Auto Unequip Ammo.esp
towConversation.esp
00FacialExpressions.esp
GoToBed.esp
Hide Those Futile Quests.esp
hmkHotKeys.esp
JaxonzEnhGrab.esp
JaxonzZoom.esp
PerksUI.esp
jkjsenhancedhotpools.esp
The Paarthurnax Dilemma.esp
Customizable Camera.esp
DetectLever.esp
DG-NoAttacks.esp
DragonBlood.esp
Dual Hand Combo Hotkeys.esp
No Magic Ninja Dodge - No DLC.esp
Qasmoke Travel Spell.esp
Upgrade Leveled Items Spell.esp
Snotgurg Equipment HUD.esp
FixCombatMusic.esp
FCO - Follower Commentary Overhaul.esp
PreventsAccidentPickUp.esp
Immersive detection of NPC.esp
jbNPCMap.esp
MIDFollowMeCloser.esp
SaveHotKeyMCM.esp
SSAssist.esp
ShowRaceMenuAlternative.esp
SkySet.esp
UnreadBooksGlow.esp
Headtracking.esp
TChomesHearth.esp
EnhancedLightsandFX.esp
ELFX - Exteriors.esp
Skysan_ELFX_SMIM_Fix.esp
ELFX - NoBreezehome.esp
BreezehomeByLupus.esp
FieldLab.esp
MammothManor.esp
Pinegrove Lodge.esp
Bluthanch.esp
Wynter.esp
Vellamo.esp
Various Hunter.esp
Chaconne.esp
Toccata.esp
Vivace.esp
GoddessDiana.esp
OK_Noemie.esp
OK_sela.esp
OK_HumanBeastCaravan.esp
OK_Rosalie.esp
RosaFollower.esp
Recorder Follower Base.esp
SolitudeTheLuckySkeeverHome_Lite.esp
Warmstone.esp
RiftenGarret.esp
Thornrock.esp
Minerva.esp
Holidays.esp
WondersofWeather.esp
BlackHorseCourier.esp
Dig Site.esp
WitchTent.esp
alesCampBedrollsSupplies.esp
SimplyKnock.esp
WetandCold.esp
WetandCold - Ashes.esp
ArtOfTheCatch.esp
PipeSmoking.esp
FNISSexyMove.esp
Kyne's Gate.esp
bbDTP.esp
EasyWheel.esp
TKDodge.esp
FacelightPlus.esp
QuickLight.esp
RealisticWaterTwo.esp
RealisticWaterTwo - Legendary.esp
Watercolor_for_ENB_RWT.esp
Fantasy Soundtrack Project.esp
Fantasy Soundtrack Project - Combat.esp
AOS.esp
AOS2_RealisticWaterTwo Patch.esp
AOS2_WetandCold Patch.esp
Hunterborn.esp
Hunterborn_Campfire_Patch.esp
Animallica.esp
iNeed.esp
Skyrim Flora Overhaul.esp
SFO - Dragonborn.esp
Vivid Weathers.esp
Vivid Weathers - AOS patch.esp
MX2BeWithHealer.esp
Tiwa44_Minidresses_Standalone.esp
Tiwa44_Minidresses_Standalone_DG&DB.esp
UNP Spice Gear.esp
Long lost smelters by Hyralux.esp
Long Lost Smelters -Winterhold.esp
AMB Glass Variants Lore.esp
Differently Ebony.esp
aMidianborn_Skyforge_Weapons.esp
Mini Vamps.esp
RUSTIC SOULGEMS - Unsorted.esp
SL01AmuletsSkyrim.esp
SL01AmuletsSkyrimDGDB_Outfit.esp
Immersive Jewelry.esp
SL99Exchanger.esp
SL99Exchanger_Patch_Immersive Jewelry.esp
Skyrim Particle Patch for ENB - Flame Atronach Fix.esp
NoAnimalsReportCrimes-DG+DB.esp
Enhanced Landscapes.esp
Enhanced Landscapes - Solstheim.esp
Enhanced Landscapes - Marsh Pines.esp
Enhanced Landscapes - Oaks.esp
Enhanced Landscapes - DLC Patch.esp
Routa.esp
HoamaiiClearskyHideout.esp
WindPath.esp
AddItemMenu.esp
DynDOLOD.esp
RSPatch.esp
Bashed Patch, 0.esp

Papyrus文字列数の推移

#012 - 39,111
#015 - 44,706 DynDOLOD v1.47の導入
#031 - 45,055
#100 - 46,286
#210 - 50,303
#300 - 52,443
#400 - 59,663
#501 - 61,572
#600 - 63,793
#705 - 65,435 全ホールドの従士になった
#706 - 65,507 ディムホロウ洞窟突入
#707 - 65,434
#708 - 65,473
#710 - 65,496 セラーナ救出
#711 - 65,515
#712 - ロードできない!!
#713 - 41,207 DynDOLOD v1.49へのアップグレード後、屋外に出てセーブ(劇的に減った!)
--- その後の経過 ---
#719 - 41,294 セラーナをヴォルキハル城に送った後
#731 - 41,268 メリディアクエストクリア
#745 - 41,206 ドーンガード砦、新たな命令クエスト開始
#756 - 41,156 ハエマールの不名誉、デイドラの親友クエスト完了
#765 - 41,136 クロンヴァングル洞窟、ガンマーと合流しクマ討伐完了
#890 - 41,383 ドーンガード砦、残響を追ってクエスト開始
#933 - 41,369 アルケイナエウム、霊魂の確認クエスト開始
#949 - 41,491 ソウル・ケルン、死の超越クエスト開始
#972 - 41,562 アイスウォーター桟橋、死の超越クエスト完了
#991 - 41,447 アルフタンド・アニモンキュロリー、霊魂の確認クエスト進行中

グラフにしてみました。
f:id:thinkingskeever:20160501155532p:plain
DynDOLOD v1.49導入以降、増加傾向はほとんどありません。この調子で全クエストクリアまで持ちそうです。

ちなみに、以前構築した2つの環境は、DynDOLODを使っていない以外この環境と似たようなMOD構成でしたが、文字列テーブル数は39,000程度でした。


f:id:thinkingskeever:20151114030550p:plain

最後に

Forumでアドバイスしてくださった元記事The Nexus Forums : Corrupt Saves - StrCount > 0xFFFF - CTD on loadの投稿者bcspさんと、速やかにDynDOLODを改善してくださったShesonさんにこの場を借りて御礼申し上げます。もちろんご両人にKudosを差し上げました!
Thanks bcsp and Sheson!!

また、Crash Fixの作者meh321さんにも感謝します。正直これはFiles of the MonthどころかFiles of the Yearに匹敵する偉業だと思います。もちろんKudosとVOTEを差し上げました!
Thanks meh321! You're a genius!

みなさんへのお願い:
エストMODや比較的複雑な構造・機能を持つMODでは文字列を多く消費する傾向がありますが、決して作者様を責めないでください! どう考えても悪いのはベゼスダですから。
上記のグラフを見ていただければ分かりますが、MODそれぞれの(一度きりの)消費文字数はさほど問題ではありません(大規模MODを大量に導入する際に問題があるかもしれませんが)。オブジェクト増加に従って文字列消費量が増加するMODにさえ気をつければなんとかなると思います。



f:id:thinkingskeever:20151114030550p:plain

変更履歴

2016/04/12 08:00 - 公開
2016/04/13 07:30 - 少し改訂。実例/Papyrus文字列数の推移については今後断りなく追記します
2016/05/04 07:00 - Crash Fix v8とRestringerに関する情報を追記した。
2016/05/04 16:00 - Crash Fix v8の詳細説明を追記した。
2016/05/12 23:30 - StrCountToolのリンクを追記した。
2016/05/13 15:30 - StrCountTool解説ページのリンクを追記した。
//

Copyright (C) 2015-2020 ThinkingSkeever, All Rights Reserved.
ブログの記事内に記載されているメーカー名、製品名称等は、日本及びその他の国における各企業の商標または登録商標です。
リンクはご自由に。記事の転載はご遠慮ください。記事を引用する場合はトラックバックするか元のURLを明記してください。