mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 12:09:51 +02:00
feat: dds decompression
This commit is contained in:
1 parent
6361b09129
commit
4ffb7cac7c
6 files changed
+164
-17
No files matched your search
@@ -519,9 +519,9 @@ if (allTargets || targets?.includes('tankModels')) {
|
|||||||
const nationVehicleId = tank.id;
|
const nationVehicleId = tank.id;
|
||||||
const id = (nationVehicleId << 8) + (NATION_IDS[nation] << 4) + 1;
|
const id = (nationVehicleId << 8) + (NATION_IDS[nation] << 4) + 1;
|
||||||
|
|
||||||
// if (id !== 15697) continue; // chieftain TODO: investigate vertices stream over read
|
if (id !== 15697) continue; // chieftain TODO: investigate vertices stream over read
|
||||||
// if (id !== 4417) continue; // amx m4 mle
|
// if (id !== 4417) continue; // amx m4 mle
|
||||||
if (id !== 7297) continue; // 60tp
|
// if (id !== 7297) continue; // 60tp
|
||||||
console.log(`Building model ${id} @ ${nation}/${tankIndex}`);
|
console.log(`Building model ${id} @ ${nation}/${tankIndex}`);
|
||||||
|
|
||||||
const parameters = await readYAMLDVPL<TankParameters>(
|
const parameters = await readYAMLDVPL<TankParameters>(
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import { times } from 'lodash';
|
||||||
|
import { Vector4Tuple } from 'three';
|
||||||
|
import { BinaryStream } from './binary';
|
||||||
|
|
||||||
|
const MAX_UINT5 = 2 ** 5 - 1;
|
||||||
|
const MAX_UINT6 = 2 ** 6 - 1;
|
||||||
|
|
||||||
|
type InterpolationKey = 0b00 | 0b01 | 0b10 | 0b11;
|
||||||
|
|
||||||
|
export class Bc1Stream extends BinaryStream {
|
||||||
|
color() {
|
||||||
|
const r = this.consume(5) / MAX_UINT5;
|
||||||
|
const g = this.consume(6) / MAX_UINT6;
|
||||||
|
const b = this.consume(5) / MAX_UINT5;
|
||||||
|
|
||||||
|
return [r, g, b, 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
interpolation() {
|
||||||
|
const color0 = this.color();
|
||||||
|
const color1 = this.color();
|
||||||
|
const color2 = color0.map(
|
||||||
|
(channel0, index) => (channel0 + color1[index]) / 2,
|
||||||
|
);
|
||||||
|
const color3 = [...color2.slice(0, -1), 0];
|
||||||
|
|
||||||
|
return {
|
||||||
|
[0b00]: color0,
|
||||||
|
[0b01]: color1,
|
||||||
|
[0b10]: color2,
|
||||||
|
[0b11]: color3,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
blockFloat() {
|
||||||
|
const interpolation = this.interpolation();
|
||||||
|
return times(16, () => interpolation[this.consume(2) as InterpolationKey]);
|
||||||
|
}
|
||||||
|
|
||||||
|
block() {
|
||||||
|
return this.blockFloat().map(
|
||||||
|
(pixel) =>
|
||||||
|
pixel.map((channel) => Math.round(channel * 255)) as Vector4Tuple,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
blocks(count: number) {
|
||||||
|
return times(count, () => this.block());
|
||||||
|
}
|
||||||
|
|
||||||
|
bc1(width: number, height: number) {
|
||||||
|
if (width % 4 !== 0 || height % 4 !== 0) {
|
||||||
|
throw new RangeError('Width and height must be divisible by 4');
|
||||||
|
}
|
||||||
|
|
||||||
|
const count = (width / 4) * (height / 4);
|
||||||
|
const blocks = this.blocks(count);
|
||||||
|
const image = Buffer.alloc(width * height * 4);
|
||||||
|
let pixelIndex = 0;
|
||||||
|
|
||||||
|
for (let y = 0; y < width; y++) {
|
||||||
|
const yOffset = Math.floor(y / 4) * (width / 4);
|
||||||
|
|
||||||
|
for (let x = 0; x < height; x++) {
|
||||||
|
const xOffset = Math.floor(x / 4);
|
||||||
|
const block = blocks[yOffset + xOffset];
|
||||||
|
const pixel = block[Math.floor(y % 4) * 4 + Math.floor(x % 4)];
|
||||||
|
|
||||||
|
pixel.forEach((channel) => (image[pixelIndex++] = channel));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
type Bit = 0 | 1;
|
||||||
|
|
||||||
|
export class BinaryStream {
|
||||||
|
public index = 0;
|
||||||
|
bits: Bit[] = [];
|
||||||
|
|
||||||
|
constructor(buffer: Buffer) {
|
||||||
|
for (let bufferIndex = 0; bufferIndex < buffer.length; bufferIndex++) {
|
||||||
|
const byte = buffer[bufferIndex];
|
||||||
|
|
||||||
|
for (let byteIndex = 7; byteIndex >= 0; byteIndex--) {
|
||||||
|
this.bits.push(((byte >> byteIndex) & 1) as Bit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
skip(size: number) {
|
||||||
|
this.index += size;
|
||||||
|
}
|
||||||
|
read(size: number) {
|
||||||
|
if (this.index + size > this.bits.length) {
|
||||||
|
throw new RangeError('Cannot read beyond end of stream');
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.bits
|
||||||
|
.slice(this.index, this.index + size)
|
||||||
|
.reduce((accumulator, bit) => (accumulator << 1) | bit, 0 as number);
|
||||||
|
}
|
||||||
|
consume(size: number) {
|
||||||
|
const value = this.read(size);
|
||||||
|
this.skip(size);
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
+47
-10
@@ -1,5 +1,8 @@
|
|||||||
|
import { writeFileSync } from 'fs';
|
||||||
import { times } from 'lodash';
|
import { times } from 'lodash';
|
||||||
|
import sharp from 'sharp';
|
||||||
import { readDVPLFile } from '../blitz/readDVPLFile';
|
import { readDVPLFile } from '../blitz/readDVPLFile';
|
||||||
|
import { Bc1Stream } from './bc1';
|
||||||
import { WindowsStream } from './windows';
|
import { WindowsStream } from './windows';
|
||||||
|
|
||||||
enum DxgiFormat {
|
enum DxgiFormat {
|
||||||
@@ -127,7 +130,7 @@ enum DxgiFormat {
|
|||||||
FORCE_UINT = 0xffffffff,
|
FORCE_UINT = 0xffffffff,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum D3d10ResourceDimension {
|
enum DdsDimension {
|
||||||
UNKNOWN = 0,
|
UNKNOWN = 0,
|
||||||
BUFFER = 1,
|
BUFFER = 1,
|
||||||
TEXTURE1D = 2,
|
TEXTURE1D = 2,
|
||||||
@@ -172,13 +175,48 @@ enum DdsCaps2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class DdsStream extends WindowsStream {
|
export class DdsStream extends WindowsStream {
|
||||||
dds() {
|
async dds() {
|
||||||
const magicNumber = this.magicNumber();
|
this.magicNumber();
|
||||||
const header = this.header();
|
const header = this.header();
|
||||||
const headerDxt10 =
|
|
||||||
header.pf.flags === DdpfFlags.FOURCC && header.pf.fourCC === 0x30315844
|
if (
|
||||||
? this.headerDxt10()
|
header.pf.flags !== DdpfFlags.FOURCC ||
|
||||||
: undefined;
|
header.pf.fourCC !== 0x30315844 // "DX10"
|
||||||
|
) {
|
||||||
|
throw new RangeError('No other formats other than DX10 are supported');
|
||||||
|
}
|
||||||
|
|
||||||
|
const headerDxt10 = this.headerDxt10();
|
||||||
|
|
||||||
|
switch (headerDxt10.dxgiFormat) {
|
||||||
|
case DxgiFormat.BC1_UNORM_SRGB: {
|
||||||
|
const image = new Bc1Stream(
|
||||||
|
this.consume((header.width / 4) * (header.height / 4) * 8),
|
||||||
|
).bc1(header.width, header.height);
|
||||||
|
|
||||||
|
writeFileSync(
|
||||||
|
'test.png',
|
||||||
|
await sharp(image, {
|
||||||
|
raw: {
|
||||||
|
channels: 4,
|
||||||
|
width: header.width,
|
||||||
|
height: header.height,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.png()
|
||||||
|
.toBuffer(),
|
||||||
|
);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new TypeError(
|
||||||
|
`Unsupported DXGI format: ${DxgiFormat[headerDxt10.dxgiFormat]} (${
|
||||||
|
headerDxt10.dxgiFormat
|
||||||
|
})`,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
magicNumber() {
|
magicNumber() {
|
||||||
@@ -201,7 +239,6 @@ export class DdsStream extends WindowsStream {
|
|||||||
caps3: this.dword(),
|
caps3: this.dword(),
|
||||||
caps4: this.dword(),
|
caps4: this.dword(),
|
||||||
reserved2: times(1, () => this.dword()),
|
reserved2: times(1, () => this.dword()),
|
||||||
headerDxt10: undefined,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,7 +258,7 @@ export class DdsStream extends WindowsStream {
|
|||||||
headerDxt10() {
|
headerDxt10() {
|
||||||
return {
|
return {
|
||||||
dxgiFormat: this.uint() as DxgiFormat,
|
dxgiFormat: this.uint() as DxgiFormat,
|
||||||
resourceDimension: this.uint() as D3d10ResourceDimension,
|
resourceDimension: this.uint() as DdsDimension,
|
||||||
miscFlag: this.uint(),
|
miscFlag: this.uint(),
|
||||||
arraySize: this.uint(),
|
arraySize: this.uint(),
|
||||||
miscFlags2: this.uint(),
|
miscFlags2: this.uint(),
|
||||||
@@ -231,6 +268,6 @@ export class DdsStream extends WindowsStream {
|
|||||||
|
|
||||||
new DdsStream(
|
new DdsStream(
|
||||||
await readDVPLFile(
|
await readDVPLFile(
|
||||||
'C:/Program Files (x86)/Steam/steamapps/common/World of Tanks Blitz/Data/3d/Tanks/German/images_pbr/E_100_BC.dx11.dds.dvpl',
|
'C:/Program Files (x86)/Steam/steamapps/common/World of Tanks Blitz/Data/3d/Tanks/GB/images_pbr/Chieftain_Mk6_BC.dx11.dds.dvpl',
|
||||||
),
|
),
|
||||||
).dds();
|
).dds();
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
export class PrimitiveStream {
|
export class PrimitiveStream {
|
||||||
public index = 0;
|
public index = 0;
|
||||||
constructor(public buffer: Buffer) {}
|
constructor(public stream: Buffer) {}
|
||||||
|
|
||||||
skip(size: number) {
|
skip(size: number) {
|
||||||
this.index += size;
|
this.index += size;
|
||||||
}
|
}
|
||||||
read(size: number) {
|
read(size: number) {
|
||||||
return this.buffer.subarray(this.index, this.index + size);
|
return this.stream.subarray(this.index, this.index + size);
|
||||||
}
|
}
|
||||||
consume(size: number) {
|
consume(size: number) {
|
||||||
const subarray = this.read(size);
|
const subarray = this.read(size);
|
||||||
@@ -19,7 +19,7 @@ export class PrimitiveStream {
|
|||||||
return this.read(Number.POSITIVE_INFINITY);
|
return this.read(Number.POSITIVE_INFINITY);
|
||||||
}
|
}
|
||||||
readRemainingLength() {
|
readRemainingLength() {
|
||||||
return this.buffer.length - this.index;
|
return this.stream.length - this.index;
|
||||||
}
|
}
|
||||||
consumeRemaining() {
|
consumeRemaining() {
|
||||||
return this.consume(Number.POSITIVE_INFINITY);
|
return this.consume(Number.POSITIVE_INFINITY);
|
||||||
|
|||||||
@@ -84,9 +84,9 @@ export class ScgStream extends ScpgStream {
|
|||||||
ScgStream.vertexStride(polygonGroupRaw.vertexFormat) *
|
ScgStream.vertexStride(polygonGroupRaw.vertexFormat) *
|
||||||
polygonGroupRaw.vertexCount;
|
polygonGroupRaw.vertexCount;
|
||||||
|
|
||||||
if (verticesStream.buffer.length !== stride) {
|
if (verticesStream.stream.length !== stride) {
|
||||||
console.warn(
|
console.warn(
|
||||||
`Vertex stride mismatch; expected ${stride}, got ${verticesStream.buffer.length}`,
|
`Vertex stride mismatch; expected ${stride}, got ${verticesStream.stream.length}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|||||||
Reference in new issue
Block a user