mirror of
https://github.com/dwsJason/gsla.git
synced 2024-11-21 21:31:39 +00:00
WIP on the GSLA file serializer
This commit is contained in:
parent
3a7b5a69fb
commit
89f6eb72e5
272
source/gsla_file.cpp
Normal file
272
source/gsla_file.cpp
Normal file
@ -0,0 +1,272 @@
|
|||||||
|
//
|
||||||
|
// C++ Encoder/Decoder
|
||||||
|
// For GSLA, GS Lzb Animation File Format
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Care is taken in the encoder, to make sure the 65816 does not have to cross
|
||||||
|
// bank boundaries during any copy. This is so we can use the MVN instruction,
|
||||||
|
// and so we can reduce the number of bank checks in the code. We will have an
|
||||||
|
// opcode, that says “source data bank has changed”
|
||||||
|
//
|
||||||
|
// The file will be laid out such that you load the file in at a 64K memory
|
||||||
|
// boundary
|
||||||
|
//
|
||||||
|
// Goals include a good balance between file size, and playback performance
|
||||||
|
// (since one often makes a trade off with the other).
|
||||||
|
//
|
||||||
|
// The file is defined as a byte stream, loaded on a 64K Bank Boundary
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// file-offset: the thing that is at the offset
|
||||||
|
//
|
||||||
|
// Header of the File is 20 bytes as follows
|
||||||
|
//
|
||||||
|
//File Offset Data Commentary
|
||||||
|
//------------------------------------------------------------------
|
||||||
|
//0 0x47 ; ‘G’ Graphics
|
||||||
|
//1 0x53 ; ‘S’
|
||||||
|
//2 0x4C ; ‘L’ LZB
|
||||||
|
//3 0x41 ; ‘A’ Animation
|
||||||
|
//
|
||||||
|
// File Length, is the total length of the file
|
||||||
|
//4 FileLengthLow ; Low byte, 32-bit file length
|
||||||
|
//5 LengthLowHigh ; High byte of low word
|
||||||
|
//6 LengthHighLow ; Low byte, of high word
|
||||||
|
//7 LengthHighHigh ; High Byte, of high word
|
||||||
|
//
|
||||||
|
// 16 bit word with version #
|
||||||
|
//8 VL ; Version # of the file format, currently only version 0 exists
|
||||||
|
//9 VH ; Version High Byte
|
||||||
|
// ; %RVVV_VVVV_VVVV_VVVV
|
||||||
|
// ; V is a version #, 0 for now
|
||||||
|
// ; R is the MSB, R = 0 no ring frame
|
||||||
|
// ; R = 1, there is a ring frame
|
||||||
|
// ; A Ring Frame is a frame that will delta from the last
|
||||||
|
// ; frame of the animation, back to the first, for smoother
|
||||||
|
// ; playback looping , If a ring frame exists, it’s also
|
||||||
|
// ; included in the frame count
|
||||||
|
//
|
||||||
|
// next is a word, width in bytes (likely 160 for now)
|
||||||
|
//0xA WL ; Display Width in bytes low byte
|
||||||
|
//0xB WH ; Display Width in bytes high byte
|
||||||
|
//
|
||||||
|
// next is a word, height (likely 200 for now)
|
||||||
|
//0xC HL ; Display Height in bytes, low byte
|
||||||
|
//0xD HH ; Display Height in bytes, high byte
|
||||||
|
// 2 bytes, Frame Size in Bytes, since a “Frame” may contain more than just the
|
||||||
|
// width * height, worth of pixels, for now this is $8000, or 32768
|
||||||
|
//0xE FBL ; Frame Buffer Length Low
|
||||||
|
//0xF FBH ; Frame Buffer Length High
|
||||||
|
//
|
||||||
|
// 4 byte, 32-bit, Frame Count (includes total frame count, so if there is a ring frame, this is included in the total)
|
||||||
|
//0x10 FrameCountLow
|
||||||
|
//0x11 FrameCountLowHigh
|
||||||
|
//0x12 FrameCountHighLow
|
||||||
|
//0x13 FrameCountHigh
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// After this comes AIFF style chunks of data, basically a 4 byte chunk name,
|
||||||
|
// followed by a 4 byte length (inclusive of the chunk size). The idea is that
|
||||||
|
// you can skip chunks you don’t understand.
|
||||||
|
//
|
||||||
|
//File Offset:
|
||||||
|
//0x14 First Chunk (followed by more Chunks, until end of file)
|
||||||
|
//
|
||||||
|
//Chunk Definitions
|
||||||
|
//Name: ‘INIT’ - Initial Frame Chunk, this is the data used to first initialize the playback buffer
|
||||||
|
//0: 0x49 ‘I’
|
||||||
|
//1: 0x4E ‘N’
|
||||||
|
//2: 0x49 ‘I’
|
||||||
|
//3: 0x54 ‘T’
|
||||||
|
// 32 bit long, length, little endian, including the 8 byte header
|
||||||
|
//4: length low low
|
||||||
|
//5: length low high
|
||||||
|
//6: length high low
|
||||||
|
//7: length high high
|
||||||
|
//
|
||||||
|
//8: …. This is a single frame of data, that decodes/decompresses into frame
|
||||||
|
// sized bytes (right now 0x8000)
|
||||||
|
// This data stream includes, an end of animation opcode, so that the normal
|
||||||
|
// animation decompressor, can be called on this data, and it will emit the
|
||||||
|
// initial frame onto the screen
|
||||||
|
//
|
||||||
|
//Name: ‘ANIM’ - Frames
|
||||||
|
//0: 0x41 ‘A’
|
||||||
|
//1: 0x4E ‘N’
|
||||||
|
//2: 0x49 ‘I’
|
||||||
|
//3: 0x4D ‘M’
|
||||||
|
// 32 bit long, length, little endian, including chunk header
|
||||||
|
//4: length low low
|
||||||
|
//5: length low high
|
||||||
|
//6: length high low
|
||||||
|
//7: length high high
|
||||||
|
//
|
||||||
|
// This is followed by the frames, with the intention of decompressing them at
|
||||||
|
// 60FPS, which is why no play speed is included, if you need a play-rate
|
||||||
|
// slower than this, blank frame’s should be inserted into the animation data
|
||||||
|
//
|
||||||
|
// Every attempt is made to delta encode the image, meaning we just encode
|
||||||
|
// information about what changed each frame. We attempt to make the size
|
||||||
|
// efficient by supporting dictionary copies (where the dictionary is made up
|
||||||
|
// of existing pixels in the frame buffer).
|
||||||
|
//
|
||||||
|
//Command Word, encoded low-high, what the bits mean:
|
||||||
|
//
|
||||||
|
// xxx_xxxx_xxxx_xxx is the number of bytes 1-16384 to follow (0 == 1 byte)
|
||||||
|
//
|
||||||
|
//%0xxx_xxxx_xxxx_xxx0 - Copy Bytes - straight copy bytes
|
||||||
|
//%1xxx_xxxx_xxxx_xxx1 - Skip Bytes - skip bytes / move the cursor
|
||||||
|
//%1xxx_xxxx_xxxx_xxx0 - Dictionary Copy Bytes from frame buffer to frame buffer
|
||||||
|
//
|
||||||
|
//%0000_0000_0000_0001- Source Skip -> Source pointer skips to next bank of data
|
||||||
|
//%0000_0000_0000_0011- End of Frame - end of frame
|
||||||
|
//%0000_0000_0000_0111- End of Animation / End of File / no more frames
|
||||||
|
//
|
||||||
|
// other remaining codes, are reserved for future expansion
|
||||||
|
|
||||||
|
#include "gsla_file.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
// If these structs are the wrong size, there's an issue with type sizes, and
|
||||||
|
// your compiler
|
||||||
|
static_assert(sizeof(GSLA_Header)==20, "GSLA_Header is supposed to be 20 bytes");
|
||||||
|
static_assert(sizeof(GSLA_INIT)==8, "GSLA_INIT is supposed to be 8 bytes");
|
||||||
|
static_assert(sizeof(GSLA_ANIM)==8, "GSLA_ANIM is supposed to be 8 bytes");
|
||||||
|
static_assert(sizeof(GSLA_CHUNK)==8, "GSLA_CHUNK is supposed to be 8 bytes");
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Load in a FanFile constructor
|
||||||
|
//
|
||||||
|
GSLAFile::GSLAFile(const char *pFilePath)
|
||||||
|
: m_widthPixels(320)
|
||||||
|
, m_heightPixels(200)
|
||||||
|
{
|
||||||
|
LoadFromFile(pFilePath);
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
GSLAFile::~GSLAFile()
|
||||||
|
{
|
||||||
|
// Free Up the memory
|
||||||
|
for (int idx = 0; idx < m_pC1PixelMaps.size(); ++idx)
|
||||||
|
{
|
||||||
|
delete[] m_pC1PixelMaps[idx];
|
||||||
|
m_pC1PixelMaps[ idx ] = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void GSLAFile::LoadFromFile(const char* pFilePath)
|
||||||
|
{
|
||||||
|
// Free Up the memory
|
||||||
|
for (int idx = 0; idx < m_pC1PixelMaps.size(); ++idx)
|
||||||
|
{
|
||||||
|
delete[] m_pC1PixelMaps[idx];
|
||||||
|
m_pC1PixelMaps[ idx ] = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pC1PixelMaps.clear();
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
|
||||||
|
std::vector<unsigned char> bytes;
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Read the file into memory
|
||||||
|
FILE* pFile = nullptr;
|
||||||
|
errno_t err = fopen_s(&pFile, pFilePath, "rb");
|
||||||
|
|
||||||
|
if (0==err)
|
||||||
|
{
|
||||||
|
fseek(pFile, 0, SEEK_END);
|
||||||
|
size_t length = ftell(pFile); // get file size
|
||||||
|
fseek(pFile, 0, SEEK_SET);
|
||||||
|
|
||||||
|
bytes.resize( length ); // make sure buffer is large enough
|
||||||
|
|
||||||
|
// Read in the file
|
||||||
|
fread(&bytes[0], sizeof(unsigned char), bytes.size(), pFile);
|
||||||
|
fclose(pFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytes.size())
|
||||||
|
{
|
||||||
|
size_t file_offset = 0; // File Cursor
|
||||||
|
|
||||||
|
// Bytes are in the buffer, so let's start looking at what we have
|
||||||
|
GSLA_Header* pHeader = (GSLA_Header*) &bytes[0];
|
||||||
|
|
||||||
|
// Early out if things don't look right
|
||||||
|
if (!pHeader->IsValid((unsigned int)bytes.size()))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Size in bytes for each frame in this animation
|
||||||
|
m_frameSize = pHeader->frame_size;
|
||||||
|
|
||||||
|
// pre-allocate all the frames
|
||||||
|
for (unsigned int idx = 0; idx < pHeader->frame_count; ++idx)
|
||||||
|
{
|
||||||
|
m_pC1PixelMaps.push_back(new unsigned char[ m_frameSize ]);
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
// Process Chunks as we encounter them
|
||||||
|
file_offset += sizeof(GSLA_Header);
|
||||||
|
|
||||||
|
// While we're not at the end of the file
|
||||||
|
while (file_offset < bytes.size())
|
||||||
|
{
|
||||||
|
// This is pretty dumb, just get it done
|
||||||
|
// These are the types I understand
|
||||||
|
// every chunk is supposed to contain a value chunk_length
|
||||||
|
// at offset +4, so that we can ignore ones we don't understand
|
||||||
|
GSLA_INIT* pINIT = (GSLA_INIT*)&bytes[ file_offset ];
|
||||||
|
GSLA_ANIM* pANIM = (GSLA_ANIM*)&bytes[ file_offset ];
|
||||||
|
GSLA_CHUNK* pCHUNK = (GSLA_CHUNK*)&bytes[ file_offset ];
|
||||||
|
|
||||||
|
if (pINIT->IsValid())
|
||||||
|
{
|
||||||
|
// We have an initial frame chunk
|
||||||
|
UnpackInitialFrame(pINIT);
|
||||||
|
}
|
||||||
|
else if (pANIM->IsValid())
|
||||||
|
{
|
||||||
|
// We have a packed animation frames chunk
|
||||||
|
UnpackAnimation(pANIM, pHeader);
|
||||||
|
}
|
||||||
|
|
||||||
|
file_offset += pCHUNK->chunk_length;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Unpack the initial frame, that's been packed with an empty initial dictionary
|
||||||
|
// So every byte of the buffer will be written out (no skip opcodes)
|
||||||
|
//
|
||||||
|
void GSLAFile::UnpackInitialFrame(GSLA_INIT* pINIT)
|
||||||
|
{
|
||||||
|
unsigned char *pData = ((unsigned char*)pINIT) + sizeof(GSLA_INIT);
|
||||||
|
|
||||||
|
unsigned char *pTargetBuffer = m_pC1PixelMaps[ 0 ]; // Data needs to be pre allocated
|
||||||
|
|
||||||
|
//DecompressFrame(pTargetBuffer, pData);
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Unpack the animation frame, assuming that the initial frame already exists
|
||||||
|
//
|
||||||
|
void GSLAFile::UnpackAnimation(GSLA_ANIM* pANIM, GSLA_Header* pHeader)
|
||||||
|
{
|
||||||
|
unsigned char *pData = ((unsigned char*)pANIM) + sizeof(GSLA_ANIM);
|
||||||
|
|
||||||
|
unsigned char *pTargetBuffer = m_pC1PixelMaps[ 0 ]; // Data needs to be pre allocated
|
||||||
|
|
||||||
|
//DecompressAnim(pTargetBuffer, pData);
|
||||||
|
}
|
||||||
|
|
143
source/gsla_file.h
Normal file
143
source/gsla_file.h
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
//
|
||||||
|
// C++ Encoder/Decoder
|
||||||
|
// For GSLA, GS Lzb Animation File Format
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Care is taken in the encoder, to make sure the 65816 does not have to cross
|
||||||
|
// bank boundaries during any copy. This is so we can use the MVN instruction,
|
||||||
|
// and so we can reduce the number of bank checks in the code. We will have an
|
||||||
|
// opcode, that says “source data bank has changed”
|
||||||
|
//
|
||||||
|
// The file will be laid out such that you load the file in at a 64K memory
|
||||||
|
// boundary
|
||||||
|
//
|
||||||
|
// Please see the gsla_file.cpp, for documentation on the actual
|
||||||
|
// format of the file
|
||||||
|
//
|
||||||
|
#ifndef GSLA_FILE_H
|
||||||
|
#define GSLA_FILE_H
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
|
||||||
|
// Data Stream Header
|
||||||
|
typedef struct GSLA_Header
|
||||||
|
{
|
||||||
|
char G,S,L,A;
|
||||||
|
unsigned int file_length; // length of the file, including this header
|
||||||
|
unsigned short version;
|
||||||
|
unsigned short width; // data width in bytes
|
||||||
|
unsigned short height; // data height in bytes
|
||||||
|
unsigned short frame_size; // frame size in bytes (0 means 64K)
|
||||||
|
unsigned int frame_count; // total number of frames in the file
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// If you're doing C, just get rid of these methods
|
||||||
|
bool IsValid(unsigned int fileLength)
|
||||||
|
{
|
||||||
|
if ((G!='G')||(S!='S')||(L!='L')||(A!='A'))
|
||||||
|
return false; // signature is not right
|
||||||
|
|
||||||
|
if (file_length != fileLength)
|
||||||
|
return false; // size isn't right
|
||||||
|
|
||||||
|
if (0x8000 != frame_size)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (160 != width)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (200 != height)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} GSLA_Header;
|
||||||
|
|
||||||
|
// INITial Frame Chunk
|
||||||
|
typedef struct GSLA_INIT
|
||||||
|
{
|
||||||
|
char I,N,i,T; // 'I','N','I','T'
|
||||||
|
unsigned int chunk_length; // in bytes, including the 9 bytes header of this chunk
|
||||||
|
|
||||||
|
// Commands Coded Data Follows
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// If you're doing C, just get rid of these methods
|
||||||
|
bool IsValid()
|
||||||
|
{
|
||||||
|
if ((I!='I')||(N!='N')||(i!='I')||(T!='T'))
|
||||||
|
return false; // signature is not right
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} GSLA_INIT;
|
||||||
|
|
||||||
|
// Animated Frames Chunk
|
||||||
|
typedef struct GSLA_ANIM
|
||||||
|
{
|
||||||
|
char A,N,I,M; // 'A','N','I','M'
|
||||||
|
unsigned int chunk_length; // in bytes, including the 8 bytes header of this chunk
|
||||||
|
|
||||||
|
// Commands Coded Data Follows
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// If you're doing C, just get rid of these methods
|
||||||
|
bool IsValid()
|
||||||
|
{
|
||||||
|
if ((A!='A')||(N!='N')||(I!='I')||(M!='M'))
|
||||||
|
return false; // signature is not right
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} GSLA_ANIM;
|
||||||
|
|
||||||
|
// Generic Unknown Chunk
|
||||||
|
typedef struct GSLA_CHUNK
|
||||||
|
{
|
||||||
|
char id0,id1,id2,id3;
|
||||||
|
unsigned int chunk_length;
|
||||||
|
|
||||||
|
} GSLA_CHUNK;
|
||||||
|
|
||||||
|
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
class GSLAFile
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// Load in a C2 File
|
||||||
|
GSLAFile(const char *pFilePath);
|
||||||
|
|
||||||
|
~GSLAFile();
|
||||||
|
|
||||||
|
// Retrieval
|
||||||
|
void LoadFromFile(const char* pFilePath);
|
||||||
|
int GetFrameCount() { return (int)m_pC1PixelMaps.size(); }
|
||||||
|
int GetWidth() { return m_widthPixels; }
|
||||||
|
int GetHeight() { return m_heightPixels; }
|
||||||
|
|
||||||
|
const std::vector<unsigned char*>& GetPixelMaps() { return m_pC1PixelMaps; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void UnpackInitialFrame(GSLA_INIT* pINIT);
|
||||||
|
void UnpackAnimation(GSLA_ANIM* pANIM, GSLA_Header* pHeader);
|
||||||
|
|
||||||
|
|
||||||
|
int m_frameSize; // frame buffer size in bytes
|
||||||
|
|
||||||
|
int m_widthPixels; // Width of image in pixels
|
||||||
|
int m_heightPixels; // Height of image in pixels
|
||||||
|
|
||||||
|
std::vector<unsigned char*> m_pC1PixelMaps;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif // C2_FILE_H
|
||||||
|
|
@ -141,10 +141,12 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="..\source\bctypes.h" />
|
<ClInclude Include="..\source\bctypes.h" />
|
||||||
<ClInclude Include="..\source\c2_file.h" />
|
<ClInclude Include="..\source\c2_file.h" />
|
||||||
|
<ClInclude Include="..\source\gsla_file.h" />
|
||||||
<ClInclude Include="..\source\lzb.h" />
|
<ClInclude Include="..\source\lzb.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="..\source\c2_file.cpp" />
|
<ClCompile Include="..\source\c2_file.cpp" />
|
||||||
|
<ClCompile Include="..\source\gsla_file.cpp" />
|
||||||
<ClCompile Include="..\source\lzb.cpp" />
|
<ClCompile Include="..\source\lzb.cpp" />
|
||||||
<ClCompile Include="..\source\main.cpp" />
|
<ClCompile Include="..\source\main.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@ -20,6 +20,9 @@
|
|||||||
<ClInclude Include="..\source\lzb.h">
|
<ClInclude Include="..\source\lzb.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\source\gsla_file.h">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="..\source\c2_file.cpp">
|
<ClCompile Include="..\source\c2_file.cpp">
|
||||||
@ -31,5 +34,8 @@
|
|||||||
<ClCompile Include="..\source\lzb.cpp">
|
<ClCompile Include="..\source\lzb.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\source\gsla_file.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
Loading…
Reference in New Issue
Block a user