thread: PSX .CAM file
View Single Post
  #1  
12-21-2013, 12:45 PM
m-35's Avatar
m-35
Zappfly
 
: Dec 2013
: At home
: 1
Rep Power: 0
m-35  (10)
PSX .CAM file

Noticed some discussion here and more recently here that .CAM files contain PlayStation MDEC data. Did some poking around and managed to get the images out. Here's a quick Python script (uses jPSXdec for the heavy lifting).

:
"""
Extract PlayStation MDEC bitstreams from an Oddworld PlayStation .CAM file
(found in .LVL files) and converts those bitstreams to images with jPSXdec.
The resulting images will need to be stitched together for form a full frame.
Note that there is other data in .CAM files that this tool ignores.

Usage:
python cam2bs.py cam-file
"""

import sys, os, struct

# Customize the jPSXdec command with quality and output options (see manual)
JPSXDEC_COMMAND = "java -jar jpsxdec.jar -f {0} -static bs -dim {1}x{2}"

class CamFramePiece:
    '''
    One piece of a .CAM file frame.
    All pieces are 240 pixels high, and all but the
    last piece is 32 pixels wide.
    The last piece is 16 pixels wide.
    The piece format is simple:
        4 bytes (little-endian): size of the bitstream data
        bitstream data
    '''

    def __init__(self, iPiece, fileStream):
        self.iPiece = iPiece
        self.iFilePos = fileStream.tell()
        sbinPieceLen = fileStream.read(4)
        self.iPieceLen, = struct.unpack('i', sbinPieceLen)
        if self.iPieceLen <= 0:
            raise Exception('BAD: %s frame len <= 0' % self)
        self.bitstreamBytes = fileStream.read(self.iPieceLen)
        iMarker00, iMarker38 = struct.unpack('xxBB', self.bitstreamBytes[0:4])
        if iMarker00 != 0x00 or iMarker38 != 0x38:
            raise Exception('BAD: %s missing 0x0038' % self)

    def savePiece(self, sSrcFile):
        if self.iPiece == 11:
            self.iWidth = 16
        else:
            self.iWidth = 32
        self.sFileName = '%s_%02d_%dx%d.bs' % (sSrcFile, self.iPiece, self.iWidth, 240)
        print 'Saving %s piece %d as %s' % (sSrcFile, self.iPiece, self.sFileName)
        with open(self.sFileName, 'wb') as f:
            f.write(self.bitstreamBytes)

    def decodePiece(self):
        print 'Converting %s to image with jPSXdec' % self.sFileName
        sCmd = JPSXDEC_COMMAND.format(self.sFileName, self.iWidth, 240)
        print sCmd
        os.system(sCmd)

    def __str__(self):
        return '#%d @%d: Len=%d' % (self.iPiece, self.iFilePos, self.iPieceLen)
    def __repr__(self):
        return self.__str__()

class CamFile:
    '''
    Oddworld .CAM file.
    Contains 12 separate images (CamFramePiece).
    When combined together, forms a 368x240 frame.
    The header is unknown, and there are quite a few bytes at the end of the file that are unknown.
        8 bytes: unknown
        4 bytes (big-endian): string "Bits"
        4 bytes: unknown
        body (variable size): 12 frame pieces (see CamFramePiece)
        remaining (variable size): unknown
    '''
    def __init__(self, sFile):
        print 'Extracting bitstream pieces from %s' % sFile
        with open(sFile, 'rb') as fileStream:
            self.sSrcFile = sFile
            self.lstPieces = []
            fileStream.read(8) # unknown 8 bytes
            sBits = fileStream.read(4)
            if sBits != 'Bits':
                raise Exception(sBits+' != Bits')
            fileStream.read(4) # unknown 4 bytes
            for iPiece in xrange(12):
                self.lstPieces.append(CamFramePiece(iPiece, fileStream))

    def saveBitstreams(self):
        for piece in self.lstPieces:
            piece.savePiece(self.sSrcFile)

    def decodeBitstreams(self):
        for piece in self.lstPieces:
            piece.decodePiece()
            print

    def printInfo(self):
        for piece in self.lstPieces:
            print piece
            

def main(lstArgs):
    camFile = CamFile(lstArgs[0])
    camFile.printInfo()
    print
    camFile.saveBitstreams()
    print
    camFile.decodeBitstreams()
    return 0

if __name__ == '__main__':
    sys.exit(main(sys.argv[1:]))
Reply With Quote