@kobold/core
Version:
Kobold core
193 lines (136 loc) • 7.42 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
var _Object$defineProperty = require("@babel/runtime-corejs3/core-js/object/define-property");
_Object$defineProperty(exports, "__esModule", {
value: true
});
exports.Category = void 0;
var _concat = _interopRequireDefault(require("@babel/runtime-corejs3/core-js/instance/concat"));
var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js/promise"));
var _map = _interopRequireDefault(require("@babel/runtime-corejs3/core-js/instance/map"));
var _lastIndexOf = _interopRequireDefault(require("@babel/runtime-corejs3/core-js/instance/last-index-of"));
var _indexOf = _interopRequireDefault(require("@babel/runtime-corejs3/core-js/instance/index-of"));
var _padStart = _interopRequireDefault(require("@babel/runtime-corejs3/core-js/instance/pad-start"));
var _slice = _interopRequireDefault(require("@babel/runtime-corejs3/core-js/instance/slice"));
var _map2 = _interopRequireDefault(require("@babel/runtime-corejs3/core-js/map"));
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/defineProperty"));
var _fastGlob = _interopRequireDefault(require("fast-glob"));
var _fs = _interopRequireDefault(require("fs"));
var _path = _interopRequireDefault(require("path"));
var _util = _interopRequireDefault(require("util"));
var _zlib = _interopRequireDefault(require("zlib"));
var _parser = require("./parser");
var _utilities = require("./utilities");
const async = {
fs: {
readFile: _util.default.promisify(_fs.default.readFile),
open: _util.default.promisify(_fs.default.open),
close: _util.default.promisify(_fs.default.close),
read: _util.default.promisify(_fs.default.read)
},
zlib: {
inflateRaw: _util.default.promisify(_zlib.default.inflateRaw)
}
};
const asyncReadBuffer = (fd, length, offset) => async.fs.read(fd, Buffer.alloc(length), 0, length, offset);
var IndexType;
(function (IndexType) {
IndexType[IndexType["INDEX"] = 0] = "INDEX";
IndexType[IndexType["INDEX2"] = 1] = "INDEX2";
})(IndexType || (IndexType = {}));
// TODO: Most of this should be broken out into CategoryChunk handling
class Category {
constructor(opts) {
(0, _defineProperty2.default)(this, "categoryId", void 0);
(0, _defineProperty2.default)(this, "repositoryPath", void 0);
(0, _defineProperty2.default)(this, "indexCache", new _map2.default());
(0, _defineProperty2.default)(this, "fixHashData", data => ({
isSynonym: (data & 0b1) === 0b1,
dataFileId: (data & 0b1110) >>> 1,
offset: (data & ~0xf) * 8
}));
this.categoryId = opts.categoryId;
this.repositoryPath = opts.repositoryPath;
}
get idPrefix() {
var _context, _context2;
return (0, _slice.default)(_context = (0, _padStart.default)(_context2 = this.categoryId.toString(16)).call(_context2, 2, '0')).call(_context, 0, 2);
}
async buildIndexes(type) {
const extension = type === IndexType.INDEX ? 'index' : 'index2'; // Find index files
const indexFiles = await (0, _fastGlob.default)(`${this.idPrefix}????.*.${extension}`, {
cwd: this.repositoryPath,
caseSensitiveMatch: false
});
(0, _utilities.assert)(indexFiles.length === 1, 'TODO: Handle multiple chunks');
const indexFile = indexFiles[0];
const platform = indexFile.substring((0, _indexOf.default)(indexFile).call(indexFile, '.') + 1, (0, _lastIndexOf.default)(indexFile).call(indexFile, '.'));
(0, _utilities.assert)(platform === 'win32', `TODO: Handle other platforms. Expected 'win32', got '${platform}'.`);
const indexPath = _path.default.join(this.repositoryPath, indexFile);
const indexBuffer = await async.fs.readFile(indexPath);
const parser = type === IndexType.INDEX ? _parser.sqPackIndexParser : _parser.sqPackIndex2Parser;
const parsed = parser.parse(indexBuffer);
const indexes = new _map2.default();
for (const entry of parsed.indexes) {
const key = typeof entry.hash === 'bigint' ? entry.hash : BigInt(entry.hash);
indexes.set(key, this.fixHashData(entry.data));
}
return indexes;
} // TODO: Write my own parser so this shit isn't required
// Square's doing a smart thing and storing a uint32 in 28 bits, as the last 4 are always 0 and can be used for the bitfield above.
getIndexes(type) {
let indexPromise = this.indexCache.get(type);
if (indexPromise == null) {
indexPromise = this.buildIndexes(type);
this.indexCache.set(type, indexPromise);
}
return indexPromise;
}
async getFileEntry(pathInfo) {
var _await$this$getIndexe;
// TODO: Consider if both indexes should be preloaded. As-is, we're lazy loading both,
// so a theoretical lookup that's only in index2 would have two sequential lookups, not parallel
const entry = (_await$this$getIndexe = (await this.getIndexes(IndexType.INDEX)).get(pathInfo.indexHash)) !== null && _await$this$getIndexe !== void 0 ? _await$this$getIndexe : (await this.getIndexes(IndexType.INDEX2)).get(pathInfo.index2Hash);
return entry;
}
async getFile(pathInfo) {
const entry = await this.getFileEntry(pathInfo);
(0, _utilities.assert)(entry != null, `${pathInfo.path} not found in indexes`);
(0, _utilities.assert)(!entry.isSynonym, 'TODO: Handle synonym files' + pathInfo.path); // TODO: handle multiple platforms
const fd = await async.fs.open(_path.default.join(this.repositoryPath, `${this.idPrefix}0000.win32.dat${entry.dataFileId}`), 'r');
const fileInfoSize = _parser.fileInfoParser.sizeOf();
const {
buffer: fileInfoBuffer
} = await asyncReadBuffer(fd, fileInfoSize, entry.offset);
const fileInfo = _parser.fileInfoParser.parse(fileInfoBuffer);
(0, _utilities.assert)(fileInfo.type === _parser.FileType.STANDARD, 'TODO: Better handling for file types');
const blockInfoSize = _parser.blockInfoParser.sizeOf();
const blockInfoGroupSize = blockInfoSize * fileInfo.blockCount;
const {
buffer: blockInfoBuffer
} = await asyncReadBuffer(fd, blockInfoGroupSize, entry.offset + fileInfoSize);
const blockInfos = [];
for (let i = 0; i < fileInfo.blockCount; i++) {
const begin = blockInfoSize * i;
blockInfos.push(_parser.blockInfoParser.parse(blockInfoBuffer.subarray(begin, begin + blockInfoSize)));
}
const blockPromises = (0, _map.default)(blockInfos).call(blockInfos, blockInfo => this.readBlock(blockInfo, fd, entry.offset + fileInfo.size));
const blocks = await _promise.default.all(blockPromises);
const fileBuffer = (0, _concat.default)(Buffer).call(Buffer, blocks, fileInfo.rawFileSize); // TODO: Cache FDs?
// Close the FD - no await as timing of close is irrelevant
async.fs.close(fd);
return fileBuffer;
}
async readBlock(blockInfo, fd, baseOffset) {
const {
buffer: blockBuffer
} = await asyncReadBuffer(fd, blockInfo.size, baseOffset + blockInfo.offset);
const blockHeader = _parser.blockHeaderParser.parse(blockBuffer);
const blockData = blockBuffer.subarray(blockHeader.size, blockHeader.size + blockHeader.compressedSize); // If it's compressed, handle it as such
if (blockHeader.compressedSize !== 32000) {
return async.zlib.inflateRaw(blockData);
}
return _promise.default.resolve(blockData);
}
}
exports.Category = Category;