From ec32cb884acd5935ccf6d2760977b740765b00a5 Mon Sep 17 00:00:00 2001 From: "JASON-6700K\\jandersen" Date: Thu, 9 Jul 2020 17:35:34 -0400 Subject: [PATCH] import: source code for the tool, WIP --- source/bctypes.h | 48 +++++++++++ source/c2_file.cpp | 143 ++++++++++++++++++++++++++++++ source/c2_file.h | 73 ++++++++++++++++ source/lzb.cpp | 210 +++++++++++++++++++++++++++++++++++++++++++++ source/lzb.h | 14 +++ source/main.cpp | 148 ++++++++++++++++++++++++++++++++ 6 files changed, 636 insertions(+) create mode 100644 source/bctypes.h create mode 100644 source/c2_file.cpp create mode 100644 source/c2_file.h create mode 100644 source/lzb.cpp create mode 100644 source/lzb.h create mode 100644 source/main.cpp diff --git a/source/bctypes.h b/source/bctypes.h new file mode 100644 index 0000000..7a55f9d --- /dev/null +++ b/source/bctypes.h @@ -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 + + diff --git a/source/c2_file.cpp b/source/c2_file.cpp new file mode 100644 index 0000000..6dab4b6 --- /dev/null +++ b/source/c2_file.cpp @@ -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 + +// 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 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; + } +} + +//------------------------------------------------------------------------------ + diff --git a/source/c2_file.h b/source/c2_file.h new file mode 100644 index 0000000..954321f --- /dev/null +++ b/source/c2_file.h @@ -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 + +#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& GetPixelMaps() { return m_pC1PixelMaps; } + +private: + + int m_widthPixels; // Width of image in pixels + int m_heightPixels; // Height of image in pixels + + std::vector m_pC1PixelMaps; + +}; + + +#endif // C2_FILE_H + diff --git a/source/lzb.cpp b/source/lzb.cpp new file mode 100644 index 0000000..57a26b6 --- /dev/null +++ b/source/lzb.cpp @@ -0,0 +1,210 @@ +// +// LZB Encode / Decode +// +#include "lzb.h" + +#include +#include + +#include "bctypes.h" + +#define DICTIONARY_SIZE (32 * 1024) +// +// Yes This is a 32K Buffer, of bytes, with no structure to it +// +static unsigned char Dictionary[ DICTIONARY_SIZE ]; + +static int AddDictionary(const std::vector&data, int dictionarySize); +static int EmitLiteral(unsigned char *pDest, std::vector& data); +static int EmitReference(unsigned char *pDest, int dictionaryOffset, std::vector& data); +static int DictionaryMatch(const std::vector& data, int dictionarySize); + +int LZB_Compress(unsigned char* pDest, unsigned char* pSource, int sourceSize) +{ + printf("LZB_Compress %d bytes\n", sourceSize); + + // anything less than 3 bytes, is going to be a literal match + + int processedBytes = 0; + int bytesInDictionary = 0; + int bytesEmitted = 0; + + std::vector candidate_data; + + while (processedBytes < sourceSize) + { + unsigned char byte_data = pSource[ processedBytes++ ]; + candidate_data.push_back(byte_data); + + // The dictionary only contains bytes that have been emitted, so we + // can't add this byte until we've emitted it? + + if (candidate_data.size() < 3) continue; + + if (DictionaryMatch(candidate_data, bytesInDictionary) < 0) + { + // Was there a dictionary match + std::vector prev_data = candidate_data; + prev_data.pop_back(); + + int MatchOffset = DictionaryMatch(prev_data, bytesInDictionary); + + if ((MatchOffset >= 0) && prev_data.size() > 2) + { + bytesInDictionary = AddDictionary(prev_data, bytesInDictionary); + bytesEmitted += EmitReference(pDest + bytesEmitted, MatchOffset, prev_data); + candidate_data[0] = candidate_data[ candidate_data.size() - 1 ]; + candidate_data.resize(1); + } + else + { + // Add Dictionary + bytesInDictionary = AddDictionary(candidate_data, bytesInDictionary); + bytesEmitted += EmitLiteral(pDest + bytesEmitted, candidate_data); + } + } + } + + if (candidate_data.size() > 0) + { + // Emit as a literal? (we have 1 more chance here for a match + // Add Dictionary + bytesInDictionary = AddDictionary(candidate_data, bytesInDictionary); + bytesEmitted += EmitLiteral(pDest + bytesEmitted, candidate_data); + } + + return bytesEmitted; +} + +//------------------------------------------------------------------------------ +// Return new dictionarySize +static int AddDictionary(const std::vector&data, int dictionarySize) +{ + int dataIndex = 0; + + while ((dictionarySize < DICTIONARY_SIZE) && (dataIndex < data.size())) + { + Dictionary[ dictionarySize++ ] = data[ dataIndex++ ]; + } + + return dictionarySize; +} + +//------------------------------------------------------------------------------ +// +// Return offset into dictionary where the string matches +// +// -1 means, no match +// +static int DictionaryMatch(const std::vector& data, int dictionarySize) +{ + if(dictionarySize < data.size()) + { + return -1; + } + + int dictionaryOffset = 0; + + 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[ dataIdx ] == Dictionary[ idx + dataIdx ]) + continue; + + bMatch = false; + break; + } + + if (bMatch) + { + result = idx; + break; + } + } + + return result; +} + +//------------------------------------------------------------------------------ + +static int EmitLiteral(unsigned char *pDest, std::vector& data) +{ + // Return Size + int outSize = 2 + (int)data.size(); + + // Opcode + *pDest++ = (unsigned char)(data.size() & 0xFF); + *pDest++ = (unsigned char)((data.size() >> 8) & 0x7F); + + // Literal Data + for (int idx = 0; idx < data.size(); ++idx) + { + *pDest++ = data[ idx ]; + } + + data.clear(); + + return outSize; +} + +//------------------------------------------------------------------------------ + +static int EmitReference(unsigned char *pDest, int dictionaryOffset, std::vector& data) +{ + // Return Size + int outSize = 2 + 2; + + // Opcode + *pDest++ = (unsigned char)(data.size() & 0xFF); + *pDest++ = (unsigned char)((data.size() >> 8) & 0x7F) | 0x80; + + *pDest++ = (unsigned char)(dictionaryOffset & 0xFF); + *pDest++ = (unsigned char)((dictionaryOffset>>8) & 0xFF); + + data.clear(); + + return outSize; +} + +//------------------------------------------------------------------------------ +// +// Simple Decompress, for validation +// +void LZB_Decompress(unsigned char* pDest, unsigned char* pSource, int destSize) +{ + int decompressedBytes = 0; + + while (decompressedBytes < destSize) + { + u16 opcode = *pSource++; + opcode |= ((u16)(*pSource++))<<8; + + if (opcode & 0x8000) + { + // Dictionary + opcode &= 0x7FFF; + + // Dictionary Copy from the output stream + u16 offset = *pSource++; + offset |= ((u16)(*pSource++))<<8; + + memcpy(&pDest[ decompressedBytes ], &pDest[ offset ], opcode); + decompressedBytes += opcode; + } + else + { + // Literal Copy, from compressed stream + memcpy(&pDest[ decompressedBytes ], pSource, opcode); + decompressedBytes += opcode; + pSource += opcode; + } + } +} + +//------------------------------------------------------------------------------ + diff --git a/source/lzb.h b/source/lzb.h new file mode 100644 index 0000000..8284aa2 --- /dev/null +++ b/source/lzb.h @@ -0,0 +1,14 @@ +// +// LZB Encode / Decode +// +#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); +void LZB_Decompress(unsigned char* pDest, unsigned char* pSource, int destSize); + +#endif // LZB_H + diff --git a/source/main.cpp b/source/main.cpp new file mode 100644 index 0000000..fcb0d00 --- /dev/null +++ b/source/main.cpp @@ -0,0 +1,148 @@ +// +// GSLA - GS LZB Animation Tool +// + +#include +#include +#include + +#include "c2_file.h" +#include "lzb.h" + +//------------------------------------------------------------------------------ +static void helpText() +{ + printf("GSLA - v0.0\n"); + printf("--------------\n"); + printf("GS Lzb Animation Tool\n"); + printf("\n"); + printf("\ngsla [options] \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(); + } + + const std::vector& c1Datas = c2data.GetPixelMaps(); + + unsigned char workbuffer[64*1024]; + + for (int idx = 0; idx < frameCount; ++idx) + { + int compressedSize = LZB_Compress(workbuffer, c1Datas[ idx ], 32 * 1024); + printf("compressedSize = %d\n", compressedSize); + + unsigned char validationBuffer[ 32 * 1024 ]; + + LZB_Decompress(validationBuffer, workbuffer, 32 * 1024); + + if (0 == memcmp(c1Datas[ idx ], validationBuffer, 32*1024)) + { + printf("Decompression Validated\n"); + } + else + { + printf("Decompression Corrupted\n"); + } + + } + } + + } + else + { + helpText(); + } + + + return 0; +} +