O Diablo II armazena seu personagem de jogo no disco como um arquivo .d2s. Este é um formato de arquivo binário que codifica todas as estatísticas, itens, nome e outros dados.
Os números inteiros são armazenados em Little Endian Byte Order, que é a ordem de bytes nativos em uma arquitetura X86 Diablo II se baseia.
Cada arquivo .d2s começa com um cabeçalho de 765 bytes, após o qual os dados são de comprimento variável.
| Hexadecimal | Byte | Comprimento | Desc |
|---|---|---|---|
| 0x00 | 0 | 4 | Assinatura (0XAA55AA55) |
| 0x04 | 4 | 4 | ID da versão |
| 0x08 | 8 | 4 | Tamanho do arquivo |
| 0x0c | 12 | 4 | Soma de verificação |
| 0x10 | 16 | 4 | Arma ativa |
| 0x14 | 20 | 16 | Nome do personagem |
| 0x24 | 36 | 1 | Status do personagem |
| 0x25 | 37 | 1 | Progressão do personagem |
| 0x26 | 38 | 2 | ? |
| 0x28 | 40 | 1 | Classe de personagem |
| 0x29 | 41 | 2 | ? |
| 0x2b | 43 | 1 | Nível |
| 0x2c | 44 | 4 | ? |
| 0x30 | 48 | 4 | Tempo |
| 0x34 | 52 | 4 | ? |
| 0x38 | 56 | 64 | Teclas de atalho |
| 0x78 | 120 | 4 | Mouse esquerdo |
| 0x7c | 124 | 4 | Mouse direito |
| 0x80 | 128 | 4 | Mouse esquerdo (interruptor de arma) |
| 0x84 | 132 | 4 | Mouse direito (interruptor de arma) |
| 0x88 | 136 | 32 | Aparência do menu do personagem |
| 0XA8 | 168 | 3 | Dificuldade |
| 0xab | 171 | 4 | Mapa |
| 0XAF | 175 | 2 | ? |
| 0xb1 | 177 | 2 | Merc Dead? |
| 0xb3 | 179 | 4 | Semente de mercadoria? |
| 0xb7 | 183 | 2 | Merc Nome ID |
| 0xb9 | 185 | 2 | Tipo Merc |
| 0xbb | 187 | 4 | Experiência Merc |
| 0xbf | 191 | 144 | ? |
| 0x14f | 335 | 298 | Busca |
| 0x279 | 633 | 81 | Waypoint |
| 0x2ca | 714 | 51 | NPC |
| 0x2fd | 765 | Estatísticas | |
| Unid |
Versão do arquivo. Os seguintes valores são conhecidos:
71 é 1,00 a v1.0687 é 1,07 ou conjunto de expansão v1.0889 é o jogo padrão v1.0892 é v1.09 (o jogo padrão e o conjunto de expansão.)96 é v1.10+Para calcular a soma de verificação, defina o valor dele nos dados .d2s como zero e iterar em todos os bytes nos dados que calcula uma soma de verificação de 32 bits:
sum = ( sum << 1 ) + data [ i ];Fonte: #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 ) ) ;
} ) ;Fonte: 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 ]
}
}Se a soma de verificação for inválida, o Diablo II não abrirá o arquivo de salvamento.
PENDÊNCIA
Os nomes dos caracteres são armazenados como uma matriz de 16 caracteres que contêm uma corda terminada nula acolchoada com 0x00 para os bytes restantes. Os caracteres são armazenados como ASCII de 8 bits, mas lembre-se de que válido deve seguir estas regras:
- ) ou sublinhado ( _ )Este é um campo de 8 bits:
| Pedaço | Desc |
|---|---|
| 0 | ? |
| 1 | ? |
| 2 | Hardcore |
| 3 | Morreu |
| 4 | ? |
| 5 | Expansão |
| 6 | ? |
| 7 | ? |
PENDÊNCIA
| EU IA | Aula |
|---|---|
| 0 | Amazon |
| 1 | Feiticeira |
| 2 | Necromante |
| 3 | Paladino |
| 4 | Bárbaro |
| 5 | druida |
| 6 | Assassino |
Esse valor de nível é visível apenas na tela de seleção de caracteres e deve ser o mesmo na seção de estatísticas.
PENDÊNCIA
32 BYTE STRUCTURA que define como o personagem fica no menu não muda a aparência no jogo
3 bytes de dados que indicam qual das três dificuldades que o caractere desbloqueou. Cada byte é representativo de uma das dificuldades. Nesta ordem: normal, pesadelo e inferno. Cada byte é um campo de bits estruturado assim:
| 7 | 6 | 5 | 4 | 3 | 2, 1, 0 |
|---|---|---|---|---|---|
| Ativo? | Desconhecido | Desconhecido | Desconhecido | Desconhecido | Qual ato (0-4)? |
PENDÊNCIA
PENDÊNCIA
Os dados do waypoint começam com 2 chars "WS" e 6 bytes desconhecidos, sempre = {0x01, 0x00, 0x00, 0x00, 0x50, 0x00}
Três estruturas estão em vigor para cada dificuldade, nos compensações 641, 665 e 689.
O conteúdo dessa estrutura é o seguinte
| byte | bytesize | conteúdo |
|---|---|---|
| 0 | 2 bytes | {0x02, 0x01} Objetivo desconhecido |
| 2 | 5 bytes | Waypoint Bitfield em ordem de bit menos significativa |
| 7 | 17 bytes | desconhecido |
No Waypoint Bitfield, um valor de um pouco de 1 significa que o waypoint está ativado, ele está em uma ordem do mais baixo ao mais alto, então 0 é o acampamento desonesto (Ato I) etc. O primeiro waypoint em cada dificuldade é sempre ativado.
PENDÊNCIA
TODO (codificação de 9 bits)
Os itens são armazenados em listas descritas por este cabeçalho:
| Byte | Tamanho | Desc |
|---|---|---|
| 0 | 2 | "JM" |
| 2 | 2 | Contagem de itens |
Depois disso, vêm n itens. Cada item começa com uma estrutura básica de 14 bytes. Muitos campos nessa estrutura não são "alinhados por bytes" e são descritos por sua posição e tamanhos de bit.
| Pedaço | Tamanho | Desc |
|---|---|---|
| 0 | 16 | "JM" (separado do cabeçalho da lista) |
| 16 | 4 | ? |
| 20 | 1 | Identificado |
| 21 | 6 | ? |
| 27 | 1 | Encaixado |
| 28 | 1 | ? |
| 29 | 1 | Pego desde o último salvamento |
| 30 | 2 | ? |
| 32 | 1 | Orelha |
| 33 | 1 | Equipamento inicial |
| 34 | 3 | ? |
| 37 | 1 | Compactar |
| 38 | 1 | Etéreo |
| 39 | 1 | ? |
| 40 | 1 | Personalizado |
| 41 | 1 | ? |
| 42 | 1 | RuneWord |
| 43 | 15 | ? |
| 58 | 3 | Pai |
| 61 | 4 | Equipado |
| 65 | 4 | Coluna |
| 69 | 3 | Linha |
| 72 | 1 | ? |
| 73 | 3 | Esconder |
| 76 | 4 | ? |
| 80 | 24 | Tipo de código (3 letras) |
| 108 | Dados estendidos de itens |
Se o item estiver marcado como Compact (bit 37 estiver definido), nenhuma informação estendida do item existirá e o item será concluído.
Itens com bits de armazenamento de informações estendidas com base nas informações no cabeçalho do item. Por exemplo, um item marcado como Socketed armazenará um número inteiro extra de 3 bits codificando quantos soquetes o item possui.
| Pedaço | Tamanho | Desc |
|---|---|---|
| 108 | Soquetes | |
| Gráficos personalizados | ||
| Classe específica | ||
| Qualidade | ||
| Mods |
Os gráficos personalizados são denotados por um único bit, que se definido significa um número de 3 bits para o índice gráfico segue. Se o bit não estiver definido, os 3 bits não estarão presentes.
| Pedaço | Tamanho | Desc |
|---|---|---|
| 0 | 1 | Item tem gráficos personalizados |
| 1 | 3 | Índice gráfico alternativo |
Itens de aula como bárbaros ou arcos da Amazon têm propriedades especiais específicas para esses tipos de itens. Se o primeiro bit estiver vazio, os 11 bits restantes não estarão presentes.
| Pedaço | Tamanho | Desc |
|---|---|---|
| 0 | 1 | Item tem dados específicos da classe |
| 1 | 11 | Bits específicos da classe |
A qualidade do item é codificada como um número inteiro de 4 bits.
Depois de cada item, é uma lista de mods. A lista é uma série de pares de valores-chave, onde a chave é um número de 9 bits e o valor depende da chave. A lista termina quando a chave 511 ( 0x1ff ) é encontrada, que está sendo definida por 9 bits.
Usando o arquivo ItemStatCost.txt como um arquivo CSV delimitado por Tab, você pode extrair a coluna ID que mapeia as teclas de 9 bits. As colunas Save Bits e Param Bits descrevem o tamanho do mod.
A única exceção são os modificadores de estilo Min-Max que usam a próxima linha no CSV para armazenar a parte "Max" do mod. Os tamanhos de bits desses dois podem ser diferentes e você deve resumir para obter o tamanho total.
PENDÊNCIA
Todos os itens estão localizados em algum lugar e têm um "pai" que pode ser outro item, como a inserção de uma jóia.
| Valor | Desc |
|---|---|
| 0 | Armazenado |
| 1 | Equipado |
| 2 | Cinto |
| 4 | Cursor |
| 6 | Item |
Para itens que são "armazenados", um número inteiro de 3 bits codificado a partir do bit 73 descreve onde armazenar o item:
| Valor | Desc |
|---|---|
| 1 | Inventário |
| 4 | Cubo horadico |
| 5 | Esconder |
Os itens equipados descrevem seu slot:
| Valor | Slot |
|---|---|
| 1 | Capacete |
| 2 | Amuleto |
| 3 | Armadura |
| 4 | Arma (à direita) |
| 5 | Arma (à esquerda) |
| 6 | Anel (direita) |
| 7 | Anel (à esquerda) |
| 8 | Cinto |
| 9 | Botas |
| 10 | Luvas |
| 11 | Arma alternativa (direita) |
| 12 | Arma alternativa (à esquerda) |