rw-parser
Version:
Parses RenderWare DFF and TXD files into usable format!
235 lines (194 loc) • 7.93 kB
text/typescript
import { RwFile } from '../RwFile';
import { ImageDecoder } from '../utils/ImageDecoder'
import {
D3DFormat,
PaletteType,
PlatformType,
RasterFormat
} from '../utils/ImageFormatEnums'
export interface RwTxd {
textureDictionary: RwTextureDictionary,
}
export interface RwTextureDictionary {
textureCount: number,
textureNatives: RwTextureNative[],
}
export interface RwTextureNative {
platformId: number,
filterMode: number,
uAddressing: number,
vAddressing: number,
textureName: string,
maskName: string,
rasterFormat: number,
d3dFormat: string,
width: number,
height: number,
depth: number,
mipmapCount: number,
rasterType: number,
alpha: boolean,
cubeTexture: boolean,
autoMipMaps: boolean,
compressed: boolean,
mipmaps: number[][],
}
export class TxdParser extends RwFile {
constructor(stream: Buffer) {
super(stream);
}
parse(): RwTxd {
return {
textureDictionary: this.readTextureDictionary(),
};
}
public readTextureDictionary(): RwTextureDictionary {
this.readSectionHeader();
this.readSectionHeader();
const textureCount = this.readUint16();
this.skip(2);
let textureNatives: RwTextureNative[] = [];
for (let i = 0; i < textureCount; i++) {
let textureNative = this.readTextureNative();
textureNatives.push(textureNative);
}
// Skip unused extension
this.skip(this.readSectionHeader().sectionSize);
return { textureCount, textureNatives };
}
public readTextureNative() : RwTextureNative {
this.readSectionHeader();
this.readSectionHeader();
// TODO: Structure this part better
// Texture format
const platformId = this.readUint32();
const flags = this.readUint32();
const filterMode = (flags & 0xFF);
const uAddressing = (flags & 0xF00) >> 8;
const vAddressing = (flags & 0xF000) >> 12;
const textureName = this.readString(32);
const maskName = this.readString(32);
// Raster format
const rasterFormat = this.readUint32();
const d3dFormat = this.readString(4);
const width = this.readUint16();
const height = this.readUint16();
const depth = this.readUint8();
const mipmapCount = this.readUint8();
const rasterType = this.readUint8();
const compressionFlags = this.readUint8(); // Is "dxtType" for III/VC
// SA
const alpha = (compressionFlags & (1 << 0)) !== 0;
const cubeTexture = (compressionFlags & (1 << 1)) !== 0;
const autoMipMaps = (compressionFlags & (1 << 2)) !== 0;
const compressed = (compressionFlags & (1 << 3)) !== 0;
const paletteType = (rasterFormat >> 13) & 0b11;
let mipWidth = width;
let mipHeight = height;
let mipmaps: number[][] = [];
const palette = (paletteType !== PaletteType.PALETTE_NONE ? this.readPalette(paletteType, depth) : new Uint8Array(0));
for (let i = 0; i < mipmapCount; i++) {
const rasterSize = this.readUint32();
const raster = this.read(rasterSize);
if (i == 0) {
// Raw RGBA presentation
let bitmap: number[];
if (palette.length !== 0) {
const rasterFormatsWithoutAlpha = [
RasterFormat.RASTER_565,
RasterFormat.RASTER_LUM,
RasterFormat.RASTER_888,
RasterFormat.RASTER_555
];
const hasAlpha = ((platformId === PlatformType.D3D9 && alpha) || (platformId == PlatformType.D3D8 && !rasterFormatsWithoutAlpha.includes(rasterFormat)));
bitmap = Array.from(this.getBitmapWithPalette(paletteType, depth, hasAlpha, raster, palette, width, height));
}
else if (platformId === PlatformType.D3D8 && compressionFlags !== 0) {
bitmap = Array.from(this.getBitmapWithDXT('DXT' + compressionFlags, raster, width, height));
}
else if (platformId === PlatformType.D3D9 && compressed) {
bitmap = Array.from(this.getBitmapWithDXT(d3dFormat, raster, width, height));
}
else {
bitmap = Array.from(this.getBitmapWithRasterFormat(rasterFormat, raster, width, height))
}
mipmaps.push(bitmap);
}
mipWidth /= 2;
mipHeight /= 2;
}
// Skip extension
this.skip(this.readSectionHeader().sectionSize);
return {
platformId,
filterMode,
uAddressing, vAddressing,
textureName, maskName,
rasterFormat,
d3dFormat,
width, height, depth,
mipmapCount,
rasterType,
alpha,
cubeTexture,
autoMipMaps,
compressed,
mipmaps,
};
}
public readPalette(paletteType: number, depth: number): Uint8Array {
const size = (paletteType === PaletteType.PALETTE_8 ? 1024 : (depth === 4 ? 64 : 128))
return this.read(size);
}
public getBitmapWithPalette(paletteType: number, depth: number, hasAlpha: boolean, raster: Uint8Array, palette: Uint8Array, width: number, height: number): Uint8Array {
if (paletteType !== PaletteType.PALETTE_8 && depth == 4) {
return (hasAlpha
? ImageDecoder.pal4(raster, palette, width, height)
: ImageDecoder.pal4NoAlpha(raster, palette, width, height)
);
}
return (hasAlpha
? ImageDecoder.pal8(raster, palette, width, height)
: ImageDecoder.pal8NoAlpha(raster, palette, width, height)
)
}
public getBitmapWithDXT(dxtType:string, raster: Uint8Array, width: number, height: number): Uint8Array {
switch (dxtType) {
case D3DFormat.D3D_DXT1:
return ImageDecoder.bc1(raster, width, height);
case D3DFormat.D3D_DXT2:
return ImageDecoder.bc2(raster, width, height, true);
case D3DFormat.D3D_DXT3:
return ImageDecoder.bc2(raster, width, height, false);
case D3DFormat.D3D_DXT4:
return ImageDecoder.bc3(raster, width, height, true);
case D3DFormat.D3D_DXT5:
return ImageDecoder.bc3(raster, width, height, false);
// LUM8_A8 has compressed flag
case D3DFormat.D3DFMT_A8L8:
return ImageDecoder.lum8a8(raster, width, height);
default:
return new Uint8Array(0);
}
}
public getBitmapWithRasterFormat (rasterFormat: number, raster: Uint8Array, width: number, height: number): Uint8Array {
switch (rasterFormat) {
case RasterFormat.RASTER_1555:
return ImageDecoder.bgra1555(raster, width, height);
case RasterFormat.RASTER_565:
return ImageDecoder.bgra565(raster, width, height);
case RasterFormat.RASTER_4444:
return ImageDecoder.bgra4444(raster, width, height);
case RasterFormat.RASTER_LUM:
return ImageDecoder.lum8(raster, width, height);
case RasterFormat.RASTER_8888:
return ImageDecoder.bgra8888(raster, width, height);
case RasterFormat.RASTER_888:
return ImageDecoder.bgra888(raster, width, height);
case RasterFormat.RASTER_555:
return ImageDecoder.bgra555(raster, width, height);
default:
return new Uint8Array(0);
}
}
}