Javascript Problème lecture D2O

Inscrit
26 Juillet 2020
Messages
2
Reactions
2
#1
Bonjour à tous, je viens vers vous car je fais face à un problème qui m'échappe complètement. Je me suis mit à faire un outil capable de lire les fichiers .d2i, .d2p, .dlm et .d2o tout fonctionne parfaitement, sauf le reader de d2o.

Je vous explique mon problème : j'ai créé mes readers en me basant sur les sources de Dofus, tous fonctionne sauf le d2o dans certain moment précis.

Lorsque je récupère le contenu du fichier comme Quests.d2o, aucun problème, par contre MapPositions.d2o et bah ça fonctionne pas, car il y a des tableaux dans des tableaux (ou tout du moins, c'est la conclusion que j'en ai faite). J'avais découvert que lors du readObject le getInstance() renvoyait forcément une nouvelle classe, donc impossible de récupérer les _counts, après l'avoir corrigé je pensais en avoir fini mais non, je but sur cette erreur :

let vectorTypeName = this._innerTypeNames[innerIndex]; // Cannot read property '1' of undefined
En faisant un print de this pour récupérer le contenu de celui-ci je me rend compte de l'erreur, par contre, je ne vois pas du tout, de où cela peut venir, voici le print :

GameDataField {
name: 'playlists',
_innerReadMethods: [ [Function: readVector], [Function: readInteger] ],
_innerTypeNames: [ 'Vector.<Vector.<int>>', 'Vector.<int>' ],
readData: [Function: readVector]
}

[ [Function: readVector], [Function: readInteger] ]
En faite si on regarde bien, l'avant-dernier passage est correctement formaté, un GameDataField, par contre le denier n'est autre qu'un simple array avec les fonctions qui aurait normalement dû se retrouver dans le _innerReadMethods.

Voici mon code :

JavaScript:
import ByteArray from "bytearray-node";
import { readFileSync } from "fs";

const Endian = require('bytearray-node/enums/Endian');

class GameDataField {
    private static NULL_IDENTIFIER: number = -1431655766;
  
    public name: string;
    public readData: any;
    private _innerReadMethods: any[] | undefined;
    private _innerTypeNames: string[] | undefined;

    constructor(fieldName: string) {
        this.name = fieldName;
    }

    public readType(stream: ByteArray): void {
        let type: number = stream.readInt();
        this.readData = this.getReadMethod(type, stream);
    }

    private getReadMethod(type: number, stream: ByteArray): any {
        switch (type) {
            case -1:
                return this.readInteger;
            case -2:
                return this.readBoolean;
            case -3:
                return this.readString;
            case -4:
                return this.readNumber;
            case -5:
                return this.readI18n;
            case -6:
                return this.readUnsignedInteger;
            case -99:
                if (!this._innerReadMethods) {
                    this._innerReadMethods = [];
                    this._innerTypeNames = [];
                }

                // @ts-ignore
                this._innerTypeNames.push(stream.readUTF());
                this._innerReadMethods.unshift(this.getReadMethod(stream.readInt(), stream));
                return this.readVector;
            default:
                if (type > 0) {
                    return this.readObject;
                }

                throw new Error(`Unknown type ${type}.`);
        }
    }

    private readInteger(moduleName: any, stream: ByteArray, innerIndex: number = 0): any {
        return stream.readInt();
    }

    private readBoolean(moduleName: any, stream: ByteArray, innerIndex: number = 0): any {
        return stream.readBoolean();
    }

    private readString(moduleName: any, stream: ByteArray, innerIndex: number = 0): any {
        let result: any = stream.readUTF();
        if (result === 'null') {
            result = null;
        }
        return result;
    }

    private readNumber(moduleName: any, stream: ByteArray, innerIndex: number = 0): any {
        return stream.readDouble();
    }

    private readI18n(moduleName: any, stream: ByteArray, innerIndex: number = 0): any {
        return stream.readInt();
    }

    private readUnsignedInteger(moduleName: any, stream: ByteArray, innerIndex: number = 0): any {
        return stream.readUnsignedInt();
    }

    private readVector(moduleName: any, stream: ByteArray, innerIndex: number = 0): any {
        console.log(this);
        let len: number = stream.readInt();
        // @ts-ignore
        let vectorTypeName: string = this._innerTypeNames[innerIndex];
        let content: any[] = [];

        for (let i: number = 0; i < len; i++) {
            // @ts-ignore
            content[i] = this._innerReadMethods[innerIndex](moduleName, stream, innerIndex + 1);
        }

        return content;
    }

    private readObject(moduleName: any, stream: ByteArray, innerIndex: number = 0): any {
        let classIdentifier: number = stream.readInt();

        if (classIdentifier === GameDataField.NULL_IDENTIFIER) {
            return null;
        }

        let classDefinition: GameDataClassDefinition = D2OReader.getInstance().getClassDefinition(moduleName, classIdentifier);
        return classDefinition.read(moduleName, stream);
    }
}

class GameDataClassDefinition {
    private _class: string;
    private _fields: GameDataField[];

    constructor(packageName: string, className: string) {
        this._class = `${packageName}.${className}`;
        this._fields = [];
    }

    public addField(fieldName: string, stream: ByteArray): void {
        let field: GameDataField = new GameDataField(fieldName);
        field.readType(stream);
        this._fields.push(field);
    }

    public read(module: any, stream: ByteArray): any {
        let objs: any[] = [];
        this._fields.forEach(field => {
            // @ts-ignore
            objs[field.name] = field.readData(module, stream);
        });
        return objs;
    }
}

class GameDataProcess {

    private _searchFieldIndex: any[] | undefined;
    private _searchFieldCount: any[] | undefined;
    private _searchFieldType: any[] | undefined;
    private _queryableField: string[] | undefined;
    private _stream: ByteArray;
    private _currentStream: ByteArray | undefined;
    private _sortIndex: any[];

    constructor(stream: ByteArray) {
        this._stream = stream;
        this._sortIndex = [];
        this.parseStream();
    }

    private parseStream(): void {
        let size: number = 0;
        let fieldName: any;
        this._queryableField = [];
        this._searchFieldIndex = [];
        this._searchFieldType = [];
        this._searchFieldCount = [];
        let fieldListSize: number = this._stream.readInt();
        let indexSearchOffset: number = this._stream.position + fieldListSize + 4;

        while (fieldListSize) {
            size = this._stream.bytesAvailable;
            fieldName = this._stream.readUTF();
            this._queryableField.push(fieldName);
            this._searchFieldIndex[fieldName] = this._stream.readInt() + indexSearchOffset;
            this._searchFieldType[fieldName] = this._stream.readInt();
            this._searchFieldCount[fieldName] = this._stream.readInt();
            fieldListSize = fieldListSize - (size - this._stream.bytesAvailable);
        }
    }
}

export default class D2OReader {
    private static _self: D2OReader;

    private _streams: any[];
    private _streamStartIndex: any[];
    private _indexes: any[];
    private _classes: any[];
    private _counter: any[];
    private _gameDataProcessor: any[];

    constructor() {
        D2OReader._self = this;
        this._streams = [];
        this._streamStartIndex = [];
        this._indexes = [];
        this._classes = [];
        this._counter = [];
        this._gameDataProcessor = [];
    }

    public static getInstance(): D2OReader {
        if (!D2OReader._self) {
            D2OReader._self = new D2OReader();
        }

        return D2OReader._self;
    }

    public init(fileUri: string): void {
        let nativeFile: Buffer = readFileSync(fileUri);

        if (!nativeFile) {
            throw new Error(`Game data file ${fileUri} not readable`);
        }

        // @ts-ignore
        let moduleName: any = fileUri.split('/').pop().split('.')[0];

        if (!this._streams) {
            this._streams = [];
        }

        if (!this._streamStartIndex) {
            this._streamStartIndex = [];
        }

        let stream: ByteArray = this._streams[moduleName];

        if (!stream) {
            stream = new ByteArray(nativeFile);
            stream.endian = Endian.BIG_ENDIAN;
            this._streams[moduleName] = stream;
            this._streamStartIndex[moduleName] = 7;
        } else {
            stream.position = 0;
        }

        this.initFromIDataInput(stream, moduleName);
    }

    public initFromIDataInput(stream: ByteArray, moduleName: any): void {
        let key: number = 0;
        let pointer: number = 0;
        let count: number = 0;
        let classIdentifier: number = 0;
        let formatVersion: number = 0;
        let len: number = 0;

        if (!this._streams) {
            this._streams = [];
        }

        if (!this._indexes) {
            this._indexes = [];
        }

        if (!this._classes) {
            this._classes = [];
        }

        if (!this._counter) {
            this._counter = [];
        }

        if (!this._streamStartIndex) {
            this._streamStartIndex = [];
        }

        if (!this._gameDataProcessor) {
            this._gameDataProcessor = [];
        }

        this._streams[moduleName] = stream;

        if (!this._streamStartIndex[moduleName]) {
            this._streamStartIndex[moduleName] = 7;
        }

        let indexes: any[] = [];
        this._indexes[moduleName] = indexes;
        let contentOffset: number = 0;
        let headers: string = stream.readMultiByte(3, 'ascii');

        if (headers !== 'D2O') {
            stream.position = 0;

            try {
                headers = stream.readUTF();
            } catch {}

            if (headers !== 'AKSF') {
                throw new Error('Malformated game data file. (AKSF)');
            }

            formatVersion = stream.readShort();
            len = stream.readInt();
            stream.position += len;
            contentOffset = stream.position;
            this._streamStartIndex[moduleName] = contentOffset + 7;
            headers = stream.readMultiByte(3, 'ascii');

            if (headers !== 'D2O') {
                throw new Error('Malformated game data file. (D2O)');
            }
        }

        let indexesPointer: number = stream.readInt();
        stream.position = contentOffset + indexesPointer;
        let indexesLength: number = stream.readInt();

        for (let i: number = 0; i < indexesLength; i += 8) {
            key = stream.readInt();
            pointer = stream.readInt();
            indexes[key] = contentOffset + pointer;
            count++;
        }

        this._counter[moduleName] = count;
        let classes: any[] = [];
        this._classes[moduleName] = classes;
        let classesCount: number = stream.readInt();

        for (let i: number = 0; i < classesCount; i++) {
            classIdentifier = stream.readInt();
            this.readClassDefinition(classIdentifier, stream, classes);
        }

        if (stream.bytesAvailable) {
            this._gameDataProcessor[moduleName] = new GameDataProcess(stream);
        }
    }

    private readClassDefinition(classId: number, stream: ByteArray, store: any[]): void {
        let fieldName: string = '';
        let fieldType: number = 0;
        let className: string = stream.readUTF();
        let packageName: string = stream.readUTF();
        let classDef: GameDataClassDefinition = new GameDataClassDefinition(packageName, className);
        let fieldsCount: number = stream.readInt();

        for (let i: number = 0; i < fieldsCount; i++) {
            fieldName = stream.readUTF();
            classDef.addField(fieldName, stream);
        }

        store[classId] = classDef;
    }

    public getClassDefinition(moduleName: any, classId: number): GameDataClassDefinition {
        console.log(this);
        return this._classes[moduleName][classId];
    }

    public getObjects(moduleName: any): any[] | null {
        if (!this._counter || !this._counter[moduleName]) {
            return null;
        }

        let len: number = this._counter[moduleName];
        let classes: any[] = this._classes[moduleName];
        let stream: ByteArray = this._streams[moduleName];
        stream.position = this._streamStartIndex[moduleName];
        let objs: any[] = [];

        for (let i: number = 0; i < len; i++) {
            objs[i] = classes[stream.readInt()].read(moduleName, stream);
        }

        return objs;
    }
}
J'ai beau chercher, retaper le code, le retaper en javascript dans le doute si j'avais fait une erreur de typage, rien, je tombe toujours sur la même erreur, j'ai bien besoin de votre aide, s'il vous plait !
 

BlueDream

Administrateur
Membre du personnel
Inscrit
8 Decembre 2012
Messages
1 932
Reactions
55
#2
Tu utilises les read system ou c'est du custom ?
 
Inscrit
26 Juillet 2020
Messages
2
Reactions
2
#3
pour récupérer le buffer d'un fichier j'utilise fs.readFileSync() et ensuite j'utilise la lib bytearray-node (https://www.npmjs.com/package/bytearray-node) qui je cite est : « A Node.js implementation of the Actionscript 3 ByteArray » je l'ai vu utilisé sur un projet d'ici pendant mes recherches, et je l'utilise sur mon reader de d2i et de d2p/dlm il fonctionne vachement bien
 

BlueDream

Administrateur
Membre du personnel
Inscrit
8 Decembre 2012
Messages
1 932
Reactions
55
#4
@lonthamps Oué mais ya quand même des fonctions custom, même si tu utilises une lib 100% officielle.
Tous les writeVar & readVar, va checker les sources; Ou alors je me trompe et ces fonctions ne sont pas utilisées dans les D2O?
J'ai rien actuellement sur mon pc , je ne peux pas vérifier.

Sinon, à ta place, je prendrais un code fonctionnel en C# par exemple + break point,
et je compare les valeurs une par une afin de savoir ou ça pose problème.
Un décalage c'est assez difficile à trouver sans débugger.
 
Haut Bas