mirror of
https://github.com/dwsJason/gsla.git
synced 2024-12-21 18:30:28 +00:00
commit
2714f80f6e
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
*.user
|
||||
*.vpj
|
||||
*.vpw
|
||||
*.vpwhist
|
||||
*.vtg
|
||||
vcxproj/.vs/
|
||||
vcxproj/x64/
|
@ -1,2 +1,10 @@
|
||||
# gsla
|
||||
Apple IIgs animation tool, alternative to Paintworks/$C2 animation format
|
||||
|
||||
This is a C++ command line tool, that will convert a C2/Paintworks Animation
|
||||
file into the more efficient GS Lzb Animation file format.
|
||||
|
||||
See https://github.com/dwsJason/gslaplay, for a GSOS Sample Application
|
||||
that can play these animations
|
||||
|
||||
|
||||
|
48
source/bctypes.h
Normal file
48
source/bctypes.h
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
bctypes.h
|
||||
|
||||
Because I need Standard Types
|
||||
*/
|
||||
|
||||
#ifndef _bctypes_h
|
||||
#define _bctypes_h
|
||||
|
||||
typedef signed char i8;
|
||||
typedef unsigned char u8;
|
||||
typedef signed short i16;
|
||||
typedef unsigned short u16;
|
||||
typedef signed long i32;
|
||||
typedef unsigned long u32;
|
||||
|
||||
typedef signed long long i64;
|
||||
typedef unsigned long long u64;
|
||||
|
||||
// If we're using C, I still like having a bool around
|
||||
#ifndef __cplusplus
|
||||
typedef i32 bool;
|
||||
#define false (0)
|
||||
#define true (!false)
|
||||
#define nullptr 0
|
||||
#endif
|
||||
|
||||
typedef float f32;
|
||||
typedef float r32;
|
||||
typedef double f64;
|
||||
typedef double r64;
|
||||
|
||||
|
||||
#define null (0)
|
||||
|
||||
// Odd Types
|
||||
typedef union {
|
||||
// u128 ul128;
|
||||
u64 ul64[2];
|
||||
u32 ui32[4];
|
||||
} QWdata;
|
||||
|
||||
|
||||
#endif // _bctypes_h
|
||||
|
||||
// EOF - bctypes.h
|
||||
|
||||
|
143
source/c2_file.cpp
Normal file
143
source/c2_file.cpp
Normal file
@ -0,0 +1,143 @@
|
||||
//
|
||||
// C++ Encoder/Decoder
|
||||
// For C2, Paintworks Animation File Format
|
||||
//
|
||||
// File Format summary here from Brutal Deluxe
|
||||
//
|
||||
//0000.7fff first pic
|
||||
//8000.8003 length of frame data - 8008
|
||||
//8004.8007 timing
|
||||
//8008.800b length of first frame data
|
||||
//800c.800d first frame index
|
||||
//800e.800f first frame data
|
||||
//
|
||||
// Offset 0, Value FFFF indicates the end of a frame
|
||||
// 00 00 FF FF
|
||||
//
|
||||
#include "c2_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(C2File_Header)==0x800C, "C2File_Header is supposed to be 0x800C bytes");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Load in a FanFile constructor
|
||||
//
|
||||
C2File::C2File(const char *pFilePath)
|
||||
: m_widthPixels(320)
|
||||
, m_heightPixels(200)
|
||||
{
|
||||
LoadFromFile(pFilePath);
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
C2File::~C2File()
|
||||
{
|
||||
// Free Up the memory
|
||||
for (int idx = 0; idx < m_pC1PixelMaps.size(); ++idx)
|
||||
{
|
||||
delete[] m_pC1PixelMaps[idx];
|
||||
m_pC1PixelMaps[ idx ] = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void C2File::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
|
||||
C2File_Header* pHeader = (C2File_Header*) &bytes[0];
|
||||
|
||||
// Early out if things don't look right
|
||||
if (!pHeader->IsValid((unsigned int)bytes.size()))
|
||||
return;
|
||||
|
||||
// Grab Initial Frame, and put it in the list
|
||||
|
||||
unsigned char* pFrame = new unsigned char[ 0x8000 ];
|
||||
unsigned char* pCanvas = new unsigned char[ 0x8001 ]; // each frame changes the canvas
|
||||
m_pC1PixelMaps.push_back(pFrame);
|
||||
memcpy(pFrame, &bytes[0], 0x8000);
|
||||
memcpy(pCanvas, &bytes[0], 0x8000);
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Process Frames as we encounter them
|
||||
file_offset += sizeof(C2File_Header);
|
||||
|
||||
// Since we always pull 4 bytes
|
||||
// let's keep us from pulling bytes outside our buffer
|
||||
size_t eof_size = bytes.size() - 4;
|
||||
|
||||
// While we're not at the end of the file
|
||||
unsigned short offset = 0;
|
||||
//unsigned short prev_offset = 0;
|
||||
unsigned short data = 0xFFFF;
|
||||
while (file_offset <= eof_size)
|
||||
{
|
||||
//prev_offset = offset;
|
||||
|
||||
offset = (unsigned short)bytes[ file_offset++ ];
|
||||
offset |= ((unsigned short)bytes[ file_offset++ ])<<8;
|
||||
|
||||
data = (unsigned short)bytes[ file_offset++ ];
|
||||
data |= ((unsigned short)bytes[ file_offset++ ])<<8;
|
||||
|
||||
//if (((offset == 0)&&(data == 0xFFFF))||(offset < prev_offset))
|
||||
if (0 == offset)
|
||||
{
|
||||
// End of Frame, capture a copy
|
||||
pFrame = new unsigned char[ 0x8000 ];
|
||||
memcpy(pFrame, pCanvas, 0x8000);
|
||||
m_pC1PixelMaps.push_back(pFrame);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Apply Change to the Canvas
|
||||
offset &= 0x7FFF; // force the offset into the Canvas
|
||||
// probably should ignore offsets outside the canvas
|
||||
pCanvas[ offset ] = data & 0xFF;
|
||||
pCanvas[ offset+1 ] = (data >> 8) & 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
delete[] pCanvas;
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
73
source/c2_file.h
Normal file
73
source/c2_file.h
Normal file
@ -0,0 +1,73 @@
|
||||
//
|
||||
// C++ Encoder/Decoder
|
||||
// For C2, Paintworks Animation File Format
|
||||
//
|
||||
// File Format summary here from Brutal Deluxe
|
||||
//
|
||||
//0000.7fff first pic
|
||||
//8000.8003 length of frame data - 8008
|
||||
//8004.8007 timing
|
||||
//8008.800b length of first frame data
|
||||
//800c.800d first frame index
|
||||
//800e.800f first frame data
|
||||
//
|
||||
// Offset 0, Value FFFF indicates the end of a frame
|
||||
// 00 00 FF FF
|
||||
//
|
||||
#ifndef C2_FILE_H
|
||||
#define C2_FILE_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
typedef struct C2File_Header
|
||||
{
|
||||
char image[0x8000]; // C1 Initial Image
|
||||
|
||||
unsigned int file_length; // length of frame_data (file length - 8008)
|
||||
unsigned int timing;
|
||||
unsigned int frame_length; // first encoded frame length
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// If you're doing C, just get rid of these methods
|
||||
bool IsValid(unsigned int fileLength)
|
||||
{
|
||||
if ((file_length+0x8008) != fileLength)
|
||||
return false; // size isn't right
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} C2File_Header;
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
class C2File
|
||||
{
|
||||
public:
|
||||
// Load in a C2 File
|
||||
C2File(const char *pFilePath);
|
||||
|
||||
~C2File();
|
||||
|
||||
// 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:
|
||||
|
||||
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
|
||||
|
606
source/gsla_file.cpp
Normal file
606
source/gsla_file.cpp
Normal file
@ -0,0 +1,606 @@
|
||||
//
|
||||
// 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_xxx1 - 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_0000- Source Skip -> Source pointer skips to next bank of data
|
||||
//%0000_0000_0000_0010- End of Frame - end of frame
|
||||
//%0000_0000_0000_0110- End of Animation / End of File / no more frames
|
||||
//
|
||||
//
|
||||
// other remaining codes, are reserved for future expansion
|
||||
|
||||
#include "gsla_file.h"
|
||||
|
||||
#include "lzb.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(int iWidthPixels, int iHeightPixels, int iFrameSizeBytes )
|
||||
: m_widthPixels(iWidthPixels)
|
||||
, m_heightPixels(iHeightPixels)
|
||||
, m_frameSize( iFrameSizeBytes )
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
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, pHeader);
|
||||
}
|
||||
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, GSLA_Header* pHeader)
|
||||
{
|
||||
unsigned char* pData = ((unsigned char*)pINIT) + sizeof(GSLA_INIT);
|
||||
|
||||
unsigned char* pTargetBuffer = m_pC1PixelMaps[ 0 ]; // Data needs to be pre allocated
|
||||
|
||||
DecompressFrame(pTargetBuffer, pData, (unsigned char*)pHeader);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// 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 *pCanvas = new unsigned char[m_frameSize];
|
||||
|
||||
// Initialize the Canvas with the first frame
|
||||
memcpy(pCanvas, m_pC1PixelMaps[0], m_frameSize);
|
||||
|
||||
for (int idx = 1; idx < m_pC1PixelMaps.size(); ++idx)
|
||||
{
|
||||
// Apply Changes to the Canvas
|
||||
pData += DecompressFrame(pCanvas, pData, (unsigned char*) pHeader);
|
||||
|
||||
// Capture the Canvas
|
||||
memcpy(m_pC1PixelMaps[idx], pCanvas, m_frameSize);
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Append a copy of raw image data into the class
|
||||
//
|
||||
void GSLAFile::AddImages( const std::vector<unsigned char*>& pFrameBytes )
|
||||
{
|
||||
for (int idx = 0; idx < pFrameBytes.size(); ++idx)
|
||||
{
|
||||
unsigned char* pPixels = new unsigned char[ m_frameSize ];
|
||||
memcpy(pPixels, pFrameBytes[ idx ], m_frameSize );
|
||||
m_pC1PixelMaps.push_back( pPixels );
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Compress / Serialize a new GSLA File
|
||||
//
|
||||
void GSLAFile::SaveToFile(const char* pFilenamePath)
|
||||
{
|
||||
// We're not going to even try encoding an empty file
|
||||
if (m_pC1PixelMaps.size() < 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// serialize to memory, then save that to a file
|
||||
std::vector<unsigned char> bytes;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Add the header
|
||||
bytes.resize( bytes.size() + sizeof(GSLA_Header) );
|
||||
|
||||
//$$JGA Remember, you have to set the pointer, before every access
|
||||
//$$JGA to the header data, because vector is going to change out
|
||||
//$$JGA memory addresses from underneath you
|
||||
GSLA_Header* pHeader = (GSLA_Header*)&bytes[0];
|
||||
|
||||
pHeader->G = 'G'; pHeader->S = 'S'; pHeader->L = 'L'; pHeader->A = 'A';
|
||||
|
||||
pHeader->file_length = 0; // Temp File Length
|
||||
|
||||
pHeader->version = 0x8000; // Version 0, with a Ring/Loop Frame at the end
|
||||
|
||||
pHeader->width = m_widthPixels >> 1;
|
||||
pHeader->height = m_heightPixels;
|
||||
|
||||
pHeader->frame_size = m_frameSize;
|
||||
|
||||
pHeader->frame_count = (unsigned int)m_pC1PixelMaps.size() + 1; // + 1 for the ring frame
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Add the INITial frame chunk
|
||||
//
|
||||
// If there's only an initial frame, I guess this becomes a picture
|
||||
//
|
||||
|
||||
size_t init_offset = bytes.size();
|
||||
|
||||
// Add space for the INIT header
|
||||
bytes.resize( bytes.size() + sizeof(GSLA_INIT) );
|
||||
GSLA_INIT* pINIT = (GSLA_INIT*) &bytes[ init_offset ];
|
||||
|
||||
pINIT->I = 'I'; pINIT->N = 'N'; pINIT->i = 'I'; pINIT->T = 'T';
|
||||
pINIT->chunk_length = 0; // temp chunk size
|
||||
|
||||
printf("Save Initial Frame\n");
|
||||
|
||||
// Need a place to put compressed data, in theory it could be bigger
|
||||
// than the original data, I think if that happens, the image was probably
|
||||
// designed to break this, anyway, give double theoretical max
|
||||
unsigned char* pWorkBuffer = new unsigned char[ m_frameSize * 2 ];
|
||||
|
||||
unsigned char* pInitialFrame = m_pC1PixelMaps[ 0 ];
|
||||
|
||||
// We're not worried about bank wrap on the first frame, and we don't have a pre-populated
|
||||
// dictionary - Also use the best compression we can get here
|
||||
int compressedSize = Old_LZB_Compress(pWorkBuffer, pInitialFrame, m_frameSize);
|
||||
|
||||
printf("frameSize = %d\n", compressedSize);
|
||||
|
||||
for (int compressedIndex = 0; compressedIndex < compressedSize; ++compressedIndex)
|
||||
{
|
||||
bytes.push_back(pWorkBuffer[ compressedIndex ]);
|
||||
}
|
||||
|
||||
// Insert EOF/ End of Animation Done opcode
|
||||
bytes.push_back( 0x06 );
|
||||
bytes.push_back( 0x00 );
|
||||
|
||||
|
||||
// Reset pointer to the pINIT (as the baggage may have shifted)
|
||||
pINIT = (GSLA_INIT*) &bytes[ init_offset ];
|
||||
pINIT->chunk_length = (unsigned int) (bytes.size() - init_offset);
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Add the ANIMation frames chunk
|
||||
//
|
||||
// We always add this, because we always add a Ring/Loop frame, we always
|
||||
// end up with at least 2 frames
|
||||
//
|
||||
|
||||
size_t anim_offset = bytes.size();
|
||||
|
||||
// Add Space for the ANIM Header
|
||||
bytes.resize( bytes.size() + sizeof(GSLA_ANIM) );
|
||||
GSLA_ANIM* pANIM = (GSLA_ANIM*) &bytes[ anim_offset ];
|
||||
|
||||
pANIM->A = 'A'; pANIM->N = 'N'; pANIM->I ='I'; pANIM->M = 'M';
|
||||
pANIM->chunk_length = 0; // temporary chunk size
|
||||
|
||||
// Initialize the Canvas with the initial frame (we alread exported this)
|
||||
unsigned char *pCanvas = new unsigned char[ m_frameSize ];
|
||||
memcpy(pCanvas, m_pC1PixelMaps[0], m_frameSize);
|
||||
|
||||
// Let's encode some frames buddy
|
||||
for (int frameIndex = 1; frameIndex < m_pC1PixelMaps.size(); ++frameIndex)
|
||||
{
|
||||
printf("Save Frame %d\n", frameIndex+1);
|
||||
|
||||
// I don't want random data in the bank gaps, so initialize this
|
||||
// buffer with zero
|
||||
//memset(pWorkBuffer, 0xEA, m_frameSize * 2);
|
||||
|
||||
int frameSize = LZBA_Compress(pWorkBuffer, m_pC1PixelMaps[ frameIndex ],
|
||||
m_frameSize, pWorkBuffer-bytes.size(),
|
||||
pCanvas, m_frameSize );
|
||||
|
||||
//int canvasDiff = memcmp(pCanvas, m_pC1PixelMaps[ frameIndex], m_frameSize);
|
||||
//if (canvasDiff)
|
||||
//{
|
||||
// printf("Canvas is not correct - %d\n", canvasDiff);
|
||||
//}
|
||||
printf("frameSize = %d\n", frameSize);
|
||||
|
||||
|
||||
for (int frameIndex = 0; frameIndex < frameSize; ++frameIndex)
|
||||
{
|
||||
bytes.push_back(pWorkBuffer[ frameIndex ]);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the RING Frame
|
||||
//memset(pWorkBuffer, 0xAB, m_frameSize * 2);
|
||||
|
||||
printf("Save Ring Frame\n");
|
||||
|
||||
int ringSize = LZBA_Compress(pWorkBuffer, m_pC1PixelMaps[ 0 ],
|
||||
m_frameSize, pWorkBuffer-bytes.size(),
|
||||
pCanvas, m_frameSize );
|
||||
|
||||
printf("Ring Size %d\n", ringSize);
|
||||
|
||||
for (int ringIndex = 0; ringIndex < ringSize; ++ringIndex)
|
||||
{
|
||||
bytes.push_back(pWorkBuffer[ ringIndex ]);
|
||||
}
|
||||
|
||||
delete[] pCanvas; pCanvas = nullptr;
|
||||
|
||||
// Insert End of file/ End of Animation Done opcode
|
||||
// -- There has to be room for this, or there wouldn't be room to insert
|
||||
// -- a source bank skip opcode
|
||||
bytes.push_back( 0x06 );
|
||||
bytes.push_back( 0x00 );
|
||||
|
||||
// Update the chunk length
|
||||
pANIM = (GSLA_ANIM*)&bytes[ anim_offset ];
|
||||
pANIM->chunk_length = (unsigned int) (bytes.size() - anim_offset);
|
||||
|
||||
// Update the header
|
||||
pHeader = (GSLA_Header*)&bytes[0]; // Required
|
||||
pHeader->file_length = (unsigned int)bytes.size(); // get some valid data in there
|
||||
|
||||
// Try not to leak memory, even though we probably do
|
||||
delete[] pWorkBuffer;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Create the file and write it
|
||||
FILE* pFile = nullptr;
|
||||
errno_t err = fopen_s(&pFile, pFilenamePath, "wb");
|
||||
|
||||
if (0==err)
|
||||
{
|
||||
fwrite(&bytes[0], sizeof(unsigned char), bytes.size(), pFile);
|
||||
fclose(pFile);
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Std C memcpy seems to be stopping the copy from happening, when I overlap
|
||||
// the buffer to get a pattern run copy (overlapped buffers)
|
||||
//
|
||||
static void my_memcpy(unsigned char* pDest, unsigned char* pSrc, int length)
|
||||
{
|
||||
while (length-- > 0)
|
||||
{
|
||||
*pDest++ = *pSrc++;
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// pTarget is the Target Frame Buffer
|
||||
// pData is the source data for a Frame
|
||||
//
|
||||
// pDataBaseAddress, is the base address wheret the animation file was loaded
|
||||
// this is used so we can properly interpret bank-skip opcodes (data is broken
|
||||
// into 64K chunks for the IIgs/65816)
|
||||
//
|
||||
// returns the number of bytes that have been processed in the pData
|
||||
//
|
||||
int GSLAFile::DecompressFrame(unsigned char* pTarget, unsigned char* pData, unsigned char* pDataBaseAddress)
|
||||
{
|
||||
unsigned char *pDataStart = pData;
|
||||
|
||||
int cursorPosition = 0;
|
||||
unsigned short opcode;
|
||||
|
||||
bool bDoWork = true;
|
||||
|
||||
while (bDoWork)
|
||||
{
|
||||
opcode = pData[0];
|
||||
opcode |= (((unsigned short)pData[1])<<8);
|
||||
|
||||
if (opcode & 0x8000)
|
||||
{
|
||||
if (opcode & 0x0001)
|
||||
{
|
||||
// Cursor Skip Forward
|
||||
opcode = (opcode>>1) & 0x3FFF;
|
||||
cursorPosition += (opcode+1);
|
||||
pData+=2;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Dictionary Copy
|
||||
unsigned short dictionaryPosition = pData[2];
|
||||
dictionaryPosition |= (((unsigned short)pData[3])<<8);
|
||||
|
||||
dictionaryPosition -= 0x2000; // it's like this to to help the
|
||||
// GS decode it quicker
|
||||
|
||||
unsigned short length = ((opcode>>1) & 0x3FFF)+1;
|
||||
|
||||
my_memcpy(pTarget + cursorPosition, pTarget + dictionaryPosition, (int) length );
|
||||
|
||||
pData += 4;
|
||||
cursorPosition += length;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (opcode & 0x0001)
|
||||
{
|
||||
// Literal Copy Bytes
|
||||
pData += 2;
|
||||
unsigned short length = ((opcode>>1) & 0x3FFF)+1;
|
||||
|
||||
my_memcpy(pTarget + cursorPosition, pData, (int) length);
|
||||
|
||||
pData += length;
|
||||
cursorPosition += length;
|
||||
}
|
||||
else
|
||||
{
|
||||
opcode = ((opcode>>1)) & 3;
|
||||
|
||||
switch (opcode)
|
||||
{
|
||||
case 0: // Source bank Skip
|
||||
{
|
||||
int offset = (int)(pData - pDataBaseAddress);
|
||||
|
||||
offset &= 0xFFFF0000;
|
||||
offset += 0x00010000;
|
||||
|
||||
pData = pDataBaseAddress + offset;
|
||||
}
|
||||
break;
|
||||
case 1: // End of frame
|
||||
pData+=2;
|
||||
bDoWork = false;
|
||||
break;
|
||||
|
||||
case 3: // End of Animation
|
||||
// Intentionally, leave cursor alone here
|
||||
bDoWork = false;
|
||||
break;
|
||||
|
||||
default:
|
||||
// Reserved / Illegal
|
||||
bDoWork = false;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
return (int)(pData - pDataStart);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
150
source/gsla_file.h
Normal file
150
source/gsla_file.h
Normal file
@ -0,0 +1,150 @@
|
||||
//
|
||||
// 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 GSLA File
|
||||
GSLAFile(const char *pFilePath);
|
||||
~GSLAFile();
|
||||
|
||||
// Creation
|
||||
GSLAFile(int iWidthPixels, int iHeightPixels, int iFrameSizeBytes);
|
||||
void AddImages( const std::vector<unsigned char*>& pFrameBytes );
|
||||
void SaveToFile(const char* pFilenamePath);
|
||||
|
||||
// Retrieval
|
||||
void LoadFromFile(const char* pFilePath);
|
||||
int GetFrameCount() { return (int)m_pC1PixelMaps.size(); }
|
||||
int GetWidth() { return m_widthPixels; }
|
||||
int GetHeight() { return m_heightPixels; }
|
||||
int GetFrameSize() { return m_frameSize; }
|
||||
|
||||
const std::vector<unsigned char*>& GetPixelMaps() { return m_pC1PixelMaps; }
|
||||
|
||||
int DecompressFrame(unsigned char* pTarget, unsigned char* pData, unsigned char* pDataBaseAddress);
|
||||
|
||||
private:
|
||||
|
||||
void UnpackInitialFrame(GSLA_INIT* pINIT, GSLA_Header* pHeader);
|
||||
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
|
||||
|
986
source/lzb.cpp
Normal file
986
source/lzb.cpp
Normal file
@ -0,0 +1,986 @@
|
||||
//
|
||||
// LZB Encode / Decode
|
||||
//
|
||||
#include "lzb.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "bctypes.h"
|
||||
|
||||
#include "assert.h"
|
||||
|
||||
//
|
||||
// This is written specifically for the GSLA, so opcodes emitted are designed
|
||||
// to work with our version of a run/skip/dump
|
||||
//
|
||||
|
||||
//
|
||||
//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_xxx1 - 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_0000- Source Skip -> Source pointer skips to next bank of data
|
||||
//%0000_0000_0000_0010- End of Frame - end of frame
|
||||
//%0000_0000_0000_0110- End of Animation / End of File / no more frames
|
||||
//
|
||||
|
||||
#define MAX_DICTIONARY_SIZE (32 * 1024)
|
||||
#define MAX_STRING_SIZE (16384)
|
||||
//
|
||||
// Yes This is a 32K Buffer, of bytes, with no structure to it
|
||||
//
|
||||
static unsigned char *pGlobalDictionary = nullptr;
|
||||
|
||||
struct DataString {
|
||||
// Information about the data we're trying to match
|
||||
int size;
|
||||
unsigned char *pData;
|
||||
};
|
||||
|
||||
static int AddDictionary(const DataString& data, int dictionarySize);
|
||||
static int EmitLiteral(unsigned char *pDest, DataString& data);
|
||||
static int ConcatLiteral(unsigned char *pDest, DataString& data);
|
||||
static int EmitReference(unsigned char *pDest, int dictionaryOffset, DataString& data);
|
||||
static int DictionaryMatch(const DataString& data, int dictionarySize);
|
||||
|
||||
// Stuff I need for a faster version
|
||||
static DataString LongestMatch(const DataString& data, const DataString& dictionary);
|
||||
static DataString LongestMatch(const DataString& data, const DataString& dictionary, int cursorPosition);
|
||||
|
||||
//
|
||||
// New Version, still Brute Force, but not as many times
|
||||
//
|
||||
int LZB_Compress(unsigned char* pDest, unsigned char* pSource, int sourceSize)
|
||||
{
|
||||
//printf("LZB Compress %d bytes\n", sourceSize);
|
||||
|
||||
unsigned char *pOriginalDest = pDest;
|
||||
|
||||
DataString sourceData;
|
||||
DataString dictionaryData;
|
||||
DataString candidateData;
|
||||
|
||||
// Source Data Stream - will compress until the size is zero
|
||||
sourceData.pData = pSource;
|
||||
sourceData.size = sourceSize;
|
||||
|
||||
// Remember, this eventually will point at the frame buffer
|
||||
pGlobalDictionary = pSource;
|
||||
dictionaryData.pData = pSource;
|
||||
dictionaryData.size = 0;
|
||||
|
||||
// dumb last emit is a literal stuff
|
||||
bool bLastEmitIsLiteral = false;
|
||||
unsigned char* pLastLiteralDest = nullptr;
|
||||
|
||||
while (sourceData.size > 0)
|
||||
{
|
||||
candidateData = LongestMatch(sourceData, dictionaryData);
|
||||
|
||||
// If no match, or the match is too small, then take the next byte
|
||||
// and emit as literal
|
||||
if ((0 == candidateData.size)) // || (candidateData.size < 4))
|
||||
{
|
||||
candidateData.size = 1;
|
||||
candidateData.pData = sourceData.pData;
|
||||
}
|
||||
|
||||
// Adjust source stream
|
||||
sourceData.pData += candidateData.size;
|
||||
sourceData.size -= candidateData.size;
|
||||
|
||||
dictionaryData.size = AddDictionary(candidateData, dictionaryData.size);
|
||||
|
||||
if (candidateData.size > 3)
|
||||
{
|
||||
// Emit a dictionary reference
|
||||
pDest += (int)EmitReference(pDest, (int)(candidateData.pData - dictionaryData.pData), candidateData);
|
||||
bLastEmitIsLiteral = false;
|
||||
}
|
||||
else if (bLastEmitIsLiteral)
|
||||
{
|
||||
// Concatenate this literal onto the previous literal
|
||||
pDest += ConcatLiteral(pLastLiteralDest, candidateData);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Emit a new literal
|
||||
pLastLiteralDest = pDest;
|
||||
bLastEmitIsLiteral = true;
|
||||
pDest += EmitLiteral(pDest, candidateData);
|
||||
}
|
||||
}
|
||||
|
||||
return (int)(pDest - pOriginalDest);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// This works, but it's stupidly slow, because it uses brute force, and
|
||||
// because the brute force starts over everytime I grow the data string
|
||||
//
|
||||
int Old_LZB_Compress(unsigned char* pDest, unsigned char* pSource, int sourceSize)
|
||||
{
|
||||
//printf("LZB_Compress %d bytes\n", sourceSize);
|
||||
|
||||
// Initialize Dictionary
|
||||
int bytesInDictionary = 0; // eventually add the ability to start with the dictionary filled
|
||||
pGlobalDictionary = pSource;
|
||||
|
||||
int processedBytes = 0;
|
||||
int bytesEmitted = 0;
|
||||
|
||||
// dumb last emit is a literal stuff
|
||||
bool bLastEmitIsLiteral = false;
|
||||
int lastEmittedLiteralOffset = 0;
|
||||
|
||||
DataString candidate_data;
|
||||
candidate_data.pData = pSource;
|
||||
candidate_data.size = 0;
|
||||
|
||||
int MatchOffset = -1;
|
||||
int PreviousMatchOffset = -1;
|
||||
|
||||
while (processedBytes < sourceSize)
|
||||
{
|
||||
// Add a byte to the candidate_data, also tally number of processed
|
||||
processedBytes++;
|
||||
candidate_data.size++;
|
||||
|
||||
// Basic Flow Idea Here
|
||||
// If there's a match, then add to the candidate data, and see if
|
||||
// there's a bigger match (use previous result to speed up search)
|
||||
// else
|
||||
// if there's a previous match, and it's large enough, emit that
|
||||
// else emit what we have as a literal
|
||||
|
||||
|
||||
// (KMP is probably the last planned optmization here)
|
||||
PreviousMatchOffset = MatchOffset;
|
||||
|
||||
MatchOffset = DictionaryMatch(candidate_data, bytesInDictionary);
|
||||
|
||||
// The dictionary only contains bytes that have been emitted, so we
|
||||
// can't add this byte until we've emitted it?
|
||||
if (MatchOffset < 0)
|
||||
{
|
||||
// Was there a dictionary match
|
||||
|
||||
// Previous Data, we can't get here with candidate_data.size == 0
|
||||
// this is an opportunity to use an assert
|
||||
candidate_data.size--;
|
||||
|
||||
MatchOffset = PreviousMatchOffset; //DictionaryMatch(candidate_data, bytesInDictionary);
|
||||
|
||||
if ((MatchOffset >= 0) && candidate_data.size > 3)
|
||||
{
|
||||
processedBytes--;
|
||||
bytesInDictionary = AddDictionary(candidate_data, bytesInDictionary);
|
||||
bytesEmitted += EmitReference(pDest + bytesEmitted, MatchOffset, candidate_data);
|
||||
bLastEmitIsLiteral = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (0 == candidate_data.size)
|
||||
{
|
||||
candidate_data.size++;
|
||||
}
|
||||
else
|
||||
{
|
||||
processedBytes--;
|
||||
|
||||
//if (candidate_data.size > 1)
|
||||
//{
|
||||
// processedBytes -= (candidate_data.size - 1);
|
||||
// candidate_data.size = 1;
|
||||
//}
|
||||
}
|
||||
|
||||
|
||||
// Add Dictionary
|
||||
bytesInDictionary = AddDictionary(candidate_data, bytesInDictionary);
|
||||
|
||||
if (bLastEmitIsLiteral)
|
||||
{
|
||||
// If the last emit was a literal, I want to concatenate
|
||||
// this literal into the previous opcode, to save space
|
||||
bytesEmitted += ConcatLiteral(pDest + lastEmittedLiteralOffset, candidate_data);
|
||||
}
|
||||
else
|
||||
{
|
||||
lastEmittedLiteralOffset = bytesEmitted;
|
||||
bytesEmitted += EmitLiteral(pDest + bytesEmitted, candidate_data);
|
||||
}
|
||||
bLastEmitIsLiteral = true;
|
||||
//MatchOffset = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (candidate_data.size > 0)
|
||||
{
|
||||
|
||||
int MatchOffset = DictionaryMatch(candidate_data, bytesInDictionary);
|
||||
|
||||
if ((MatchOffset >=0) && candidate_data.size > 2)
|
||||
{
|
||||
bytesInDictionary = AddDictionary(candidate_data, bytesInDictionary);
|
||||
bytesEmitted += EmitReference(pDest + bytesEmitted, MatchOffset, candidate_data);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add Dictionary
|
||||
bytesInDictionary = AddDictionary(candidate_data, bytesInDictionary);
|
||||
|
||||
if (bLastEmitIsLiteral)
|
||||
{
|
||||
// If the last emit was a literal, I want to concatenate
|
||||
// this literal into the previous opcode, to save space
|
||||
bytesEmitted += ConcatLiteral(pDest + lastEmittedLiteralOffset, candidate_data);
|
||||
}
|
||||
else
|
||||
{
|
||||
bytesEmitted += EmitLiteral(pDest + bytesEmitted, candidate_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bytesEmitted;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Return new dictionarySize
|
||||
static int AddDictionary(const DataString& data, int dictionarySize)
|
||||
{
|
||||
int dataIndex = 0;
|
||||
while (dataIndex < data.size)
|
||||
{
|
||||
pGlobalDictionary[ dictionarySize++ ] = data.pData[ dataIndex++ ];
|
||||
}
|
||||
|
||||
//dictionarySize += data.size;
|
||||
|
||||
return dictionarySize;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Return longest match of data, in dictionary
|
||||
//
|
||||
|
||||
DataString LongestMatch(const DataString& data, const DataString& dictionary)
|
||||
{
|
||||
DataString result;
|
||||
result.pData = nullptr;
|
||||
result.size = 0;
|
||||
|
||||
// Find the longest matching data in the dictionary
|
||||
if ((dictionary.size > 0) && (data.size > 0))
|
||||
{
|
||||
DataString candidate;
|
||||
candidate.pData = data.pData;
|
||||
candidate.size = 0;
|
||||
|
||||
// First Check for a pattern / run-length style match
|
||||
// Check the end of the dictionary, to see if this data could be a
|
||||
// pattern "run" (where we can repeat a pattern for X many times for free
|
||||
// using the memcpy with overlapping source/dest buffers)
|
||||
// (This is a dictionary based pattern run/length)
|
||||
{
|
||||
// Check for pattern sizes, start small
|
||||
int max_pattern_size = 4096;
|
||||
if (dictionary.size < max_pattern_size) max_pattern_size = dictionary.size;
|
||||
if (data.size < max_pattern_size) max_pattern_size = data.size;
|
||||
|
||||
for (int pattern_size = 1; pattern_size <= max_pattern_size; ++pattern_size)
|
||||
{
|
||||
int pattern_start = dictionary.size - pattern_size;
|
||||
|
||||
for (int dataIndex = 0; dataIndex < data.size; ++dataIndex)
|
||||
{
|
||||
if (data.pData[ dataIndex ] == dictionary.pData[ pattern_start + (dataIndex % pattern_size) ])
|
||||
{
|
||||
candidate.pData = dictionary.pData + pattern_start;
|
||||
candidate.size = dataIndex+1;
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
//if (candidate.size < pattern_size)
|
||||
// break;
|
||||
|
||||
if (candidate.size > result.size)
|
||||
{
|
||||
result = candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// As an optimization
|
||||
int dictionarySize = dictionary.size; // - 1; // This last string has already been checked by, the
|
||||
// run-length matcher above
|
||||
|
||||
// As the size grows, we're missing potential matches in here
|
||||
// I think the best way to counter this is to attempt somthing
|
||||
// like KMP
|
||||
|
||||
if (dictionarySize > candidate.size)
|
||||
{
|
||||
// Check the dictionary for a match, brute force
|
||||
for (int dictionaryIndex = 0; dictionaryIndex <= (dictionarySize-candidate.size); ++dictionaryIndex)
|
||||
{
|
||||
int sizeAvailable = dictionarySize - dictionaryIndex;
|
||||
|
||||
if (sizeAvailable > data.size) sizeAvailable = data.size;
|
||||
|
||||
// this could index off the end of the dictionary!!! FIX ME
|
||||
for (int dataIndex = 0; dataIndex < sizeAvailable; ++dataIndex)
|
||||
{
|
||||
if (data.pData[ dataIndex ] == dictionary.pData[ dictionaryIndex + dataIndex ])
|
||||
{
|
||||
if (dataIndex >= candidate.size)
|
||||
{
|
||||
candidate.pData = dictionary.pData + dictionaryIndex;
|
||||
candidate.size = dataIndex + 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (candidate.size > result.size)
|
||||
{
|
||||
result = candidate;
|
||||
//dictionaryIndex = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
DataString LongestMatch(const DataString& data, const DataString& dictionary, int cursorPosition)
|
||||
{
|
||||
DataString result;
|
||||
result.pData = nullptr;
|
||||
result.size = 0;
|
||||
|
||||
// Find the longest matching data in the dictionary
|
||||
if ((dictionary.size > 0) && (data.size > 0))
|
||||
{
|
||||
DataString candidate;
|
||||
candidate.pData = data.pData;
|
||||
candidate.size = 0;
|
||||
|
||||
// First Check for a pattern / run-length style match
|
||||
// Check the end of the dictionary, to see if this data could be a
|
||||
// pattern "run" (where we can repeat a pattern for X many times for free
|
||||
// using the memcpy with overlapping source/dest buffers)
|
||||
// (This is a dictionary based pattern run/length)
|
||||
{
|
||||
// Check for pattern sizes, start small
|
||||
int max_pattern_size = 4096;
|
||||
if (cursorPosition < max_pattern_size) max_pattern_size = cursorPosition;
|
||||
if (data.size < max_pattern_size) max_pattern_size = data.size;
|
||||
|
||||
for (int pattern_size = 1; pattern_size <= max_pattern_size; ++pattern_size)
|
||||
{
|
||||
int pattern_start = cursorPosition - pattern_size;
|
||||
|
||||
for (int dataIndex = 0; dataIndex < data.size; ++dataIndex)
|
||||
{
|
||||
if (data.pData[ dataIndex ] == dictionary.pData[ pattern_start + (dataIndex % pattern_size) ])
|
||||
{
|
||||
candidate.pData = dictionary.pData + pattern_start;
|
||||
candidate.size = dataIndex+1;
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (candidate.size > result.size)
|
||||
{
|
||||
result = candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Not getting better than this
|
||||
if (result.size == data.size)
|
||||
return result;
|
||||
|
||||
// This will keep us from finding matches that we can't use
|
||||
|
||||
int dictionarySize = cursorPosition;
|
||||
|
||||
// As the size grows, we're missing potential matches in here
|
||||
// I think the best way to counter this is to attempt somthing
|
||||
// like KMP
|
||||
|
||||
if (dictionarySize > candidate.size)
|
||||
{
|
||||
// Check the dictionary for a match, brute force
|
||||
for (int dictionaryIndex = 0; dictionaryIndex <= (dictionarySize-candidate.size); ++dictionaryIndex)
|
||||
{
|
||||
int sizeAvailable = dictionarySize - dictionaryIndex;
|
||||
|
||||
if (sizeAvailable > data.size) sizeAvailable = data.size;
|
||||
|
||||
// this could index off the end of the dictionary!!! FIX ME
|
||||
for (int dataIndex = 0; dataIndex < sizeAvailable; ++dataIndex)
|
||||
{
|
||||
if (data.pData[ dataIndex ] == dictionary.pData[ dictionaryIndex + dataIndex ])
|
||||
{
|
||||
if (dataIndex >= candidate.size)
|
||||
{
|
||||
candidate.pData = dictionary.pData + dictionaryIndex;
|
||||
candidate.size = dataIndex + 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (candidate.size > result.size)
|
||||
{
|
||||
result = candidate;
|
||||
//dictionaryIndex = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Not getting better than this
|
||||
if (result.size == data.size)
|
||||
return result;
|
||||
|
||||
|
||||
#if 1
|
||||
// Look for matches beyond the cursor
|
||||
dictionarySize = dictionary.size;
|
||||
|
||||
if ((dictionarySize-cursorPosition) > candidate.size)
|
||||
{
|
||||
// Check the dictionary for a match, brute force
|
||||
for (int dictionaryIndex = cursorPosition+3; dictionaryIndex <= (dictionarySize-candidate.size); ++dictionaryIndex)
|
||||
{
|
||||
int sizeAvailable = dictionarySize - dictionaryIndex;
|
||||
|
||||
if (sizeAvailable > data.size) sizeAvailable = data.size;
|
||||
|
||||
// this could index off the end of the dictionary!!! FIX ME
|
||||
for (int dataIndex = 0; dataIndex < sizeAvailable; ++dataIndex)
|
||||
{
|
||||
if (data.pData[ dataIndex ] == dictionary.pData[ dictionaryIndex + dataIndex ])
|
||||
{
|
||||
if (dataIndex >= candidate.size)
|
||||
{
|
||||
candidate.pData = dictionary.pData + dictionaryIndex;
|
||||
candidate.size = dataIndex + 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (candidate.size > result.size)
|
||||
{
|
||||
result = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Return offset into dictionary where the string matches
|
||||
//
|
||||
// -1 means, no match
|
||||
//
|
||||
static int DictionaryMatch(const DataString& data, int dictionarySize)
|
||||
{
|
||||
if( (0 == dictionarySize ) ||
|
||||
(0 == data.size) ||
|
||||
(data.size > MAX_STRING_SIZE) ) // 16384 is largest string copy we can encode
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Check the end of the dictionary, to see if this data could be a
|
||||
// pattern "run" (where we can repeat a pattern for X many times for free
|
||||
// using the memcpy with overlapping source/dest buffers)
|
||||
// (This is a dictionary based pattern run/length)
|
||||
|
||||
{
|
||||
// Check for pattern sizes, start small
|
||||
int max_pattern_size = 256;
|
||||
if (dictionarySize < max_pattern_size) max_pattern_size = dictionarySize;
|
||||
if (data.size < max_pattern_size) max_pattern_size = data.size;
|
||||
|
||||
for (int pattern_size = 1; pattern_size <= max_pattern_size; ++pattern_size)
|
||||
{
|
||||
bool bMatch = true;
|
||||
int pattern_start = dictionarySize - pattern_size;
|
||||
|
||||
for (int dataIndex = 0; dataIndex < data.size; ++dataIndex)
|
||||
{
|
||||
if (data.pData[ dataIndex ] == pGlobalDictionary[ pattern_start + (dataIndex % pattern_size) ])
|
||||
continue;
|
||||
|
||||
bMatch = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (bMatch)
|
||||
{
|
||||
// Return a RLE Style match result
|
||||
return pattern_start;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// As an optimization
|
||||
dictionarySize -= 1; // This last string has already been checked by, the
|
||||
// run-length matcher above
|
||||
|
||||
if (dictionarySize < data.size)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int result = -1;
|
||||
|
||||
// Check the dictionary for a match, brute force
|
||||
for (int idx = 0; idx <= (dictionarySize-data.size); ++idx)
|
||||
{
|
||||
bool bMatch = true;
|
||||
for (int dataIdx = 0; dataIdx < data.size; ++dataIdx)
|
||||
{
|
||||
if (data.pData[ dataIdx ] == pGlobalDictionary[ idx + dataIdx ])
|
||||
continue;
|
||||
|
||||
bMatch = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (bMatch)
|
||||
{
|
||||
result = idx;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Emit a literal, that appends itself to an existing literal
|
||||
//
|
||||
static int ConcatLiteral(unsigned char *pDest, DataString& data)
|
||||
{
|
||||
// Return Size
|
||||
int outSize = (int)data.size;
|
||||
|
||||
int opCode = pDest[0];
|
||||
opCode |= (int)(((pDest[1])&0x7F)<<8);
|
||||
|
||||
opCode>>=1;
|
||||
opCode+=1;
|
||||
// opCode contains the length of the literal that's already encoded
|
||||
|
||||
int skip = opCode;
|
||||
opCode += outSize;
|
||||
|
||||
// Opcode
|
||||
opCode -= 1;
|
||||
opCode <<=1;
|
||||
opCode |= 1;
|
||||
|
||||
*pDest++ = (unsigned char)(opCode & 0xFF);
|
||||
*pDest++ = (unsigned char)((opCode >> 8) & 0x7F);
|
||||
|
||||
pDest += skip;
|
||||
|
||||
// Literal Data
|
||||
for (int idx = 0; idx < data.size; ++idx)
|
||||
{
|
||||
*pDest++ = data.pData[ idx ];
|
||||
}
|
||||
|
||||
// Clear
|
||||
data.pData += data.size;
|
||||
data.size = 0;
|
||||
|
||||
return outSize;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
static int EmitLiteral(unsigned char *pDest, DataString& data)
|
||||
{
|
||||
// Return Size
|
||||
int outSize = 2 + (int)data.size;
|
||||
|
||||
unsigned short length = (unsigned short)data.size;
|
||||
length -= 1;
|
||||
|
||||
assert(length < MAX_STRING_SIZE);
|
||||
|
||||
unsigned short opcode = length<<1;
|
||||
opcode |= 0x0001;
|
||||
|
||||
// Opcode out
|
||||
*pDest++ = (unsigned char)( opcode & 0xFF );
|
||||
*pDest++ = (unsigned char)(( opcode>>8)&0xFF);
|
||||
|
||||
// Literal Data
|
||||
for (int idx = 0; idx < data.size; ++idx)
|
||||
{
|
||||
*pDest++ = data.pData[ idx ];
|
||||
}
|
||||
|
||||
// Clear
|
||||
data.pData += data.size;
|
||||
data.size = 0;
|
||||
|
||||
return outSize;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
static int EmitReference(unsigned char *pDest, int dictionaryOffset, DataString& data)
|
||||
{
|
||||
// Return Size
|
||||
int outSize = 2 + 2;
|
||||
|
||||
unsigned short length = (unsigned short)data.size;
|
||||
length -= 1;
|
||||
|
||||
assert(length < MAX_STRING_SIZE);
|
||||
|
||||
unsigned short opcode = length<<1;
|
||||
opcode |= 0x8000;
|
||||
|
||||
// Opcode out
|
||||
*pDest++ = (unsigned char)( opcode & 0xFF );
|
||||
*pDest++ = (unsigned char)(( opcode>>8)&0xFF);
|
||||
|
||||
// Destination Address out
|
||||
unsigned short address = (unsigned short)dictionaryOffset;
|
||||
address += 0x2000; // So we don't have to add $2000 in the animation player
|
||||
|
||||
*pDest++ = (unsigned char)(address & 0xFF);
|
||||
*pDest++ = (unsigned char)((address>>8)&0xFF);
|
||||
|
||||
// Clear
|
||||
data.pData += data.size;
|
||||
data.size = 0;
|
||||
|
||||
return outSize;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Std C memcpy seems to be stopping the copy from happening, when I overlap
|
||||
// the buffer to get a pattern run copy (overlapped buffers)
|
||||
//
|
||||
static void my_memcpy(u8* pDest, u8* pSrc, int length)
|
||||
{
|
||||
while (length-- > 0)
|
||||
{
|
||||
*pDest++ = *pSrc++;
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Emit one or more Cursor Skip forward opcode
|
||||
//
|
||||
int EmitSkip(unsigned char* pDest, int skipSize)
|
||||
{
|
||||
int outSize = 0;
|
||||
int thisSkip = 0;
|
||||
|
||||
while (skipSize > 0)
|
||||
{
|
||||
outSize+=2;
|
||||
|
||||
thisSkip = skipSize;
|
||||
if (thisSkip > MAX_STRING_SIZE)
|
||||
{
|
||||
thisSkip = MAX_STRING_SIZE;
|
||||
}
|
||||
skipSize -= thisSkip;
|
||||
|
||||
|
||||
unsigned short length = (unsigned short)thisSkip;
|
||||
length -= 1;
|
||||
|
||||
assert(length < MAX_STRING_SIZE);
|
||||
|
||||
unsigned short opcode = length<<1;
|
||||
opcode |= 0x8001;
|
||||
// Opcode out
|
||||
*pDest++ = (unsigned char)( opcode & 0xFF );
|
||||
*pDest++ = (unsigned char)(( opcode>>8)&0xFF);
|
||||
}
|
||||
|
||||
return outSize;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Forcibly Emit a source Skip Opcode
|
||||
//
|
||||
// return space_left_in_Bank
|
||||
//
|
||||
int EmitSourceSkip(unsigned char*& pDest, int space_left_in_bank)
|
||||
{
|
||||
assert(space_left_in_bank >= 2);
|
||||
|
||||
*pDest++ = 0;
|
||||
*pDest++ = 0;
|
||||
space_left_in_bank-=2;
|
||||
|
||||
while (space_left_in_bank)
|
||||
{
|
||||
space_left_in_bank--;
|
||||
*pDest++ = 0;
|
||||
}
|
||||
|
||||
return 0x10000;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Conditionally shit out the Source Bank Skip
|
||||
//
|
||||
int CheckEmitSourceSkip(int checkSpace, unsigned char*& pDest, int space_left_in_bank)
|
||||
{
|
||||
if ((checkSpace+2) > space_left_in_bank)
|
||||
{
|
||||
return EmitSourceSkip(pDest, space_left_in_bank);
|
||||
}
|
||||
|
||||
space_left_in_bank -= checkSpace;
|
||||
|
||||
return space_left_in_bank;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Compress a Frame in the GSLA LZB Format
|
||||
//
|
||||
// The dictionary is also the canvas, so when we're finished the dictionary
|
||||
// buffer will match the original pSource buffer
|
||||
//
|
||||
// If they both match to begin with, we just crap out an End of Frame opcode
|
||||
//
|
||||
int LZBA_Compress(unsigned char* pDest, unsigned char* pSource, int sourceSize,
|
||||
unsigned char* pDataStart, unsigned char* pDictionary,
|
||||
int dictionarySize)
|
||||
{
|
||||
// printf("LZBA Compress %d bytes\n", sourceSize);
|
||||
|
||||
pGlobalDictionary = pDictionary;
|
||||
|
||||
// Used for bank skip opcode emission
|
||||
int bankOffset = (int)((pDest - pDataStart) & 0xFFFF);
|
||||
|
||||
// So we can track how big our compressed data ends up being
|
||||
unsigned char *pOriginalDest = pDest;
|
||||
|
||||
DataString sourceData;
|
||||
DataString dictionaryData;
|
||||
DataString candidateData;
|
||||
|
||||
// Source Data Stream - will compress until the size is zero
|
||||
sourceData.pData = pSource;
|
||||
sourceData.size = sourceSize;
|
||||
|
||||
// Dictionary is the Frame Buffer
|
||||
dictionaryData.pData = pDictionary;
|
||||
dictionaryData.size = dictionarySize;
|
||||
|
||||
// dumb last emit is a literal stuff
|
||||
bool bLastEmitIsLiteral = false;
|
||||
unsigned char* pLastLiteralDest = nullptr;
|
||||
|
||||
int lastEmittedCursorPosition = 0; // This is the default for each frame
|
||||
|
||||
int space_left_in_bank = (int)0x10000 - (int)((pDest - pDataStart)&0xFFFF);
|
||||
|
||||
space_left_in_bank = CheckEmitSourceSkip(0, pDest, space_left_in_bank);
|
||||
|
||||
for (int cursorPosition = 0; cursorPosition < dictionarySize;)
|
||||
{
|
||||
if (pSource[ cursorPosition ] != pDictionary[ cursorPosition ])
|
||||
{
|
||||
// Here is some data that has to be processed, so let's decide
|
||||
// how large of a chunk of data we're looking at here
|
||||
|
||||
// Do we need to emit a Skip opcode?, compare cursor to last emit
|
||||
// and emit a skip command if we need it (I'm going want a gap of
|
||||
// at least 3 bytes? before we call it the end
|
||||
int skipSize = cursorPosition - lastEmittedCursorPosition;
|
||||
|
||||
if (skipSize)
|
||||
{
|
||||
int numSkips = (skipSize / MAX_STRING_SIZE) + 1;
|
||||
|
||||
space_left_in_bank = CheckEmitSourceSkip(2 * numSkips, pDest, space_left_in_bank);
|
||||
|
||||
// We need to Skip
|
||||
pDest += EmitSkip(pDest, skipSize);
|
||||
bLastEmitIsLiteral = false;
|
||||
lastEmittedCursorPosition = cursorPosition;
|
||||
}
|
||||
|
||||
int tempCursorPosition = cursorPosition;
|
||||
int gapCount = 0;
|
||||
for (; tempCursorPosition < dictionarySize; ++tempCursorPosition)
|
||||
{
|
||||
if (pSource[ tempCursorPosition ] != pDictionary[ tempCursorPosition ])
|
||||
{
|
||||
gapCount = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// if there's a small amount of matching data, let's include
|
||||
// it in the clump (try and reduce opcode emissions)
|
||||
if (gapCount >= 3)
|
||||
break;
|
||||
gapCount++;
|
||||
}
|
||||
}
|
||||
|
||||
tempCursorPosition -= gapCount;
|
||||
|
||||
// Now we know from cursorPosition to tempCursorPosition is data
|
||||
// that we want to encode, we either literally copy it, or look
|
||||
// to see if this data is already in the dictionary (so we can copy
|
||||
// it from one part of the frame buffer to another part)
|
||||
|
||||
sourceData.pData = &pSource[ cursorPosition ];
|
||||
sourceData.size = tempCursorPosition - cursorPosition;
|
||||
|
||||
#if 0 // This Works
|
||||
//-------------------------- Dump, so skip dump only
|
||||
space_left_in_bank = CheckEmitSourceSkip(2+sourceData.size, pDest, space_left_in_bank);
|
||||
|
||||
cursorPosition = AddDictionary(sourceData, cursorPosition);
|
||||
|
||||
pDest += EmitLiteral(pDest, sourceData);
|
||||
lastEmittedCursorPosition = cursorPosition;
|
||||
#endif
|
||||
|
||||
while (sourceData.size > 0)
|
||||
{
|
||||
candidateData = LongestMatch(sourceData, dictionaryData, cursorPosition);
|
||||
|
||||
// If no match, or the match is too small, then take the next byte
|
||||
// and emit as literal
|
||||
if ((0 == candidateData.size)) // || (candidateData.size < 4))
|
||||
{
|
||||
candidateData.size = 1;
|
||||
candidateData.pData = sourceData.pData;
|
||||
}
|
||||
|
||||
// Adjust source stream
|
||||
sourceData.pData += candidateData.size;
|
||||
sourceData.size -= candidateData.size;
|
||||
|
||||
// Modify the dictionary
|
||||
cursorPosition = AddDictionary(candidateData, cursorPosition);
|
||||
lastEmittedCursorPosition = cursorPosition;
|
||||
|
||||
if (candidateData.size > 3)
|
||||
{
|
||||
space_left_in_bank = CheckEmitSourceSkip(4, pDest, space_left_in_bank);
|
||||
|
||||
// Emit a dictionary reference
|
||||
pDest += (int)EmitReference(pDest, (int)(candidateData.pData - dictionaryData.pData), candidateData);
|
||||
bLastEmitIsLiteral = false;
|
||||
|
||||
}
|
||||
else if (bLastEmitIsLiteral)
|
||||
{
|
||||
// This is a problem for the source bank skip, we can't
|
||||
// concatenate if we end up injecting a source bank skip opcode
|
||||
// into the stream... what to do???, if insert, we will need to
|
||||
// do a "normal" literal emission, ugly
|
||||
|
||||
int space = CheckEmitSourceSkip(candidateData.size, pDest, space_left_in_bank);
|
||||
|
||||
if (space != (space_left_in_bank - candidateData.size))
|
||||
{
|
||||
space_left_in_bank = space-2;
|
||||
|
||||
// Emit a new literal
|
||||
pLastLiteralDest = pDest;
|
||||
pDest += EmitLiteral(pDest, candidateData);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Concatenate this literal onto the previous literal
|
||||
space_left_in_bank = space;
|
||||
pDest += ConcatLiteral(pLastLiteralDest, candidateData);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
space_left_in_bank = CheckEmitSourceSkip(2 + candidateData.size, pDest, space_left_in_bank);
|
||||
|
||||
// Emit a new literal
|
||||
pLastLiteralDest = pDest;
|
||||
bLastEmitIsLiteral = true;
|
||||
pDest += EmitLiteral(pDest, candidateData);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// no change
|
||||
cursorPosition++;
|
||||
}
|
||||
}
|
||||
|
||||
space_left_in_bank = CheckEmitSourceSkip(2, pDest, space_left_in_bank);
|
||||
|
||||
// Emit the End of Frame Opcode
|
||||
*pDest++ = 0x02;
|
||||
*pDest++ = 0x00;
|
||||
|
||||
for (int idx = 0; idx < dictionarySize; ++idx)
|
||||
{
|
||||
if (pSource[ idx ] != pDictionary[ idx ])
|
||||
{
|
||||
assert(0);
|
||||
}
|
||||
}
|
||||
|
||||
return (int)(pDest - pOriginalDest);
|
||||
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
21
source/lzb.h
Normal file
21
source/lzb.h
Normal file
@ -0,0 +1,21 @@
|
||||
//
|
||||
// LZB Encode
|
||||
//
|
||||
#ifndef LZB_H
|
||||
#define LZB_H
|
||||
|
||||
//
|
||||
// returns the size of data saved into the pDest Buffer
|
||||
//
|
||||
int LZB_Compress(unsigned char* pDest, unsigned char* pSource, int sourceSize);
|
||||
int Old_LZB_Compress(unsigned char* pDest, unsigned char* pSource, int sourceSize);
|
||||
|
||||
//
|
||||
// LZB Compressor that uses GSLA Opcodes while encoding
|
||||
//
|
||||
int LZBA_Compress(unsigned char* pDest, unsigned char* pSource, int sourceSize,
|
||||
unsigned char* pDataStart, unsigned char* pDictionary,
|
||||
int dictionarySize);
|
||||
|
||||
#endif // LZB_H
|
||||
|
157
source/main.cpp
Normal file
157
source/main.cpp
Normal file
@ -0,0 +1,157 @@
|
||||
//
|
||||
// GSLA - GS LZB Animation Tool
|
||||
//
|
||||
// Look in gsla_file.cpp/h for more information about the file format
|
||||
//
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string>
|
||||
|
||||
#include "c2_file.h"
|
||||
#include "gsla_file.h"
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
static void helpText()
|
||||
{
|
||||
printf("GSLA - v1.0\n");
|
||||
printf("--------------\n");
|
||||
printf("GS Lzb Animation Creation Tool\n");
|
||||
printf("\n");
|
||||
printf("\ngsla [options] <input_file> <outfile>\n");
|
||||
printf("\n\n There are no [options] yet\n");
|
||||
printf("Converts from C2 to GSLA\n");
|
||||
|
||||
exit(-1);
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Local helper functions
|
||||
|
||||
static std::string toLower(const std::string s)
|
||||
{
|
||||
std::string result = s;
|
||||
|
||||
for (int idx = 0; idx < result.size(); ++idx)
|
||||
{
|
||||
result[ idx ] = (char)tolower(result[idx]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Case Insensitive
|
||||
static bool endsWith(const std::string& S, const std::string& SUFFIX)
|
||||
{
|
||||
bool bResult = false;
|
||||
|
||||
std::string s = toLower(S);
|
||||
std::string suffix = toLower(SUFFIX);
|
||||
|
||||
bResult = s.rfind(suffix) == (s.size()-suffix.size());
|
||||
|
||||
return bResult;
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
char* pInfilePath = nullptr;
|
||||
char* pOutfilePath = nullptr;
|
||||
|
||||
|
||||
// index 0 is the executable name
|
||||
|
||||
if (argc < 2) helpText();
|
||||
|
||||
for (int idx = 1; idx < argc; ++idx )
|
||||
{
|
||||
char* arg = argv[ idx ];
|
||||
|
||||
if ('-' == arg[0])
|
||||
{
|
||||
// Parse as an option
|
||||
// Currently I have no options, so I'll just skip
|
||||
}
|
||||
else if (nullptr == pInfilePath)
|
||||
{
|
||||
// Assume the first non-option is an input file path
|
||||
pInfilePath = argv[ idx ];
|
||||
}
|
||||
else if (nullptr == pOutfilePath)
|
||||
{
|
||||
// Assume second non-option is an output file path
|
||||
pOutfilePath = argv[ idx ];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Oh Crap, we have a non-option, but we don't know what to do with
|
||||
// it
|
||||
printf("ERROR: Invalid option, Arg %d = %s\n\n", idx, argv[ idx ]);
|
||||
helpText();
|
||||
}
|
||||
}
|
||||
|
||||
if (pInfilePath)
|
||||
{
|
||||
// See what we can do with the input file path
|
||||
// could be a .gsla file, for a .c2 file, or maybe a series of .c1 files
|
||||
if (endsWith(pInfilePath, ".c2") || endsWith(pInfilePath, "#c20000"))
|
||||
{
|
||||
// It's a C2 file
|
||||
|
||||
printf("Loading C2 File %s\n", pInfilePath);
|
||||
|
||||
C2File c2data( pInfilePath );
|
||||
|
||||
int frameCount = c2data.GetFrameCount();
|
||||
|
||||
if (frameCount < 1)
|
||||
{
|
||||
// c2 file can't be valid, if there are no frames
|
||||
printf("C2 File seems invalid.\n");
|
||||
helpText();
|
||||
}
|
||||
|
||||
if (pOutfilePath)
|
||||
{
|
||||
const std::vector<unsigned char*>& c1Datas = c2data.GetPixelMaps();
|
||||
|
||||
printf("Saving %s with %d frames\n", pOutfilePath, (int)c1Datas.size());
|
||||
|
||||
GSLAFile anim(320,200, 0x8000);
|
||||
|
||||
anim.AddImages(c1Datas);
|
||||
|
||||
anim.SaveToFile(pOutfilePath);
|
||||
|
||||
#if 1
|
||||
{
|
||||
// Verify the conversion is good
|
||||
// Load the file back in
|
||||
GSLAFile verify(pOutfilePath);
|
||||
|
||||
const std::vector<unsigned char *> &frames = verify.GetPixelMaps();
|
||||
|
||||
for (int idx = 0; idx < frames.size(); ++idx)
|
||||
{
|
||||
int result = memcmp(c1Datas[idx % c1Datas.size()], frames[idx], verify.GetFrameSize());
|
||||
printf("Verify Frame %d - %s\n", idx, result ? "Failed" : "Good");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
helpText();
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
31
vcxproj/gsla.sln
Normal file
31
vcxproj/gsla.sln
Normal file
@ -0,0 +1,31 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.30225.117
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "gsla", "gsla.vcxproj", "{08214015-2B09-4ED2-ACD4-93031A939CF1}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{08214015-2B09-4ED2-ACD4-93031A939CF1}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{08214015-2B09-4ED2-ACD4-93031A939CF1}.Debug|x64.Build.0 = Debug|x64
|
||||
{08214015-2B09-4ED2-ACD4-93031A939CF1}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{08214015-2B09-4ED2-ACD4-93031A939CF1}.Debug|x86.Build.0 = Debug|Win32
|
||||
{08214015-2B09-4ED2-ACD4-93031A939CF1}.Release|x64.ActiveCfg = Release|x64
|
||||
{08214015-2B09-4ED2-ACD4-93031A939CF1}.Release|x64.Build.0 = Release|x64
|
||||
{08214015-2B09-4ED2-ACD4-93031A939CF1}.Release|x86.ActiveCfg = Release|Win32
|
||||
{08214015-2B09-4ED2-ACD4-93031A939CF1}.Release|x86.Build.0 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {922EAE98-7805-4172-A57D-9B441077B035}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
156
vcxproj/gsla.vcxproj
Normal file
156
vcxproj/gsla.vcxproj
Normal file
@ -0,0 +1,156 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>16.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{08214015-2b09-4ed2-acd4-93031a939cf1}</ProjectGuid>
|
||||
<RootNamespace>gsla</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\source\bctypes.h" />
|
||||
<ClInclude Include="..\source\c2_file.h" />
|
||||
<ClInclude Include="..\source\gsla_file.h" />
|
||||
<ClInclude Include="..\source\lzb.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\source\c2_file.cpp" />
|
||||
<ClCompile Include="..\source\gsla_file.cpp" />
|
||||
<ClCompile Include="..\source\lzb.cpp" />
|
||||
<ClCompile Include="..\source\main.cpp" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
41
vcxproj/gsla.vcxproj.filters
Normal file
41
vcxproj/gsla.vcxproj.filters
Normal file
@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;c++;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\source\bctypes.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\source\c2_file.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\source\lzb.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\source\gsla_file.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\source\c2_file.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\source\main.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\source\lzb.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\source\gsla_file.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
Loading…
Reference in New Issue
Block a user