Skip to content
Patrice Mandin edited this page Aug 27, 2021 · 13 revisions

Table of Contents

Games

The .BSS file format is used on Resident Evil game series on Playstation to store the 2D background images. A file holds all different views (camera angles) for the same room.

Structure

Playstation

The file is an archive that contains several compressed images, aligned on 32KB boundaries (for Resident Evil) or 64KB boundaries (Resident Evil 2 and 3).

For Resident Evil 2 and 3, the video stream compressed image (see below) for the background image is followed most likely by a TIM file (lightly compressed, maybe some kind of RLE compression, see below), which should be the masks image for the background.

After this file you'll find the offset in the 64KB block and a counter of extra files, mostly 1. It appears the game engine contains hardcoded offsets to this position to find the mask offset.

Saturn

The file starts with a list of unsigned long offsets, to a number of 512 bytes block.

- TODO -

Decompression

Each image is compressed for use with Playstation MDEC decoder (the chip responsible for video decoding). An extra decompression step in software, to decode variable-length data (VLC), is performed before feeding the data through MDEC processing.

The libbs library from psxdev-libs package provides the two necessary routines/steps to fully depack the images. The MDEC decoding part in this library comes from the PCSX emulator.

The resolution of the image must be known for correct depacking, and these images use standard 320x240 resolution.

RE2 Decompression for TIM masks

The file starts with a LONG with is the uncompressed file length, then an unused WORD. The rest of the file are compressed blocks with an header byte beeing XXXYZZZZ. Bytes following this header are counter or offsets to source or already decompressed file.

void memcpy_overlap(Uint8 *dest, Uint8 *src, int count) {
	int i;

	for (i=0; i<count; i++) {
		dest[i] = src[i];
	}
}

void bsssld_depack_re2(Uint8 *srcPtr, int srcLen, Uint8 **dstBufPtr, int *dstLength)
{
	Uint32 buflen;
	Uint8 *dstPtr;
	int srcPos, dstPos;
	int count;

	buflen = SDL_SwapLE32(*((Uint32 *)srcPtr));

	dstPtr = *dstBufPtr = malloc(buflen);
	memset(*dstBufPtr, 0, buflen);
	*dstLength = buflen;

	srcPos = 6;
	dstPos = 0;
	while ((srcPos<srcLen) && (dstPos<buflen)) {

		while ((srcPtr[srcPos] & 0x10)==0) {
			count = srcPtr[srcPos] & 0x0f;
			int srcOffset = (-256 | (srcPtr[srcPos] & 0xe0))<<3;
			srcOffset |= srcPtr[srcPos+1];
			if (count == 0x0f) {
				count += srcPtr[srcPos+2];
				srcPos += 3;
			} else {
				srcPos += 2;
			}
			count += 3;

			memcpy_overlap(&dstPtr[dstPos], &dstPtr[dstPos+srcOffset], count);
			dstPos += count;
		}

		if (srcPtr[srcPos] == 0xff)
			break;

		count = ((srcPtr[srcPos++] | 0xffe0) ^ 0xffff)+1;
		if (count == 0x10) {
			count += srcPtr[srcPos++];
		}

		memcpy(&dstPtr[dstPos], &srcPtr[srcPos], count);
		dstPos += count;
		srcPos += count;
	}
}

RE3 Decompression for TIM masks

This routine is similar to the one for RE masks, but slightly different. The first LONG is a number of blocks to decompress. Each block has an header byte, with 1 or more following bytes depending on the header byte.

void memcpy_overlap(Uint8 *dest, Uint8 *src, int count) {
	int i;

	for (i=0; i<count; i++) {
		dest[i] = src[i];
	}
}

void bsssld_depack_re3(Uint8 *srcPtr, int srcLen, Uint8 **dstBufPtr, int *dstLength)
{
	Uint8 *dstPtr;
	int srcPos, dstPos, i, numBlocks;
	int count, offset;

	numBlocks = SDL_SwapLE32(*((Uint32 *)srcPtr));

	*dstLength = 65536;
	dstPtr = malloc(*dstLength);
	memset(dstPtr, 0, *dstLength);

	srcPos = 4;
	dstPos = 0;
	for(i=0; (i<numBlocks) && (srcPos<srcLen); i++) {
		if ((srcPtr[srcPos] & 0x80) != 0) {
			count = srcPtr[srcPos++] & 0x7f;

			if (dstPos+count >= *dstLength) {
				*dstLength += 65536;
				dstPtr = (Uint8 *) realloc(dstPtr, *dstLength);
			}

			memcpy(&dstPtr[dstPos], &srcPtr[srcPos], count);
			srcPos += count;
			dstPos += count;
		} else {
			offset = srcPtr[srcPos++]<<8;
			offset |= srcPtr[srcPos++];
			count = (offset>>11)+2;
			offset &= 0x7ff;

			if (dstPos+count >= *dstLength) {
				*dstLength += 65536;
				dstPtr = (Uint8 *) realloc(dstPtr, *dstLength);
			}

			memcpy_overlap(&dstPtr[dstPos], &dstPtr[dstPos-(offset+4)], count);
			dstPos += count;
		}
	}

	*dstLength = dstPos;
	*dstBufPtr = (Uint8 *) realloc(dstPtr, *dstLength);
}

External links