Commit 44eed477 authored by Ray's avatar Ray
Browse files

Initial commit.

parents
*.pdf filter=lfs diff=lfs merge=lfs -text
# Visual Studio
**/.vs/
**/bin/
**/obj/
*.user
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- Metadata -->
<PropertyGroup>
<Authors>Syroot</Authors>
<Copyright>(c) Syroot 2021</Copyright>
<Version>0.1.0</Version>
</PropertyGroup>
<!-- Compilation -->
<PropertyGroup>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<!-- Output -->
<PropertyGroup>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<OutputPath>$(SolutionDir)bin\$(Configuration)\</OutputPath>
<IntermediateOutputPath>$(SolutionDir)obj\$(Configuration)\</IntermediateOutputPath>
</PropertyGroup>
</Project>
MIT License
Copyright (c) 2021 Syroot
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.
# Fortress
This repository hosts utilities for working with CCR New Fortress file formats.
- **Syroot.Fortress**: .NET Core library for accessing file formats.
- **Syroot.Fortress.Compressor**: Native rewrite of the compression method used by the game (s. below).
- **Syroot.Fortress.Scratchpad**: Small app to test the library with, currently extracts all sprites and gsysdata.dat.
## Compression
New Fortress uses Intel's 2001/2002 EFI compression method as documented in `res/intelefi.pdf`, appendices H and I.
Newer versions of this compression do not seem to work. CCR apparently copy-pasted this code into their `CCompress`
class, converting global variables to instance data and modifying it further to decompress in blocks. A rewrite of this
is found in `Syroot.Fortress.Compressor`, it exports the `Decompress` method for use by other libraries and apps.

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.31702.278
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Syroot.Fortress", "src\Syroot.Fortress\Syroot.Fortress.csproj", "{0698ADBE-DD7B-41C5-B5A4-2222D63EB3EC}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Syroot.Fortress.Compressor", "src\Syroot.Fortress.Compressor\Syroot.Fortress.Compressor.vcxproj", "{1117300F-B4A8-4BC0-93C1-7C0E5B017949}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Syroot.Fortress.Scratchpad", "src\Syroot.Fortress.Scratchpad\Syroot.Fortress.Scratchpad.csproj", "{3A398CD8-4B5F-4552-B394-7F1311358BFD}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0703EE4A-0B3A-49DF-AEC6-9B1FE00087B5}"
ProjectSection(SolutionItems) = preProject
.gitattributes = .gitattributes
.gitignore = .gitignore
Directory.Build.props = Directory.Build.props
LICENSE = LICENSE
README.md = README.md
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x86 = Debug|x86
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{0698ADBE-DD7B-41C5-B5A4-2222D63EB3EC}.Debug|x86.ActiveCfg = Debug|x86
{0698ADBE-DD7B-41C5-B5A4-2222D63EB3EC}.Debug|x86.Build.0 = Debug|x86
{0698ADBE-DD7B-41C5-B5A4-2222D63EB3EC}.Release|x86.ActiveCfg = Release|x86
{0698ADBE-DD7B-41C5-B5A4-2222D63EB3EC}.Release|x86.Build.0 = Release|x86
{1117300F-B4A8-4BC0-93C1-7C0E5B017949}.Debug|x86.ActiveCfg = Debug|Win32
{1117300F-B4A8-4BC0-93C1-7C0E5B017949}.Debug|x86.Build.0 = Debug|Win32
{1117300F-B4A8-4BC0-93C1-7C0E5B017949}.Release|x86.ActiveCfg = Release|Win32
{1117300F-B4A8-4BC0-93C1-7C0E5B017949}.Release|x86.Build.0 = Release|Win32
{3A398CD8-4B5F-4552-B394-7F1311358BFD}.Debug|x86.ActiveCfg = Debug|x86
{3A398CD8-4B5F-4552-B394-7F1311358BFD}.Debug|x86.Build.0 = Debug|x86
{3A398CD8-4B5F-4552-B394-7F1311358BFD}.Release|x86.ActiveCfg = Release|x86
{3A398CD8-4B5F-4552-B394-7F1311358BFD}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5B804E54-C2EF-400B-B341-0913741EBBA1}
EndGlobalSection
EndGlobal
File added
#include "CCompress.h"
#include <cassert>
void CCompress::Decode(void* dec, void* src, uint32_t size, uint32_t osize)
{
Source = (uint8_t*)src;
OrigSize = osize;
SourPos = 0;
SourSize = size;
DestPos = 0;
Dest = (uint8_t*)dec;
CompSize = size;
BitBuffer = 0;
SubBitBuffer = 0;
BitCount = 0;
FillBuffer(0x10);
BlockSize = 0;
DecodeStart();
}
uint16_t CCompress::Pbit = EFIPBIT;
void CCompress::FillBuffer(uint16_t n)
{
BitBuffer = (uint16_t)(BitBuffer << n);
while (n > BitCount)
{
BitBuffer |= (uint16_t)(SubBitBuffer << (n = (uint16_t)(n - BitCount)));
if (CompSize > 0)
{
// Get 1 byte into SubBitBuf.
CompSize--;
SubBitBuffer = 0;
SubBitBuffer = Source[SourPos++];
BitCount = 8;
}
else
{
SubBitBuffer = 0;
BitCount = 8;
}
}
BitCount = (uint16_t)(BitCount - n);
BitBuffer |= SubBitBuffer >> BitCount;
}
uint32_t CCompress::GetBits(uint16_t n)
{
uint32_t OutBits = (uint32_t)(BitBuffer >> (BITBUFSIZ - n));
FillBuffer(n);
return OutBits;
}
uint16_t CCompress::ReadPTLength(uint16_t nn, uint16_t nbit, uint16_t is)
{
uint16_t Number;
uint16_t CharC;
uint16_t Index;
uint32_t Mask;
assert(nn <= NPT);
Number = (uint16_t)GetBits(nbit);
if (Number == 0)
{
CharC = (uint16_t)GetBits(nbit);
for (Index = 0; Index < 256; Index++)
PTTable[Index] = CharC;
for (Index = 0; Index < nn; Index++)
PTLength[Index] = 0;
return 0;
}
Index = 0;
while (Index < Number && Index < NPT)
{
CharC = (uint16_t)(BitBuffer >> (BITBUFSIZ - 3));
if (CharC == 7)
{
Mask = 1U << (BITBUFSIZ - 1 - 3);
while (Mask & BitBuffer)
{
Mask >>= 1;
CharC += 1;
}
}
FillBuffer((uint16_t)((CharC < 7) ? 3 : CharC - 3));
PTLength[Index++] = (uint8_t)CharC;
if (Index == is)
{
CharC = (uint16_t)GetBits(2);
CharC--;
while ((int16_t)(CharC) >= 0 && Index < NPT)
{
PTLength[Index++] = 0;
CharC--;
}
}
}
while (Index < nn && Index < NPT)
PTLength[Index++] = 0;
return MakeTable(nn, PTLength, 8, PTTable);
}
void CCompress::ReadCLength()
{
uint16_t Number;
uint16_t CharC;
uint16_t Index;
uint32_t Mask;
Number = (uint16_t)GetBits(CBIT);
if (Number == 0)
{
CharC = (uint16_t)GetBits(CBIT);
for (Index = 0; Index < NC; Index++)
CLength[Index] = 0;
for (Index = 0; Index < 4096; Index++)
CTable[Index] = CharC;
return;
}
Index = 0;
while (Index < Number)
{
CharC = PTTable[BitBuffer >> (BITBUFSIZ - 8)];
if (CharC >= NT)
{
Mask = 1U << (BITBUFSIZ - 1 - 8);
do
{
if (Mask & BitBuffer)
CharC = Right[CharC];
else
CharC = Left[CharC];
Mask >>= 1;
} while (CharC >= NT);
}
// Advance what we have read.
FillBuffer(PTLength[CharC]);
if (CharC <= 2)
{
if (CharC == 0)
CharC = 1;
else if (CharC == 1)
CharC = (uint16_t)(GetBits(4) + 3);
else if (CharC == 2)
CharC = (uint16_t)(GetBits(CBIT) + 20);
CharC--;
while ((int16_t)(CharC) >= 0)
{
CLength[Index++] = 0;
CharC--;
}
}
else
{
CLength[Index++] = (uint8_t)(CharC - 2);
}
}
while (Index < NC)
CLength[Index++] = 0;
MakeTable(NC, CLength, 12, CTable);
}
uint16_t CCompress::DecodeC()
{
uint16_t Index2;
uint32_t Mask;
if (BlockSize == 0)
{
// Starting a new block.
BlockSize = (uint16_t)GetBits(16);
ReadPTLength(NT, TBIT, 3);
ReadCLength();
ReadPTLength(MAXNP, Pbit, (uint16_t)(-1));
}
BlockSize--;
Index2 = CTable[BitBuffer >> (BITBUFSIZ - 12)];
if (Index2 >= NC)
{
Mask = 1U << (BITBUFSIZ - 1 - 12);
do
{
if (BitBuffer & Mask)
Index2 = Right[Index2];
else
Index2 = Left[Index2];
Mask >>= 1;
} while (Index2 >= NC);
}
// Advance what we have read.
FillBuffer(CLength[Index2]);
return Index2;
}
uint32_t CCompress::DecodeP()
{
uint16_t Val;
uint32_t Mask;
uint32_t Pos;
Val = PTTable[BitBuffer >> (BITBUFSIZ - 8)];
if (Val >= MAXNP)
{
Mask = 1U << (BITBUFSIZ - 1 - 8);
do
{
if (BitBuffer & Mask)
Val = Right[Val];
else
Val = Left[Val];
Mask >>= 1;
} while (Val >= MAXNP);
}
// Advance what we have read.
FillBuffer(PTLength[Val]);
Pos = Val;
if (Val > 1)
Pos = (uint32_t)((1U << (Val - 1)) + GetBits((uint16_t)(Val - 1)));
return Pos;
}
uint16_t CCompress::MakeTable(uint16_t NumOfChar, uint8_t* BitLen, uint16_t TableBits, uint16_t* Table)
{
uint16_t Count[17];
uint16_t Weight[17];
uint16_t Start[18];
uint16_t* Pointer;
uint16_t Index3;
uint16_t Index;
uint16_t Len;
uint16_t Char;
uint16_t JuBits;
uint16_t Avail;
uint16_t NextCode;
uint16_t Mask;
uint16_t MaxTableLength;
for (Index = 1; Index <= 16; Index++)
Count[Index] = 0;
for (Index = 0; Index < NumOfChar; Index++)
{
if (BitLen[Index] > 16)
return (uint16_t)BAD_TABLE;
Count[BitLen[Index]]++;
}
Start[1] = 0;
for (Index = 1; Index <= 16; Index++)
Start[Index + 1] = (uint16_t)(Start[Index] + (Count[Index] << (16 - Index)));
/*(1U << 16)*/
if (Start[17] != 0)
return (uint16_t)BAD_TABLE;
JuBits = (uint16_t)(16 - TableBits);
for (Index = 1; Index <= TableBits; Index++)
{
Start[Index] >>= JuBits;
Weight[Index] = (uint16_t)(1U << (TableBits - Index));
}
while (Index <= 16)
{
Weight[Index] = (uint16_t)(1U << (16 - Index));
Index++;
}
Index = (uint16_t)(Start[TableBits + 1] >> JuBits);
if (Index != 0)
{
Index3 = (uint16_t)(1U << TableBits);
while (Index != Index3)
Table[Index++] = 0;
}
Avail = NumOfChar;
Mask = (uint16_t)(1U << (15 - TableBits));
MaxTableLength = (uint16_t)(1U << TableBits);
for (Char = 0; Char < NumOfChar; Char++)
{
Len = BitLen[Char];
if (Len == 0 || Len >= 17)
continue;
NextCode = (uint16_t)(Start[Len] + Weight[Len]);
if (Len <= TableBits)
{
if (Start[Len] >= NextCode || NextCode > MaxTableLength)
return (uint16_t)BAD_TABLE;
for (Index = Start[Len]; Index < NextCode; Index++)
Table[Index] = Char;
}
else
{
Index3 = Start[Len];
Pointer = &Table[Index3 >> JuBits];
Index = (uint16_t)(Len - TableBits);
while (Index != 0)
{
if (*Pointer == 0)
{
Right[Avail] = Left[Avail] = 0;
*Pointer = Avail++;
}
if (Index3 & Mask)
Pointer = &Right[*Pointer];
else
Pointer = &Left[*Pointer];
Index3 <<= 1;
Index--;
}
*Pointer = Char;
}
Start[Len] = NextCode;
}
// Succeeds.
return 0;
}
void CCompress::DecodeStart()
{
uint16_t BytesRemain;
uint32_t DataIdx;
uint16_t CharC;
BytesRemain = (uint16_t)(-1);
DataIdx = 0;
for (;;)
{
CharC = DecodeC();
if (CharC < 256)
{
// Process an Original character.
Dest[DestPos++] = (uint8_t)CharC;
if (DestPos >= OrigSize)
return;
}
else
{
// Process a Pointer.
CharC = (uint16_t)(CharC - (UINT8_MAX + 1 - THRESHOLD));
BytesRemain = CharC;
DataIdx = DestPos - DecodeP() - 1;
BytesRemain--;
while ((int16_t)(BytesRemain) >= 0)
{
if (DestPos >= OrigSize)
return;
if (DataIdx >= OrigSize)
return;
Dest[DestPos++] = Dest[DataIdx++];
BytesRemain--;
}
// Once DestPos is fully filled, directly return.
if (DestPos >= OrigSize)
return;
}
}
}
#pragma once
#include <stdint.h>
class CCompress
{
public:
void Decode(void* dec, void* src, uint32_t size, uint32_t osize);
private:
static constexpr uint32_t BITBUFSIZ = 16;
static constexpr uint32_t MAXMATCH = 256;
static constexpr uint32_t THRESHOLD = 3;
static constexpr uint32_t CODE_BIT = 16;
static constexpr uint32_t BAD_TABLE = -1;
// C: Char&Len Set; P: Position Set; T: exTra Set
static constexpr uint32_t NC = (0xFF + MAXMATCH + 2 - THRESHOLD);
static constexpr uint32_t CBIT = 9;
static constexpr uint32_t EFIPBIT = 4;
static constexpr uint32_t MAXPBIT = 5;
static constexpr uint32_t TBIT = 5;
static constexpr uint32_t MAXNP = (1U << MAXPBIT) - 1;
static constexpr uint32_t NT = CODE_BIT + 3;
static constexpr uint32_t NPT = MAXNP;
static uint16_t Pbit;
uint8_t* Source; // Starting address of compressed data
uint8_t* Dest; // Starting address of decompressed data
uint32_t DestPos;
uint32_t SourPos;
uint32_t SourSize;
uint16_t BitCount;
uint16_t BitBuffer;
uint16_t SubBitBuffer;
uint16_t BlockSize;
uint32_t CompSize;
uint32_t OrigSize;
uint16_t Left[2 * NC - 1];
uint16_t Right[2 * NC - 1];
uint8_t CLength[NC];
uint8_t PTLength[NPT];
uint16_t CTable[4096];
uint16_t PTTable[256];
void FillBuffer(uint16_t n);
uint32_t GetBits(uint16_t n);
uint16_t ReadPTLength(uint16_t nn, uint16_t nbit, uint16_t is);
void ReadCLength();
uint16_t DecodeC();
uint32_t DecodeP();
uint16_t MakeTable(uint16_t NumOfChar, uint8_t* BitLen, uint16_t TableBits, uint16_t* Table);
void DecodeStart();
};
<?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">