PDA

View Full Version : Uncommon file format .SPR - Store sequencial sprites.


LightTenshimaru
November 28th, 2014, 15:57
Hi everyone.
I have a lot of files with extension ".spr" who are compressed in a unique form unlike common .spr files.
Someone in another forum found a little about its structure.

This .spr files stores somes sequential images whos turn into a animation with the second file. Extension .ani

He found:


That's how I think the file is structured ...

npc_priz_card.spr

Code:

(11 bytes) Signature. ("DCSPRITE10" 00)
(4 bytes) Number of elements in the next 2 tables. (0x98) Maybe number of images.
Start of table 1:
(4 bytes) Width for the first image? (0x00000140 = 320)
... (Repeat for every element in the table. 0x98 times.)
Start of table 2:
(4 bytes) Height for the first image? (0x000000F0 = 240)
... (Repeat for every element in the table. 0x98 times.)
(4 bytes) Multiply by 2 to get pointer to a second structure. (*)
Array of pointers to images (0x98 elements):
(4 bytes) Multiply by 2 to get pointer to first image. (*)
... (Repeat for every element in the table. 0x98 times.)
Start of first image: (Note this is the base offset for the above pointers marked with *.)
... (Here come the images.)
Second structure: (I haven't looked too much into it, but it seems similar to the above structure.)


With the example file, the first image starts at offset 0x00000733 and that address is the base for the pointers.
It seem images start with F0 00 00 00 and are followed by 0x74 bytes with value 0x00. (This is for the few ones I looked into, so I may be wrong.)
After this come the compressed data. (For the first image in the example file it's offset 0x000007AB.)
Data compression is as follows:


Code:
(2 bytes) Number of sub-blocks for first scan line
Sub-block #1:
(2 bytes) Number of 0x0000 that should be injected.
(2 bytes) Number of half-words (each one a pixel in 16bpp format) that follow and are supposed to be injected as they are.
Pixels of sub-block:
(2 bytes) 16bpp color for the pixel.
... (Repeat for every pixel.)
Sub-block #2:
Sub-block #3:
... (Repeat for each sub-block.)
Scan line #2:
Scan line #3:
... (Repeat for every scan line.)


The general structure for SPRs is:

Code:
- Signature.
- Image count.
- Widths for all the images. (Array from 0 To image_count-1)
- Heights for all the images. (Array 0 To image_count-1)
- Pointer to second table ("table 1".
- First table ("table 0":
- Pointers to images. (Array from 0 To image_count-1)
- Collection of compressed images. (image_count images.)
- Pointer to end of secod table or third table?
- Second table ("table 1":
- Pointers to images. (Array from 0 To image_count-1)
- Collection of compressed images. (image_count images.)



Now, the trick to work with those files is to know where each element starts, its size and the encoding.


Code:
a) The signature seems to be the same in all the files you posted:
- It starts at offset 0.
- Its size is 11 bytes (including the 0x00).
- The encoding doesn't matter, but in ASCII it's the string "DCSPRITE10" followed by the byte 00.
b) The image count tells the total number of images that are stored inside the SPR file.
- It starts at offset 11. (Because the signature is fixed.)
- Its size is 4 bytes.
- It's the representation of a 32-bit integer.
c) The widths for the images are an array of integer with the widths in pixels.
- It starts at offset 15.
- Its size is '4*image_count' because each element is 4 bytes long.
- Each element represents a 32-bit integer.
d) The heights for the images are an array of integers with the heights in pixels.
- It starts at offset '15+4*image_count'.
- Its size is '4*image_count' because each element is 4 bytes long.
- Each element represents a 32-bit integer.
e) The pointer to the second table tells where in the file the pointers to the second group of compressed images start.
- It starts at offset '15+4*image_count*2'.
- Its size is 4 bytes.
- It's the representation of a 32-bit integer.
Note: This is not a direct offset in the file, but it has to be multiplied by 2 and added to a base offset instead. (* See below.)
f) The first table of pointers tells where each image from the first table can be found in the file.
- It starts at offset '15+4*image_count*2+4'
- Its size is '4*image_count' as each element is 4 bytes long.
- Each element represents a 32-bit integer.
Note: Each element is not a direct offset in the file, but they have to be multiplied by 2 and added to a base offset instead. (* See below.)
g) The collection of compressed images for the first table has all the images from the first table in compressed form.
- It starts at offset '15+4*image_count*2+4+4*image_count'.
- Its size is variable and it depends on the size of the images and the compression obtained.
- Each element is a compressed image and is described somewhere else.
h) Not sure how to interpret the pointer to the end of the second table (or third table).
- It's start is determined by the pointer explained in (e).
- Its size is 4 bytes.
- It's the representation of a 32-bit integer.
Note: This is not a direct offset in the file, but it has to be multiplied by 2 and added to a base offset instead. (** See below.)
i) The first table of pointers tells where each image from the second table can be found in the file.
- Its start is determined by adding 4 to the offset of (h).
- Its size is '4*image_count' as each element is 4 bytes long.
- Each element represents a 32-bit integer.
Note: Each element is not a direct offset in the file, but they have to be multiplied by 2 and added to a base offset instead. (** See below.)
j) The collection of compressed images for the second table has all the images from the second table in compressed form.
- It starts '4+4*image_count' bytes after the start of the array seen in (h).
- Its size is variable and it depends on the size of the images and the compression obtained.
- Each element is a compressed image and is described somewhere else.

(*) As the size of the data inside the images is always a multiple of 2 bytes, the pointers seem to be expressed as half-words (16 bits) instead of bytes, so they have to be multiplied by 2.
These pointers (multiplied by 2) are relative to a base offset. In the case of the pointers described in (e) and (f), the base offset is the start of (g).
(**) Similar to (*), but the base offset for the pointers in (h) and (i) is the start of (j).


----------------

I would like someone to help me build a tool that can extract and insert images within these .spr files.
Or even create a valid .spr.

Some files will be attached for analysis.
2956

blabberer
December 12th, 2014, 02:00
That's how I think the file is structured ...

confirming that should be trivial do you know / or have reversed / or can explain the compression algo used ? that would be the major task splitting this spr file into constituent parts shouldn't be a hurdle.

Code:

#include <stdio.h>
#include <Windows.h>
unsigned long rwulong(FILE *fp,fpos_t pos,int len) {
unsigned long buff = 0;
fpos_t curpos;
fpos_t newpos = pos;
fgetpos(fp,&curpos);
fsetpos(fp,&newpos);
fread(&buff,sizeof(unsigned long),len,fp);
fsetpos(fp,&curpos);
return buff;
}
int main(int argc, char * argv[]) {
if (argc !=2) {printf("%s <full path to spr file>\n",argv[0]); return 0;}
FILE *fp = NULL;
errno_t err;
err = fopen_s(&fp,argv[1],"rb";
if (err == 0 && fp ) {
unsigned long noofimg = rwulong(fp,11,1);
unsigned long ptrto2ndtable = (( rwulong(fp,(15+noofimg*8),1) *2 ) + (noofimg*12) + 15 + 4 );
unsigned long secondtabhdr = rwulong(fp,ptrto2ndtable,1);
printf("no of images = 0x%x\nptr to second table = 0x%x contains 0x%x\n",noofimg,ptrto2ndtable,secondtabhdr);
unsigned long *posit = (unsigned long *)malloc(noofimg*sizeof(unsigned long));
if (posit != 0 ) {
for(unsigned long i =0; i< noofimg;i++) {
unsigned long width = ((rwulong(fp,( 15 + (i*4) + (noofimg*0) + 0 ),1)));
unsigned long height = ((rwulong(fp,( 15 + (i*4) + (noofimg*4) + 0 ),1)));
posit[I] = ((rwulong(fp,( 15 + (i*4) + (noofimg*8) + 4 ),1) * 2) + 15 + 4 + (noofimg*12 ) );
unsigned long header = rwulong(fp,posit[I],1);
printf("image %3d is%4d W x%4d H located at %8x %4d\n",i,width,height,posit[I],header);
}
free(posit);
}
fclose(fp);
}
return 0;
}


the first compressed thingie lies between a3 and 0x13ddc (both inclusive) and so on in the output below
haven't checked the second table but this seems to be valid for the first table on all the three files

Code:

:\>dir /b
dumpspr.cpp

:\>cl /nologo /W4 dumpspr.cpp /link /RELEASE
dumpspr.cpp

:\>dir /b
dumpspr.cpp
dumpspr.exe
dumpspr.obj

:\>dumpspr.exe
dumpspr.exe <full path to spr file>

:\>dumpspr.exe c:\SPR\NPC_Pocha.spr
no of images = 0xc
ptr to second table = 0xee35b contains 0x0
image 0 is 420 W x 420 H located at a3 420
image 1 is 420 W x 420 H located at 13ddd 420
image 2 is 420 W x 420 H located at 27d95 420
image 3 is 420 W x 420 H located at 3c049 420
image 4 is 420 W x 420 H located at 5029f 420
image 5 is 420 W x 420 H located at 643f1 420
image 6 is 420 W x 420 H located at 782e3 420
image 7 is 420 W x 420 H located at 8becf 420
image 8 is 420 W x 420 H located at 9f95f 420
image 9 is 420 W x 420 H located at b381d 420
image 10 is 420 W x 420 H located at c738d 420
image 11 is 420 W x 420 H located at dabef 420

:\>


LightTenshimaru
December 12th, 2014, 07:10
Thanks for the help friend. This code can help a lot on our cause.

I have a tool that can only read these .spr files, but it can't extract or insert.
I will attach it, maybe can help.

2967

blabberer
December 16th, 2014, 01:39
that is a nice viewer and has a wealth of debug spew hidden inside it and you can simply watch it decompressing the image.if you patch the exe to enable the Trace Messages
if you read msdn docs about BITMAP you can understand it is simply a bottom up compressed format
for example rpc_pocha.spr first frame is between offset a3 to 13ddd = so length = 13ddd - a3 = 13d3a
at a5 you have no of scanlines = 1a4
each consecutive words denote one scan line
if it is 00 00 it is an empty scan line to be filled with 0 for the width of entire scanline
if it is 01 00 it contains 1 sub block within this scanline
if it is 02 00 it means two subblocks etc etc
the first word after the subblock is the number of 00 to be filled in the scan line
so 01 00 30 00 means 30 00 to be filled
the second word is no of compressed pixels to be decompressed and written as it is
so 01 30 02 00 ff ff ff ff means 30 00 two decompressed ff and and scanline width - 32 00 to be filled up to line width
for example you can notice at end of first frame bytes like these

Code:
Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

00013C90 01 00 FC 00 01 00 ..ü...
00013CA0 05 3C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 .<..............
00013CB0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00013CC0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00013CD0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00013CE0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00013CF0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00013D00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00013D10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00013D20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00013D30 00 00 00 00 00 00 00 00 00 00 ..........




so in the decompressed bmp 4c lines are just filled with 00 so 4c * bitmapwidth * 3 = 0x17610 then the scanline 4d has

01 00 FC 00 01 00 05 3C
so fc 00 and then 1 pixel decompressed from 05 3c

so the first pixel in dumped bmp will be at 17610 + BmpFileHeader->bmpbitinfo == 0x36 + fc *3 ==2f4 == 1793A

Code:
Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17

00017928 28 80 38 00 00 00 (€8...
00017940 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........................



53C was decompressed to 28 80 38 and so on

btw if just ripping the bmp's is the only requirement it is dead easy to rip them off from the viewer
simply set a BreakPoint at gdi32!SetDiBits() parse the arguments and piece the FileHeader, InfoHeader and BmpBits to a valid BMP file
any debugger that is scriptable can be used to dump all the Frames on every SPR 2968

LightTenshimaru
December 16th, 2014, 06:43
Thanks for the help again, you are saving much time from me.
One last thing, if you know how to insert the extracted BMP back to .SPR will solve my problem completely.

Dump is no more a problem (thanks to you), but insert an edited BMP to the original .SPR file appears to be a problem to me.