Unpletter initial implementation.

This commit is contained in:
Sylvain Glaize 2024-02-21 00:30:22 +01:00
commit 9378eadf71
9 changed files with 995 additions and 0 deletions

66
.clang-format Normal file
View File

@ -0,0 +1,66 @@
# Generated from CLion C/C++ Code Style settings
BasedOnStyle: LLVM
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: None
AlignOperands: Align
AllowAllArgumentsOnNextLine: false
AllowAllConstructorInitializersOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: Always
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Never
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterReturnType: None
AlwaysBreakTemplateDeclarations: Yes
BreakBeforeBraces: Custom
BraceWrapping:
AfterCaseLabel: false
AfterClass: true
AfterControlStatement: Always
AfterEnum: true
AfterFunction: true
AfterNamespace: true
AfterUnion: true
BeforeCatch: true
BeforeElse: true
IndentBraces: false
SplitEmptyFunction: false
SplitEmptyRecord: true
BreakBeforeBinaryOperators: None
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeColon
BreakInheritanceList: BeforeColon
ColumnLimit: 0
CompactNamespaces: false
ContinuationIndentWidth: 8
IndentCaseLabels: true
IndentPPDirectives: None
IndentWidth: 4
KeepEmptyLinesAtTheStartOfBlocks: true
MaxEmptyLinesToKeep: 1
NamespaceIndentation: All
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PointerAlignment: Left
ReflowComments: false
SpaceAfterCStyleCast: true
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 0
SpacesInAngles: false
SpacesInCStyleCastParentheses: false
SpacesInContainerLiterals: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
TabWidth: 4
UseTab: Never

8
CMakeLists.txt Normal file
View File

@ -0,0 +1,8 @@
cmake_minimum_required(VERSION 3.27)
project(pletter_uncompress CXX C)
set(CMAKE_CXX_STANDARD 23)
add_executable(pletter pletter.cpp)
add_executable(unpletter unpletter/unpletter.cpp)
add_executable(unpletter_c unpletter/unpletter_c.c)

14
license.txt Normal file
View File

@ -0,0 +1,14 @@
Pletter v0.5b - XL2S Entertainment 2008
Have fun with it, just do not harm anyone :)
Keep in mind pletter is based on software with the following license:
Copyright (c) 2002-2003 Team Bomba
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

413
pletter.cpp Normal file
View File

@ -0,0 +1,413 @@
/*
Pletter v0.5c1
XL2S Entertainment
*/
#pragma warning(disable: 4996)
#include <stdlib.h>
#include <string>
#include <iostream>
#include <cstring>
using namespace std;
unsigned char *inData;
unsigned maxlen[7] = { 128, 128+128, 512+128, 1024+128, 2048+128, 4096+128, 8192+128 };
unsigned varcost[65536];
struct metadata
{
unsigned reeks;
unsigned cpos[7], clen[7];
} *meta;
struct pakdata
{
unsigned cost, mode, mlen;
} *p[7];
string sourcefilename, destfilename;
bool savelength = false;
unsigned inLength, offset;
struct saves
{
unsigned char *outData;
int ep, dp, p, e;
void init()
{
ep = dp = p = e = 0;
outData = new unsigned char[inLength * 2];
}
void add0()
{
if (p == 0)
claimevent();
e *= 2;
++p;
if (p == 8)
addevent();
}
void add1()
{
if (p == 0)
claimevent();
e *= 2;
++p;
++e;
if (p == 8)
addevent();
}
void addbit(int b)
{
if (b)
add1();
else
add0();
}
void add3(int b)
{
addbit(b & 4);
addbit(b & 2);
addbit(b & 1);
}
void addvar(int i)
{
int j = 32768;
while (!(i & j))
j /= 2;
do
{
if (j == 1)
{
add0();
return;
}
j/=2;
add1();
if (i & j)
add1();
else
add0();
}
while (1);
}
void adddata(unsigned char d)
{
outData[dp++] = d;
}
void addevent()
{
outData[ep] = e;
e = p = 0;
}
void claimevent()
{
ep = dp;
++dp;
}
void done()
{
if (p != 0)
{
while (p != 8)
{
e *= 2;
++p;
}
addevent();
}
FILE *file;
if (!(file = fopen(destfilename.c_str(), "wb")))
{
cout << "Error writing file!\n";
exit(1);
}
fwrite(outData, 1, dp, file);
fclose(file);
cout << " " << destfilename << ": " << inLength << " -> " << dp << endl;
}
} s;
void loadfile(string sourcefilename)
{
FILE *file;
if ((file = fopen(sourcefilename.c_str(), "rb")) == NULL)
{
cout << "Error opening file:" << sourcefilename << endl;
exit(1);
}
if (!inLength)
{
fseek(file, 0, SEEK_END);
inLength = ftell(file) - offset;
}
fseek(file, offset, SEEK_SET);
inData = new unsigned char[inLength + 1];
meta = new metadata[inLength + 1];
if (!fread(inData, inLength, 1, file))
{
cout << "Filesize error" << endl;
exit(1);
}
inData[inLength] = 0;
fclose(file);
}
void initvarcost()
{
int v=1, b=1, r=1;
while (r != 65536)
{
for (int j = 0; j != r; ++j)
varcost[v++] = b;
b += 2;
r *= 2;
}
}
void createmetadata()
{
unsigned i, j;
unsigned *last = new unsigned[65536];
memset(last, -1, 65536 * sizeof(unsigned));
unsigned *prev = new unsigned[inLength + 1];
for (i = 0; i != inLength; ++i)
{
meta[i].cpos[0] = meta[i].clen[0] = 0;
prev[i] = last[inData[i] + inData[i + 1] * 256];
last[inData[i] + inData[i + 1] * 256] = i;
}
unsigned r=-1, t=0;
for (i = inLength - 1; i != -1; --i)
if (inData[i] == r)
meta[i].reeks = ++t;
else
{
r = inData[i];
meta[i].reeks = t = 1;
}
for (int bl = 0; bl != 7; ++bl)
{
for (i = 0; i < inLength; ++i)
{
unsigned l, p;
p = i;
if (bl)
{
meta[i].clen[bl] = meta[i].clen[bl - 1];
meta[i].cpos[bl] = meta[i].cpos[bl - 1];
p = i - meta[i].cpos[bl];
}
while((p = prev[p]) != -1)
{
if (i - p > maxlen[bl])
break;
l = 0;
while (inData[p + l] == inData[i + l] && (i + l) < inLength)
{
if (meta[i + l].reeks > 1)
{
if ((j = meta[i + l].reeks) > meta[p + l].reeks)
j = meta[p + l].reeks;
l+=j;
}
else
++l;
}
if (l > meta[i].clen[bl])
{
meta[i].clen[bl] = l;
meta[i].cpos[bl] = i - p;
}
}
}
cout << ".";
}
cout << " ";
// delete [] prev;
// delete [] last;
}
int getlen(pakdata *p, unsigned q)
{
unsigned i, j, cc, ccc, kc, kmode, kl;
p[inLength].cost=0;
for (i = inLength - 1; i != -1; --i)
{
kmode = 0;
kl = 0;
kc = 9 + p[i + 1].cost;
j=meta[i].clen[0];
while (j > 1)
{
cc = 9 + varcost[j - 1] + p[i + j].cost;
if (cc < kc)
{
kc = cc;
kmode = 1;
kl = j;
}
--j;
}
j = meta[i].clen[q];
if (q == 1)
ccc = 9;
else
ccc = 9 + q;
while (j > 1)
{
cc = ccc + varcost[j - 1] + p[i + j].cost;
if (cc < kc)
{
kc = cc;
kmode = 2;
kl = j;
}
--j;
}
p[i].cost=kc; p[i].mode=kmode; p[i].mlen=kl;
}
return p[0].cost;
}
void save(pakdata *p, unsigned q)
{
s.init();
unsigned i, j;
if (savelength)
{
s.adddata(inLength & 255);
s.adddata(inLength >> 8);
}
s.add3(q-1);
s.adddata(inData[0]);
i=1;
while(i<inLength)
{
switch (p[i].mode)
{
case 0:
s.add0();
s.adddata(inData[i]);
++i;
break;
case 1:
s.add1();
s.addvar(p[i].mlen - 1);
j = meta[i].cpos[0] - 1;
if (j > 127)
cout << "-j>128-";
s.adddata(j);
i += p[i].mlen;
break;
case 2:
s.add1();
s.addvar(p[i].mlen - 1);
j = meta[i].cpos[q] - 1;
if (j < 128)
cout << "-j<128-";
j -= 128;
s.adddata(128 | j & 127);
switch (q)
{
case 6: s.addbit(j & 4096);
case 5: s.addbit(j & 2048);
case 4: s.addbit(j & 1024);
case 3: s.addbit(j & 512);
case 2: s.addbit(j & 256);
s.addbit(j & 128);
case 1:
break;
default:
cout << "-2-";
break;
}
i += p[i].mlen;
break;
default:
cout << "-?-";
break;
}
}
for (i = 0; i != 34; ++i)
s.add1();
s.done();
}
int main(int argc, char *argv[])
{
if (argc == 1)
cout << endl;
cout << "Pletter v0.5c2 - www.xl2s.tk" << endl;
if (argc == 1)
{
cout << "\nUsage:\npletter [-s[ave_length]] sourcefile [[offset [inLength]] [destinationfile]]\n";
exit(0);
}
offset = 0;
inLength = 0;
int i = 1;
if (argv[i][0] == '-')
{
savelength = (argv[i][1] == 's') || (argv[i][1] == 'S');
++i;
}
if (argv[i])
sourcefilename = argv[i++];
if (argv[i] && isdigit(argv[i][0]))
{
offset=atoi(argv[i++]);
if (argv[i] && isdigit(argv[i][0]))
inLength = atoi(argv[i++]);
}
if (argv[i])
destfilename = argv[i++];
if (!sourcefilename[0])
{
cout << "No inputfile" << endl;
exit(1);
}
if (!destfilename[0])
destfilename = sourcefilename + ".plet5";
loadfile(sourcefilename);
initvarcost();
createmetadata();
int minlen = inLength * 1000;
int minbl = 0;
for(i = 1; i != 7; ++i)
{
p[i] = new pakdata[inLength + 1];
int l = getlen(p[i], i);
if (l < minlen && i)
{
minlen = l;
minbl = i;
}
cout << ".";
}
save(p[minbl], minbl);
#ifdef _DEBUG
cin.get();
#endif
return 0;
}
//eof

57
pletter.txt Normal file
View File

@ -0,0 +1,57 @@
Pletter v0.5c1 - XL2S Entertainment 2008
-==============---==================------
Pletter is a PC based compressor with an MSX based decompressor.
What has changed
-================-
version 0.5c1
- Now also with working pletter.exe.
version 0.5c
- The size of the data can be saved in the file.
- Version 0.5c uses a smaller maximum look back buffer size. This means the new unpacker cannot unpack all files packed with version 0.5b; old 0.5 unpackers can unpack 0.5c files though. Unless you let Pletter store the filesize in the file.
version 0.5a
- The size of the lookbackbuffer is stored in the compressed data.
How to compress
-===============-
The compressor only works on PC (or any other system that is able to get the source compiled):
pletter [-s] <source file> [[offset [length]] [<destination file>]]
The sourcefile is the file to compress. When the destination file is omitted, it will be constructed the following way: sourcefilename+.plet5
So: pletter xl2slogo.sc2
Will (hopefully ;) result in a file called: xl2slogo.sc2.plet5
It is possible to compress a part of a file by specifying the start and, optionally, the length:
pletter fun.sc5 7
will skip the bin header and result in a file called fun.sc5.plet5.
The <20>s option makes Pletter save the length of the original data at the beginning of the file. If you use this option, you have to modify the unpack routine.
How to decompress
-=================-
- Include unpack.asm in your sourcefile.
- Include the compressed data in your sourcefile.
- Call unpack with in HL a pointer to the compressed data, and in DE the destination.
Example:
<some code>
ld hl,data
ld de,8000h
call unpack
<more code>
include unpack.asm ; include the unpacker
data
incbin data.gfx.plet5 ; the data
If you used to option to include the length of the uncompressed data in the file, you should modify the unpack routine to skip it, or skip it yourself by using ld hl,data+2, or even just incbin "data.gfx.plet5",2.

141
unpack.asm Normal file
View File

@ -0,0 +1,141 @@
; pletter v0.5c msx unpacker
; call unpack with hl pointing to some pletter5 data, and de pointing to the destination.
; changes all registers
; define lengthindata when the original size is written in the pletter data
; define LENGTHINDATA
module pletter
macro GETBIT
add a,a
call z,getbit
endmacro
macro GETBITEXX
add a,a
call z,getbitexx
endmacro
@unpack
ifdef LENGTHINDATA
inc hl
inc hl
endif
ld a,(hl)
inc hl
exx
ld de,0
add a,a
inc a
rl e
add a,a
rl e
add a,a
rl e
rl e
ld hl,modes
add hl,de
ld e,(hl)
ld ixl,e
inc hl
ld e,(hl)
ld ixh,e
ld e,1
exx
ld iy,loop
literal
ldi
loop
GETBIT
jr nc,literal
exx
ld h,d
ld l,e
getlen
GETBITEXX
jr nc,.lenok
.lus
GETBITEXX
adc hl,hl
ret c
GETBITEXX
jr nc,.lenok
GETBITEXX
adc hl,hl
ret c
GETBITEXX
jp c,.lus
.lenok
inc hl
exx
ld c,(hl)
inc hl
ld b,0
bit 7,c
jp z,offsok
jp ix
mode6
GETBIT
rl b
mode5
GETBIT
rl b
mode4
GETBIT
rl b
mode3
GETBIT
rl b
mode2
GETBIT
rl b
GETBIT
jr nc,offsok
or a
inc b
res 7,c
offsok
inc bc
push hl
exx
push hl
exx
ld l,e
ld h,d
sbc hl,bc
pop bc
ldir
pop hl
jp iy
getbit
ld a,(hl)
inc hl
rla
ret
getbitexx
exx
ld a,(hl)
inc hl
exx
rla
ret
modes
word offsok
word mode2
word mode3
word mode4
word mode5
word mode6
endmodule
;eof

5
unpletter/README.md Normal file
View File

@ -0,0 +1,5 @@
Unpletter is a C and C++ version of the unpacker for the Pletter compression scheme.
By Sylvain Glaize, 2024.
Distributed as the same license as the Pletter packer. See license.txt on the root folder.

145
unpletter/unpletter.cpp Normal file
View File

@ -0,0 +1,145 @@
#include <fstream>
#include <iostream>
#include <vector>
using data_type = unsigned char;
class Uncompressor
{
public:
explicit Uncompressor(const std::vector<unsigned char>& inData)
: compressedData(inData)
{
std::vector<unsigned char> output;
int q_value = (getBit() << 2 | getBit() << 1 | getBit()) + 1;
data_type first_byte = getByte();
output.push_back(first_byte);
dataPosition = 2;
while (dataPosition < inData.size())
{
if (getBit())// Back reference
{
data_type length = getInterlacedEliasGamma() + 1;
if (length == 255)
{
break;
}
int offset = getByte();
if (offset & 0x80)
{
offset = offset & 0x7F;
switch(q_value)
{
case 6:
offset = offset | (getBit() << 12);
case 5:
offset = offset | (getBit() << 11);
case 4:
offset = offset | (getBit() << 10);
case 3:
offset = offset | (getBit() << 9);
case 2:
offset = offset | (getBit() << 8);
offset = offset | (getBit() << 7);
case 1:
break;
default:
std::cout << "Invalid mode: " << q_value << std::endl;
break;
}
offset += 128;
}
offset += 1;
for (int i = 0; i < length; i++)
{
output.push_back(output[output.size() - offset]);
}
}
else// Literal data
{
output.push_back(getByte());
}
}
for (unsigned char byte: output)
{
std::cout << byte;
}
}
private:
int dataPosition = 0;
int varPosition = 0;
int bitForVarPosition = 7;
const std::vector<unsigned char>& compressedData;
data_type getByte()
{
return compressedData[dataPosition++];
}
data_type getBit()
{
if (bitForVarPosition == 7)
{
varPosition = dataPosition;
dataPosition = varPosition + 1;
}
data_type bit = (compressedData[varPosition] >> bitForVarPosition) & 1;
bitForVarPosition--;
if (bitForVarPosition == -1)
{
bitForVarPosition = 7;
}
return bit;
}
data_type getInterlacedEliasGamma()
{
data_type value = 1;
while (getBit())
{
value = (value << 1) | getBit();
}
return value;
}
};
int main(int argc, char* argv[])
{
if (argc < 2)
{
std::cout << "Usage: " << argv[0] << " <compressed file>" << std::endl;
return 1;
}
std::ifstream file(argv[1], std::ios::binary | std::ios::ate);
if (!file)
{
std::cout << "Error reading file!" << std::endl;
return 1;
}
std::streamsize length = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<unsigned char> compressedData(length);
if (!file.read(reinterpret_cast<char*>(compressedData.data()), length))
{
std::cout << "Error reading file!" << std::endl;
return 1;
}
Uncompressor data(compressedData);
return 0;
}

146
unpletter/unpletter_c.c Normal file
View File

@ -0,0 +1,146 @@
#include <stdio.h>
#include <stdlib.h>
typedef unsigned char data_type;
struct Uncompressor {
unsigned char* compressedData;
int dataPosition;
int varPosition;
int bitForVarPosition;
};
data_type getByte(struct Uncompressor* uncompressor)
{
return uncompressor->compressedData[uncompressor->dataPosition++];
}
data_type getBit(struct Uncompressor* uncompressor)
{
if (uncompressor->bitForVarPosition == 7)
{
uncompressor->varPosition = uncompressor->dataPosition;
uncompressor->dataPosition = uncompressor->varPosition + 1;
}
data_type bit = (uncompressor->compressedData[uncompressor->varPosition] >> uncompressor->bitForVarPosition) & 1;
uncompressor->bitForVarPosition--;
if (uncompressor->bitForVarPosition == -1)
{
uncompressor->bitForVarPosition = 7;
}
return bit;
}
data_type getInterlacedEliasGamma(struct Uncompressor* uncompressor)
{
data_type value = 1;
while (getBit(uncompressor))
{
value = (value << 1) | getBit(uncompressor);
}
return value;
}
void uncompress(struct Uncompressor* uncompressor, size_t in_data_length)
{
unsigned char* output = (unsigned char*) malloc(in_data_length * 3);// * 3 is an arbitrary number...
size_t output_position = 0;
int q_value = (getBit(uncompressor) << 2 | getBit(uncompressor) << 1 | getBit(uncompressor)) + 1;
data_type first_byte = getByte(uncompressor);
output[output_position++] = first_byte;
while (uncompressor->dataPosition < in_data_length)
{
if (getBit(uncompressor))
{
data_type length = getInterlacedEliasGamma(uncompressor) + 1;
if (length == 255)
{
break;
}
int offset = getByte(uncompressor);
if (offset & 0x80)
{
offset = offset & 0x7F;
switch (q_value)
{
case 6:
offset = offset | (getBit(uncompressor) << 12);
case 5:
offset = offset | (getBit(uncompressor) << 11);
case 4:
offset = offset | (getBit(uncompressor) << 10);
case 3:
offset = offset | (getBit(uncompressor) << 9);
case 2:
offset = offset | (getBit(uncompressor) << 8);
offset = offset | (getBit(uncompressor) << 7);
case 1:
break;
default:
printf("Invalid mode: %d\n", q_value);
break;
}
offset += 128;
}
offset += 1;
for (int i = 0; i < length; i++)
{
output[output_position] = output[output_position - offset];
output_position += 1;
}
}
else
{
output[output_position++] = getByte(uncompressor);
}
}
for (int i = 0; i < output_position; i++)
{
printf("%c", output[i]);
}
free(output);
}
int main(int argc, char* argv[])
{
if (argc < 2)
{
printf("Usage: %s <compressed file>\n", argv[0]);
return 1;
}
FILE* file = fopen(argv[1], "rb");
if (!file)
{
printf("Error reading file!\n");
return 1;
}
fseek(file, 0, SEEK_END);
long length = ftell(file);
fseek(file, 0, SEEK_SET);
unsigned char* compressedData = (unsigned char*) malloc(length);
if (!fread(compressedData, length, 1, file))
{
printf("Error reading file!\n");
return 1;
}
struct Uncompressor uncompressor = {compressedData, 0, 0, 7};
uncompress(&uncompressor, length);
free(compressedData);
fclose(file);
return 0;
}