Diablo IIは、ゲームキャラクターをDiskに.d2sファイルとして保存します。これは、すべての統計、アイテム、名前、およびその他のデータをエンコードするバイナリファイル形式です。
整数はリトルエンディアンバイトの順序で保存されます。これは、x86アーキテクチャで注文するネイティブバイトであるDiablo IIに基づいています。
各.D2Sファイルは765バイトヘッダーで始まり、その後データは長さが変動します。
| ヘックス | バイト | 長さ | desc |
|---|---|---|---|
| 0x00 | 0 | 4 | 署名(0xaa55aa55) |
| 0x04 | 4 | 4 | バージョンID |
| 0x08 | 8 | 4 | ファイルサイズ |
| 0x0c | 12 | 4 | チェックサム |
| 0x10 | 16 | 4 | アクティブな武器 |
| 0x14 | 20 | 16 | 文字名 |
| 0x24 | 36 | 1 | 文字ステータス |
| 0x25 | 37 | 1 | キャラクターの進行 |
| 0x26 | 38 | 2 | ? |
| 0x28 | 40 | 1 | キャラクタークラス |
| 0x29 | 41 | 2 | ? |
| 0x2b | 43 | 1 | レベル |
| 0x2c | 44 | 4 | ? |
| 0x30 | 48 | 4 | 時間 |
| 0x34 | 52 | 4 | ? |
| 0x38 | 56 | 64 | ホットキー |
| 0x78 | 120 | 4 | 左マウス |
| 0x7c | 124 | 4 | 右マウス |
| 0x80 | 128 | 4 | 左マウス(武器スイッチ) |
| 0x84 | 132 | 4 | 右マウス(武器スイッチ) |
| 0x88 | 136 | 32 | キャラクターメニューの外観 |
| 0xa8 | 168 | 3 | 困難 |
| 0xab | 171 | 4 | 地図 |
| 0xaf | 175 | 2 | ? |
| 0xb1 | 177 | 2 | メルク・デッド? |
| 0xb3 | 179 | 4 | メルコシード? |
| 0xb7 | 183 | 2 | MercネームID |
| 0xb9 | 185 | 2 | メルクタイプ |
| 0xbb | 187 | 4 | メルクエクスペリエンス |
| 0xbf | 191 | 144 | ? |
| 0x14f | 335 | 298 | クエスト |
| 0x279 | 633 | 81 | ウェイポイント |
| 0x2CA | 714 | 51 | NPC |
| 0x2fd | 765 | 統計 | |
| アイテム |
ファイルバージョン。次の値が既知です:
71は1.00からv1.06です87は1.07または拡張セットv1.08です89は標準ゲームv1.08です92はv1.09です(標準ゲームと拡張セットの両方です。)96はv1.10+ですチェックサムを計算するには、.d2sデータの値をゼロに設定し、32ビットチェックサムを計算するデータのすべてのバイトを繰り返します。
sum = ( sum << 1 ) + data [ i ];出典:#5
const fs = require ( "fs" ) ;
const path = require ( "path" ) ;
const file = path . join ( process . cwd ( ) , "path_to_save.d2s" ) ;
function calculateSum ( data ) {
let sum = 0 ;
for ( let i = 0 ; i < data . length ; i ++ ) {
let ch = data [ i ] ;
if ( i >= 12 && i < 16 ) {
ch = 0 ;
}
ch += sum < 0 ;
sum = ( sum << 1 ) + ch ;
}
return sum ;
}
function littleToBigEndian ( number ) {
return new DataView (
Int32Array . of (
new DataView ( Int32Array . of ( number ) . buffer ) . getUint32 ( 0 , true )
) . buffer
) ;
}
function ashex ( buffer ) {
return buffer . getUint32 ( 0 , false ) . toString ( 16 ) ;
}
async function readSafeFile ( ) {
return await new Promise ( ( resolve , reject ) => {
fs . readFile ( file , ( err , data ) => {
if ( err ) return reject ( err ) ;
return resolve ( data ) ;
} ) ;
} ) ;
}
async function writeCheckSumToSafeFile ( data ) {
return await new Promise ( ( resolve , reject ) => {
fs . writeFile ( file , data , err => {
if ( err ) reject ( err ) ;
resolve ( ) ;
} ) ;
} ) ;
}
readSafeFile ( ) . then ( data => {
const sum = calculateSum ( data ) ;
const bufferSum = littleToBigEndian ( sum ) ;
const hex = ashex ( bufferSum ) ;
const newData = data ;
for ( let i = 0 ; i < 4 ; i ++ ) {
newData [ 12 + i ] = bufferSum . getInt8 ( i ) ;
}
writeCheckSumToSafeFile ( newData ) . then ( ( ) => console . log ( hex ) ) ;
} ) ;出典:https://github.com/gucio321/d2d2s/blob/66f91e2af7b3949ca7f279aae397bd8904519e2d/pkg/d2s/d2s.go#l397
// CalculateChecksum calculates a checksum and saves in a byte slice
func CalculateChecksum ( data * [] byte ) {
var sum uint32
for i := range * data {
sum = (( sum << 1 ) % math . MaxUint32 ) | ( sum >> ( int32Size * byteLen - 1 ))
sum += uint32 (( * data )[ i ])
}
sumBytes := make ([] byte , int32Size )
binary . LittleEndian . PutUint32 ( sumBytes , sum )
const (
int32Size = 4
checksumPosition = 12
)
for i := 0 ; i < int32Size ; i ++ {
( * data )[ checksumPosition + i ] = sumBytes [ i ]
}
}チェックサムが無効である場合、Diablo IIは保存ファイルを開きません。
トト
文字名は、残りのバイトに0x00でパッド入りのヌル終端文字列を含む16文字の配列として保存されます。文字は8ビットASCIIとして保存されますが、有効なことは次のルールに従う必要があることを忘れないでください。
- )またはアンダースコア( _ )を含む場合がありますこれは8ビットフィールドです。
| 少し | desc |
|---|---|
| 0 | ? |
| 1 | ? |
| 2 | ハードコア |
| 3 | 死んだ |
| 4 | ? |
| 5 | 拡大 |
| 6 | ? |
| 7 | ? |
トト
| id | クラス |
|---|---|
| 0 | アマゾン |
| 1 | 魔術師 |
| 2 | ネクロマンサー |
| 3 | パラディン |
| 4 | 野b人 |
| 5 | ドルイド |
| 6 | 暗殺者 |
このレベルの値は、文字選択画面でのみ表示され、統計セクションでこれと同じでなければなりません。
トト
メニューでキャラクターがどのように見えるかを定義する32バイト構造ゲーム内の外観が変更されない
キャラクターがロックを解除した3つの困難のどれを示す3バイトのデータ。各バイトは、困難の1つを表しています。この順序で:通常、悪夢、地獄。各バイトは、このように構造化されたビットフィールドです。
| 7 | 6 | 5 | 4 | 3 | 2、1、0 |
|---|---|---|---|---|---|
| アクティブ? | 未知 | 未知 | 未知 | 未知 | どの法律(0-4)? |
トト
トト
ウェイポイントデータは、2枚のchars "ws"と6つの不明なバイトから始まります。
オフセット641、665、および689で、難易度ごとに3つの構造が整っています。
この構造の内容は次のとおりです
| バイト | バイテス | コンテンツ |
|---|---|---|
| 0 | 2バイト | {0x02、0x01}未知の目的 |
| 2 | 5バイト | ウェイポイントビットフィールドは、最小限のビットの順に |
| 7 | 17バイト | 未知 |
ウェイポイントビットフィールドでは、1のビット値は、ウェイポイントが有効になることを意味します。これは、最低から最高まで順序であるため、0は不正な野営地(Act I)などです。各難易度の最初のウェイポイントは常にアクティブ化されます。
トト
TODO(9ビットエンコーディング)
アイテムは、このヘッダーで説明されているリストに保存されます。
| バイト | サイズ | desc |
|---|---|---|
| 0 | 2 | 「JM」 |
| 2 | 2 | アイテム数 |
この後、nアイテムが来ます。各アイテムは、基本的な14バイト構造から始まります。この構造内の多くのフィールドは「バイトアライメント」されておらず、ビットの位置とサイズで説明されています。
| 少し | サイズ | desc |
|---|---|---|
| 0 | 16 | 「JM」(リストヘッダーとは別) |
| 16 | 4 | ? |
| 20 | 1 | 識別されます |
| 21 | 6 | ? |
| 27 | 1 | ソケット |
| 28 | 1 | ? |
| 29 | 1 | 最後のセーブ以来ピックアップ |
| 30 | 2 | ? |
| 32 | 1 | 耳 |
| 33 | 1 | スターターギア |
| 34 | 3 | ? |
| 37 | 1 | コンパクト |
| 38 | 1 | エーテル |
| 39 | 1 | ? |
| 40 | 1 | パーソナライズ |
| 41 | 1 | ? |
| 42 | 1 | Runeword |
| 43 | 15 | ? |
| 58 | 3 | 親 |
| 61 | 4 | 装備 |
| 65 | 4 | カラム |
| 69 | 3 | 行 |
| 72 | 1 | ? |
| 73 | 3 | 隠し場所 |
| 76 | 4 | ? |
| 80 | 24 | タイプコード(3文字) |
| 108 | 拡張アイテムデータ |
アイテムがCompact (ビット37が設定されている)としてマークされている場合、拡張アイテム情報は存在しません。アイテムは終了します。
アイテムヘッダーの情報に基づいて、拡張情報ストアのビットを備えたアイテム。たとえば、 Socketedとしてマークされたアイテムには、アイテムが持っているソケットの数をエンコードする追加の3ビット整数が保存されます。
| 少し | サイズ | desc |
|---|---|---|
| 108 | ソケット | |
| カスタムグラフィックス | ||
| クラス固有 | ||
| 品質 | ||
| mods |
カスタムグラフィックは、1ビットで示されます。これは、セットがグラフィックインデックスの3ビット番号が続くことを意味します。ビットが設定されていない場合、3ビットは存在しません。
| 少し | サイズ | desc |
|---|---|---|
| 0 | 1 | アイテムにはカスタムグラフィックがあります |
| 1 | 3 | 代替グラフィックインデックス |
Barbarian HelmsやAmazon Bowsなどのクラスアイテムには、これらの種類のアイテムに固有の特別な特性があります。最初のビットが空の場合、残りの11ビットは存在しません。
| 少し | サイズ | desc |
|---|---|---|
| 0 | 1 | アイテムにはクラス固有のデータがあります |
| 1 | 11 | クラス固有のビット |
アイテムの品質は、4ビット整数としてエンコードされています。
各アイテムの後、MODのリストになります。リストは一連のキー値ペアで、キーは9ビット番号で、値はキーに依存します。リストは、すべての9ビットが設定されているキー511 ( 0x1ff )が見つかったときに終了します。
ファイルItemStatCost.txtタブで削除したCSVファイルとして使用すると、9ビットキーにマップするID列を抽出できます。列はSave BitsとParam Bitsを保存します。MODの大きさを説明します。
唯一の例外は、CSVの次の行を使用してmodの「max」部分を保存するmin-maxスタイルの修飾子です。これら2つのビットサイズは異なる場合があり、合計サイズを取得するために合計する必要があります。
トト
すべてのアイテムはどこかにあり、宝石を挿入するときなど、別のアイテムになる可能性のある「親」があります。
| 価値 | desc |
|---|---|
| 0 | 保存されています |
| 1 | 装備 |
| 2 | ベルト |
| 4 | カーソル |
| 6 | アイテム |
「保存」されたアイテムについては、ビット73から開始された3ビット整数エンコードされたエンコードについては、アイテムを保存する場所について説明します。
| 価値 | desc |
|---|---|
| 1 | 在庫 |
| 4 | ホラドリックキューブ |
| 5 | 隠し場所 |
装備されているアイテムは、スロットを説明しています。
| 価値 | スロット |
|---|---|
| 1 | ヘルメット |
| 2 | お守り |
| 3 | 鎧 |
| 4 | 武器(右) |
| 5 | 武器(左) |
| 6 | リング(右) |
| 7 | リング(左) |
| 8 | ベルト |
| 9 | ブーツ |
| 10 | 手袋 |
| 11 | 代替武器(右) |
| 12 | 代替武器(左) |