mirror of
https://github.com/diasurgical/DevilutionX.git
synced 2026-10-11 22:29:54 +00:00
Migrate from libmpq to mpqfs (#8482)
Co-authored-by: staphen <[email protected]>
This commit is contained in:
Vendored
-4
@@ -1,4 +0,0 @@
|
||||
add_library(PKWare STATIC
|
||||
explode.cpp
|
||||
implode.cpp)
|
||||
target_include_directories(PKWare PUBLIC .)
|
||||
Vendored
-59
@@ -1,59 +0,0 @@
|
||||
VC5_DIR ?= $(HOME)/DevStudio_5.10/VC
|
||||
|
||||
# The $(VS6_DIR) directory is a copy of the "Microsoft Visual Studio" directory.
|
||||
#
|
||||
# To get a working setup on Linux or other "portable" copies of VS,
|
||||
# the following DLLs have to be copied to the
|
||||
# $(VS6_DIR)/VC98/Bin directory.
|
||||
#
|
||||
# - $(VS6_DIR)/Common/MSDev98/Bin/MSPDB60.DLL
|
||||
#
|
||||
# And to the $(VC5_DIR)/bin directory.
|
||||
#
|
||||
# - $(VC5_DIR)/SharedIDE/bin/MSDIS100.DLL
|
||||
# - $(VC5_DIR)/SharedIDE/bin/MSPDB50.DLL
|
||||
VS6_DIR ?= $(HOME)/VS6
|
||||
|
||||
VC6_DIR = $(VS6_DIR)/VC98
|
||||
|
||||
VC6_BIN_DIR = $(VC6_DIR)/Bin
|
||||
VC6_INC_DIR = $(VC6_DIR)/Include
|
||||
VC6_LIB_DIR = $(VC6_DIR)/Lib
|
||||
|
||||
VC5_LIB_DIR = $(VC5_DIR)/lib
|
||||
|
||||
IDE_DIR ?= $(VS6_DIR)/Common/MSDev98
|
||||
IDE_BIN_DIR = $(IDE_DIR)/bin
|
||||
ifeq ($(OS),Windows_NT)
|
||||
CL = $(VC6_BIN_DIR)/CL.EXE
|
||||
RC = $(IDE_BIN_DIR)/RC.EXE
|
||||
VC5_LINK = $(VC5_DIR)/bin/link.exe
|
||||
VC6_LINK = $(VC6_BIN_DIR)/link.exe
|
||||
else
|
||||
CL = wine $(VC6_BIN_DIR)/CL.EXE
|
||||
RC = wine $(IDE_BIN_DIR)/RC.EXE
|
||||
VC5_LINK = wine $(VC5_DIR)/bin/link.exe
|
||||
VC6_LINK = wine $(VC6_BIN_DIR)/link.exe
|
||||
endif
|
||||
|
||||
CFLAGS=/nologo /c /GX /W3 /O1 /I $(VC6_INC_DIR) /FD /MT /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /YX /Gm /Zi
|
||||
LINKFLAGS=/nologo /subsystem:windows /machine:I386 /incremental:no
|
||||
|
||||
VC_LINK=$(VC5_LINK)
|
||||
LINKFLAGS+= /LIBPATH:$(VC5_LIB_DIR)
|
||||
|
||||
all: pkware.lib
|
||||
|
||||
PKWARE_SRC=$(sort $(wildcard *.cpp))
|
||||
PKWARE_OBJS=$(PKWARE_SRC:.cpp=.obj)
|
||||
|
||||
pkware.lib: $(PKWARE_OBJS)
|
||||
$(VC_LINK) -lib /OUT:$@ $^ /nologo
|
||||
|
||||
%.obj: %.cpp
|
||||
$(CL) $(CFLAGS) /Fo$@ $<
|
||||
|
||||
clean:
|
||||
@$(RM) -v $(PKWARE_OBJS) pkware.lib vc60.{idb,pch,pdb}
|
||||
|
||||
.PHONY: clean all
|
||||
Vendored
-100
@@ -1,100 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.0" 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>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="explode.cpp" />
|
||||
<ClCompile Include="implode.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="pkware.h" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>15.0</VCProjectVersion>
|
||||
<ProjectGuid>{C7F9F3B4-2F7C-4672-9586-94D8BA0950B6}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>PKWare</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0.17763.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v141</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>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<OutDir>.\WinDebug\</OutDir>
|
||||
<IntDir>.\WinDebug\</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<OutDir>.\WinRel</OutDir>
|
||||
<IntDir>.\WinRel</IntDir>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
<?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;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;hm;inl;inc;ipp;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="explode.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="implode.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="pkware.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
Vendored
-100
@@ -1,100 +0,0 @@
|
||||
# Microsoft Developer Studio Project File - Name="Pkware" - Package Owner=<4>
|
||||
# Microsoft Developer Studio Generated Build File, Format Version 6.00
|
||||
# ** DO NOT EDIT **
|
||||
|
||||
# TARGTYPE "Win32 (x86) Static Library" 0x0104
|
||||
|
||||
CFG=Pkware - Win32 Debug
|
||||
!MESSAGE This is not a valid makefile. To build this project using NMAKE,
|
||||
!MESSAGE use the Export Makefile command and run
|
||||
!MESSAGE
|
||||
!MESSAGE NMAKE /f "Pkware.mak".
|
||||
!MESSAGE
|
||||
!MESSAGE You can specify a configuration when running NMAKE
|
||||
!MESSAGE by defining the macro CFG on the command line. For example:
|
||||
!MESSAGE
|
||||
!MESSAGE NMAKE /f "Pkware.mak" CFG="Pkware - Win32 Debug"
|
||||
!MESSAGE
|
||||
!MESSAGE Possible choices for configuration are:
|
||||
!MESSAGE
|
||||
!MESSAGE "Pkware - Win32 Release" (based on "Win32 (x86) Static Library")
|
||||
!MESSAGE "Pkware - Win32 Debug" (based on "Win32 (x86) Static Library")
|
||||
!MESSAGE
|
||||
|
||||
# Begin Project
|
||||
# PROP AllowPerConfigDependencies 0
|
||||
# PROP Scc_ProjName ""
|
||||
# PROP Scc_LocalPath ""
|
||||
CPP=cl.exe
|
||||
RSC=rc.exe
|
||||
|
||||
!IF "$(CFG)" == "Pkware - Win32 Release"
|
||||
|
||||
# PROP BASE Use_MFC 0
|
||||
# PROP BASE Use_Debug_Libraries 0
|
||||
# PROP BASE Output_Dir "WinRel"
|
||||
# PROP BASE Intermediate_Dir "WinRel"
|
||||
# PROP BASE Target_Dir ""
|
||||
# PROP Use_MFC 0
|
||||
# PROP Use_Debug_Libraries 0
|
||||
# PROP Output_Dir "WinRel"
|
||||
# PROP Intermediate_Dir "WinRel"
|
||||
# PROP Target_Dir ""
|
||||
# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c
|
||||
# ADD CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c
|
||||
# ADD BASE RSC /l 0x409 /d "NDEBUG"
|
||||
# ADD RSC /l 0x409 /d "NDEBUG"
|
||||
BSC32=bscmake.exe
|
||||
# ADD BASE BSC32 /nologo
|
||||
# ADD BSC32 /nologo
|
||||
LIB32=link.exe -lib
|
||||
# ADD BASE LIB32 /nologo
|
||||
# ADD LIB32 /nologo
|
||||
|
||||
!ELSEIF "$(CFG)" == "Pkware - Win32 Debug"
|
||||
|
||||
# PROP BASE Use_MFC 0
|
||||
# PROP BASE Use_Debug_Libraries 1
|
||||
# PROP BASE Output_Dir "WinDebug"
|
||||
# PROP BASE Intermediate_Dir "WinDebug"
|
||||
# PROP BASE Target_Dir ""
|
||||
# PROP Use_MFC 0
|
||||
# PROP Use_Debug_Libraries 1
|
||||
# PROP Output_Dir "WinDebug"
|
||||
# PROP Intermediate_Dir "WinDebug"
|
||||
# PROP Target_Dir ""
|
||||
# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c
|
||||
# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c
|
||||
# ADD BASE RSC /l 0x409 /d "_DEBUG"
|
||||
# ADD RSC /l 0x409 /d "_DEBUG"
|
||||
BSC32=bscmake.exe
|
||||
# ADD BASE BSC32 /nologo
|
||||
# ADD BSC32 /nologo
|
||||
LIB32=link.exe -lib
|
||||
# ADD BASE LIB32 /nologo
|
||||
# ADD LIB32 /nologo
|
||||
|
||||
!ENDIF
|
||||
|
||||
# Begin Target
|
||||
|
||||
# Name "Pkware - Win32 Release"
|
||||
# Name "Pkware - Win32 Debug"
|
||||
# Begin Group "Source Files"
|
||||
|
||||
# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\explode.cpp
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\implode.cpp
|
||||
# End Source File
|
||||
# End Group
|
||||
# Begin Group "Header Files"
|
||||
|
||||
# PROP Default_Filter "h;hpp;hxx;hm;inl"
|
||||
# End Group
|
||||
# End Target
|
||||
# End Project
|
||||
Vendored
-522
@@ -1,522 +0,0 @@
|
||||
/*****************************************************************************/
|
||||
/* explode.cpp Copyright (c) Ladislav Zezula 2003 */
|
||||
/*---------------------------------------------------------------------------*/
|
||||
/* Implode function of PKWARE Data Compression library */
|
||||
/*---------------------------------------------------------------------------*/
|
||||
/* Date Ver Who Comment */
|
||||
/* -------- ---- --- ------- */
|
||||
/* 11.03.03 1.00 Lad Splitted from Pkware.cpp */
|
||||
/* 08.04.03 1.01 Lad Renamed to explode.cpp to be compatible with pkware */
|
||||
/* 02.05.03 1.01 Lad Stress test done */
|
||||
/* 22.04.10 1.01 Lad Documented */
|
||||
/*****************************************************************************/
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "pkware.h"
|
||||
|
||||
#define PKDCL_OK 0
|
||||
#define PKDCL_STREAM_END 1 // All data from the input stream is read
|
||||
#define PKDCL_NEED_DICT 2 // Need more data (dictionary)
|
||||
#define PKDCL_CONTINUE 10 // Internal flag, not returned to user
|
||||
#define PKDCL_GET_INPUT 11 // Internal flag, not returned to user
|
||||
|
||||
static char CopyrightPkware[] = "PKWARE Data Compression Library for Win32\r\n"
|
||||
"Copyright 1989-1995 PKWARE Inc. All Rights Reserved\r\n"
|
||||
"Patent No. 5,051,745\r\n"
|
||||
"PKWARE Data Compression Library Reg. U.S. Pat. and Tm. Off.\r\n"
|
||||
"Version 1.11\r\n";
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Tables
|
||||
|
||||
static unsigned char DistBits[] =
|
||||
{
|
||||
0x02, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
|
||||
0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
|
||||
0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
|
||||
0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08
|
||||
};
|
||||
|
||||
static unsigned char DistCode[] =
|
||||
{
|
||||
0x03, 0x0D, 0x05, 0x19, 0x09, 0x11, 0x01, 0x3E, 0x1E, 0x2E, 0x0E, 0x36, 0x16, 0x26, 0x06, 0x3A,
|
||||
0x1A, 0x2A, 0x0A, 0x32, 0x12, 0x22, 0x42, 0x02, 0x7C, 0x3C, 0x5C, 0x1C, 0x6C, 0x2C, 0x4C, 0x0C,
|
||||
0x74, 0x34, 0x54, 0x14, 0x64, 0x24, 0x44, 0x04, 0x78, 0x38, 0x58, 0x18, 0x68, 0x28, 0x48, 0x08,
|
||||
0xF0, 0x70, 0xB0, 0x30, 0xD0, 0x50, 0x90, 0x10, 0xE0, 0x60, 0xA0, 0x20, 0xC0, 0x40, 0x80, 0x00
|
||||
};
|
||||
|
||||
static unsigned char ExLenBits[] =
|
||||
{
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08
|
||||
};
|
||||
|
||||
static unsigned short LenBase[] =
|
||||
{
|
||||
0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
|
||||
0x0008, 0x000A, 0x000E, 0x0016, 0x0026, 0x0046, 0x0086, 0x0106
|
||||
};
|
||||
|
||||
static unsigned char LenBits[] =
|
||||
{
|
||||
0x03, 0x02, 0x03, 0x03, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 0x07, 0x07
|
||||
};
|
||||
|
||||
static unsigned char LenCode[] =
|
||||
{
|
||||
0x05, 0x03, 0x01, 0x06, 0x0A, 0x02, 0x0C, 0x14, 0x04, 0x18, 0x08, 0x30, 0x10, 0x20, 0x40, 0x00
|
||||
};
|
||||
|
||||
static unsigned char ChBitsAsc[] =
|
||||
{
|
||||
0x0B, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x08, 0x07, 0x0C, 0x0C, 0x07, 0x0C, 0x0C,
|
||||
0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0D, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
|
||||
0x04, 0x0A, 0x08, 0x0C, 0x0A, 0x0C, 0x0A, 0x08, 0x07, 0x07, 0x08, 0x09, 0x07, 0x06, 0x07, 0x08,
|
||||
0x07, 0x06, 0x07, 0x07, 0x07, 0x07, 0x08, 0x07, 0x07, 0x08, 0x08, 0x0C, 0x0B, 0x07, 0x09, 0x0B,
|
||||
0x0C, 0x06, 0x07, 0x06, 0x06, 0x05, 0x07, 0x08, 0x08, 0x06, 0x0B, 0x09, 0x06, 0x07, 0x06, 0x06,
|
||||
0x07, 0x0B, 0x06, 0x06, 0x06, 0x07, 0x09, 0x08, 0x09, 0x09, 0x0B, 0x08, 0x0B, 0x09, 0x0C, 0x08,
|
||||
0x0C, 0x05, 0x06, 0x06, 0x06, 0x05, 0x06, 0x06, 0x06, 0x05, 0x0B, 0x07, 0x05, 0x06, 0x05, 0x05,
|
||||
0x06, 0x0A, 0x05, 0x05, 0x05, 0x05, 0x08, 0x07, 0x08, 0x08, 0x0A, 0x0B, 0x0B, 0x0C, 0x0C, 0x0C,
|
||||
0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D,
|
||||
0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D,
|
||||
0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D,
|
||||
0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
|
||||
0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
|
||||
0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
|
||||
0x0D, 0x0C, 0x0D, 0x0D, 0x0D, 0x0C, 0x0D, 0x0D, 0x0D, 0x0C, 0x0D, 0x0D, 0x0D, 0x0D, 0x0C, 0x0D,
|
||||
0x0D, 0x0D, 0x0C, 0x0C, 0x0C, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D
|
||||
};
|
||||
|
||||
static unsigned short ChCodeAsc[] =
|
||||
{
|
||||
0x0490, 0x0FE0, 0x07E0, 0x0BE0, 0x03E0, 0x0DE0, 0x05E0, 0x09E0,
|
||||
0x01E0, 0x00B8, 0x0062, 0x0EE0, 0x06E0, 0x0022, 0x0AE0, 0x02E0,
|
||||
0x0CE0, 0x04E0, 0x08E0, 0x00E0, 0x0F60, 0x0760, 0x0B60, 0x0360,
|
||||
0x0D60, 0x0560, 0x1240, 0x0960, 0x0160, 0x0E60, 0x0660, 0x0A60,
|
||||
0x000F, 0x0250, 0x0038, 0x0260, 0x0050, 0x0C60, 0x0390, 0x00D8,
|
||||
0x0042, 0x0002, 0x0058, 0x01B0, 0x007C, 0x0029, 0x003C, 0x0098,
|
||||
0x005C, 0x0009, 0x001C, 0x006C, 0x002C, 0x004C, 0x0018, 0x000C,
|
||||
0x0074, 0x00E8, 0x0068, 0x0460, 0x0090, 0x0034, 0x00B0, 0x0710,
|
||||
0x0860, 0x0031, 0x0054, 0x0011, 0x0021, 0x0017, 0x0014, 0x00A8,
|
||||
0x0028, 0x0001, 0x0310, 0x0130, 0x003E, 0x0064, 0x001E, 0x002E,
|
||||
0x0024, 0x0510, 0x000E, 0x0036, 0x0016, 0x0044, 0x0030, 0x00C8,
|
||||
0x01D0, 0x00D0, 0x0110, 0x0048, 0x0610, 0x0150, 0x0060, 0x0088,
|
||||
0x0FA0, 0x0007, 0x0026, 0x0006, 0x003A, 0x001B, 0x001A, 0x002A,
|
||||
0x000A, 0x000B, 0x0210, 0x0004, 0x0013, 0x0032, 0x0003, 0x001D,
|
||||
0x0012, 0x0190, 0x000D, 0x0015, 0x0005, 0x0019, 0x0008, 0x0078,
|
||||
0x00F0, 0x0070, 0x0290, 0x0410, 0x0010, 0x07A0, 0x0BA0, 0x03A0,
|
||||
0x0240, 0x1C40, 0x0C40, 0x1440, 0x0440, 0x1840, 0x0840, 0x1040,
|
||||
0x0040, 0x1F80, 0x0F80, 0x1780, 0x0780, 0x1B80, 0x0B80, 0x1380,
|
||||
0x0380, 0x1D80, 0x0D80, 0x1580, 0x0580, 0x1980, 0x0980, 0x1180,
|
||||
0x0180, 0x1E80, 0x0E80, 0x1680, 0x0680, 0x1A80, 0x0A80, 0x1280,
|
||||
0x0280, 0x1C80, 0x0C80, 0x1480, 0x0480, 0x1880, 0x0880, 0x1080,
|
||||
0x0080, 0x1F00, 0x0F00, 0x1700, 0x0700, 0x1B00, 0x0B00, 0x1300,
|
||||
0x0DA0, 0x05A0, 0x09A0, 0x01A0, 0x0EA0, 0x06A0, 0x0AA0, 0x02A0,
|
||||
0x0CA0, 0x04A0, 0x08A0, 0x00A0, 0x0F20, 0x0720, 0x0B20, 0x0320,
|
||||
0x0D20, 0x0520, 0x0920, 0x0120, 0x0E20, 0x0620, 0x0A20, 0x0220,
|
||||
0x0C20, 0x0420, 0x0820, 0x0020, 0x0FC0, 0x07C0, 0x0BC0, 0x03C0,
|
||||
0x0DC0, 0x05C0, 0x09C0, 0x01C0, 0x0EC0, 0x06C0, 0x0AC0, 0x02C0,
|
||||
0x0CC0, 0x04C0, 0x08C0, 0x00C0, 0x0F40, 0x0740, 0x0B40, 0x0340,
|
||||
0x0300, 0x0D40, 0x1D00, 0x0D00, 0x1500, 0x0540, 0x0500, 0x1900,
|
||||
0x0900, 0x0940, 0x1100, 0x0100, 0x1E00, 0x0E00, 0x0140, 0x1600,
|
||||
0x0600, 0x1A00, 0x0E40, 0x0640, 0x0A40, 0x0A00, 0x1200, 0x0200,
|
||||
0x1C00, 0x0C00, 0x1400, 0x0400, 0x1800, 0x0800, 0x1000, 0x0000
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Local functions
|
||||
|
||||
static void PKWAREAPI GenDecodeTabs(
|
||||
unsigned char * positions, // [out] Table of positions
|
||||
unsigned char * start_indexes, // [in] Table of start indexes
|
||||
unsigned char * length_bits, // [in] Table of lengths. Each length is stored as number of bits
|
||||
size_t elements) // [in] Number of elements in start_indexes and length_bits
|
||||
{
|
||||
unsigned int index;
|
||||
unsigned int length;
|
||||
size_t i;
|
||||
|
||||
for(i = 0; i < elements; i++)
|
||||
{
|
||||
length = 1 << length_bits[i]; // Get the length in bytes
|
||||
|
||||
for(index = start_indexes[i]; index < 0x100; index += length)
|
||||
{
|
||||
positions[index] = (unsigned char)i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void PKWAREAPI GenAscTabs(TDcmpStruct * pWork)
|
||||
{
|
||||
unsigned short * pChCodeAsc = &ChCodeAsc[0xFF];
|
||||
unsigned int acc, add;
|
||||
unsigned short count;
|
||||
|
||||
for(count = 0x00FF; pChCodeAsc >= ChCodeAsc; pChCodeAsc--, count--)
|
||||
{
|
||||
unsigned char * pChBitsAsc = pWork->ChBitsAsc + count;
|
||||
unsigned char bits_asc = *pChBitsAsc;
|
||||
|
||||
if(bits_asc <= 8)
|
||||
{
|
||||
add = (1 << bits_asc);
|
||||
acc = *pChCodeAsc;
|
||||
|
||||
do
|
||||
{
|
||||
pWork->offs2C34[acc] = (unsigned char)count;
|
||||
acc += add;
|
||||
}
|
||||
while(acc < 0x100);
|
||||
}
|
||||
else if((acc = (*pChCodeAsc & 0xFF)) != 0)
|
||||
{
|
||||
pWork->offs2C34[acc] = 0xFF;
|
||||
|
||||
if(*pChCodeAsc & 0x3F)
|
||||
{
|
||||
bits_asc -= 4;
|
||||
*pChBitsAsc = bits_asc;
|
||||
|
||||
add = (1 << bits_asc);
|
||||
acc = *pChCodeAsc >> 4;
|
||||
do
|
||||
{
|
||||
pWork->offs2D34[acc] = (unsigned char)count;
|
||||
acc += add;
|
||||
}
|
||||
while(acc < 0x100);
|
||||
}
|
||||
else
|
||||
{
|
||||
bits_asc -= 6;
|
||||
*pChBitsAsc = bits_asc;
|
||||
|
||||
add = (1 << bits_asc);
|
||||
acc = *pChCodeAsc >> 6;
|
||||
do
|
||||
{
|
||||
pWork->offs2E34[acc] = (unsigned char)count;
|
||||
acc += add;
|
||||
}
|
||||
while(acc < 0x80);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bits_asc -= 8;
|
||||
*pChBitsAsc = bits_asc;
|
||||
|
||||
add = (1 << bits_asc);
|
||||
acc = *pChCodeAsc >> 8;
|
||||
do
|
||||
{
|
||||
pWork->offs2EB4[acc] = (unsigned char)count;
|
||||
acc += add;
|
||||
}
|
||||
while(acc < 0x100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Removes given number of bits in the bit buffer. New bits are reloaded from
|
||||
// the input buffer, if needed.
|
||||
// Returns: PKDCL_OK: Operation was successful
|
||||
// PKDCL_STREAM_END: There are no more bits in the input buffer
|
||||
|
||||
static int PKWAREAPI WasteBits(TDcmpStruct * pWork, unsigned int nBits)
|
||||
{
|
||||
// If number of bits required is less than number of (bits in the buffer) ?
|
||||
if(nBits <= pWork->extra_bits)
|
||||
{
|
||||
pWork->extra_bits -= nBits;
|
||||
pWork->bit_buff >>= nBits;
|
||||
return PKDCL_OK;
|
||||
}
|
||||
|
||||
// Load input buffer if necessary
|
||||
pWork->bit_buff >>= pWork->extra_bits;
|
||||
if(pWork->in_pos == pWork->in_bytes)
|
||||
{
|
||||
pWork->in_pos = sizeof(pWork->in_buff);
|
||||
if((pWork->in_bytes = pWork->read_buf((char *)pWork->in_buff, &pWork->in_pos, pWork->param)) == 0)
|
||||
return PKDCL_STREAM_END;
|
||||
pWork->in_pos = 0;
|
||||
}
|
||||
|
||||
// Update bit buffer
|
||||
pWork->bit_buff |= (pWork->in_buff[pWork->in_pos++] << 8);
|
||||
pWork->bit_buff >>= (nBits - pWork->extra_bits);
|
||||
pWork->extra_bits = (pWork->extra_bits - nBits) + 8;
|
||||
return PKDCL_OK;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Decodes next literal from the input (compressed) data.
|
||||
// Returns : 0x000: One byte 0x00
|
||||
// 0x001: One byte 0x01
|
||||
// ...
|
||||
// 0x0FF: One byte 0xFF
|
||||
// 0x100: Repetition, length of 0x02 bytes
|
||||
// 0x101: Repetition, length of 0x03 bytes
|
||||
// ...
|
||||
// 0x304: Repetition, length of 0x206 bytes
|
||||
// 0x305: End of stream
|
||||
// 0x306: Error
|
||||
|
||||
static unsigned int PKWAREAPI DecodeLit(TDcmpStruct * pWork)
|
||||
{
|
||||
unsigned int extra_length_bits; // Number of bits of extra literal length
|
||||
unsigned int length_code; // Length code
|
||||
unsigned int value;
|
||||
|
||||
// Test the current bit in byte buffer. If is not set, simply return the next 8 bits.
|
||||
if(pWork->bit_buff & 1)
|
||||
{
|
||||
// Remove one bit from the input data
|
||||
if(WasteBits(pWork, 1))
|
||||
return 0x306;
|
||||
|
||||
// The next 8 bits hold the index to the length code table
|
||||
length_code = pWork->LengthCodes[pWork->bit_buff & 0xFF];
|
||||
|
||||
// Remove the apropriate number of bits
|
||||
if(WasteBits(pWork, pWork->LenBits[length_code]))
|
||||
return 0x306;
|
||||
|
||||
// Are there some extra bits for the obtained length code ?
|
||||
if((extra_length_bits = pWork->ExLenBits[length_code]) != 0)
|
||||
{
|
||||
unsigned int extra_length = pWork->bit_buff & ((1 << extra_length_bits) - 1);
|
||||
|
||||
if(WasteBits(pWork, extra_length_bits))
|
||||
{
|
||||
if((length_code + extra_length) != 0x10E)
|
||||
return 0x306;
|
||||
}
|
||||
length_code = pWork->LenBase[length_code] + extra_length;
|
||||
}
|
||||
|
||||
// In order to distinguish uncompressed byte from repetition length,
|
||||
// we have to add 0x100 to the length.
|
||||
return length_code + 0x100;
|
||||
}
|
||||
|
||||
// Remove one bit from the input data
|
||||
if(WasteBits(pWork, 1))
|
||||
return 0x306;
|
||||
|
||||
// If the binary compression type, read 8 bits and return them as one byte.
|
||||
if(pWork->ctype == CMP_BINARY)
|
||||
{
|
||||
unsigned int uncompressed_byte = pWork->bit_buff & 0xFF;
|
||||
|
||||
if(WasteBits(pWork, 8))
|
||||
return 0x306;
|
||||
return uncompressed_byte;
|
||||
}
|
||||
|
||||
// When ASCII compression ...
|
||||
if(pWork->bit_buff & 0xFF)
|
||||
{
|
||||
value = pWork->offs2C34[pWork->bit_buff & 0xFF];
|
||||
|
||||
if(value == 0xFF)
|
||||
{
|
||||
if(pWork->bit_buff & 0x3F)
|
||||
{
|
||||
if(WasteBits(pWork, 4))
|
||||
return 0x306;
|
||||
|
||||
value = pWork->offs2D34[pWork->bit_buff & 0xFF];
|
||||
}
|
||||
else
|
||||
{
|
||||
if(WasteBits(pWork, 6))
|
||||
return 0x306;
|
||||
|
||||
value = pWork->offs2E34[pWork->bit_buff & 0x7F];
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(WasteBits(pWork, 8))
|
||||
return 0x306;
|
||||
|
||||
value = pWork->offs2EB4[pWork->bit_buff & 0xFF];
|
||||
}
|
||||
|
||||
return WasteBits(pWork, pWork->ChBitsAsc[value]) ? 0x306 : value;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Decodes the distance of the repetition, backwards relative to the
|
||||
// current output buffer position
|
||||
|
||||
static unsigned int PKWAREAPI DecodeDist(TDcmpStruct * pWork, unsigned int rep_length)
|
||||
{
|
||||
unsigned int dist_pos_code; // Distance position code
|
||||
unsigned int dist_pos_bits; // Number of bits of distance position
|
||||
unsigned int distance; // Distance position
|
||||
|
||||
// Next 2-8 bits in the input buffer is the distance position code
|
||||
dist_pos_code = pWork->DistPosCodes[pWork->bit_buff & 0xFF];
|
||||
dist_pos_bits = pWork->DistBits[dist_pos_code];
|
||||
if(WasteBits(pWork, dist_pos_bits))
|
||||
return 0;
|
||||
|
||||
if(rep_length == 2)
|
||||
{
|
||||
// If the repetition is only 2 bytes length,
|
||||
// then take 2 bits from the stream in order to get the distance
|
||||
distance = (dist_pos_code << 2) | (pWork->bit_buff & 0x03);
|
||||
if(WasteBits(pWork, 2))
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the repetition is more than 2 bytes length,
|
||||
// then take "dsize_bits" bits in order to get the distance
|
||||
distance = (dist_pos_code << pWork->dsize_bits) | (pWork->bit_buff & pWork->dsize_mask);
|
||||
if(WasteBits(pWork, pWork->dsize_bits))
|
||||
return 0;
|
||||
}
|
||||
return distance + 1;
|
||||
}
|
||||
|
||||
static unsigned int PKWAREAPI Expand(TDcmpStruct * pWork)
|
||||
{
|
||||
unsigned int next_literal; // Literal decoded from the compressed data
|
||||
unsigned int result; // Value to be returned
|
||||
unsigned int copyBytes; // Number of bytes to copy to the output buffer
|
||||
|
||||
pWork->outputPos = 0x1000; // Initialize output buffer position
|
||||
|
||||
// Decode the next literal from the input data.
|
||||
// The returned literal can either be an uncompressed byte (next_literal < 0x100)
|
||||
// or an encoded length of the repeating byte sequence that
|
||||
// is to be copied to the current buffer position
|
||||
while((result = next_literal = DecodeLit(pWork)) < 0x305)
|
||||
{
|
||||
// If the literal is greater than 0x100, it holds length
|
||||
// of repeating byte sequence
|
||||
// literal of 0x100 means repeating sequence of 0x2 bytes
|
||||
// literal of 0x101 means repeating sequence of 0x3 bytes
|
||||
// ...
|
||||
// literal of 0x305 means repeating sequence of 0x207 bytes
|
||||
if(next_literal >= 0x100)
|
||||
{
|
||||
unsigned char * source;
|
||||
unsigned char * target;
|
||||
unsigned int rep_length; // Length of the repetition, in bytes
|
||||
unsigned int minus_dist; // Backward distance to the repetition, relative to the current buffer position
|
||||
|
||||
// Get the length of the repeating sequence.
|
||||
// Note that the repeating block may overlap the current output position,
|
||||
// for example if there was a sequence of equal bytes
|
||||
rep_length = next_literal - 0xFE;
|
||||
|
||||
// Get backward distance to the repetition
|
||||
if((minus_dist = DecodeDist(pWork, rep_length)) == 0)
|
||||
{
|
||||
result = 0x306;
|
||||
break;
|
||||
}
|
||||
|
||||
// Target and source pointer
|
||||
target = &pWork->out_buff[pWork->outputPos];
|
||||
source = target - minus_dist;
|
||||
|
||||
// Update buffer output position
|
||||
pWork->outputPos += rep_length;
|
||||
|
||||
// Copy the repeating sequence
|
||||
while(rep_length-- > 0)
|
||||
*target++ = *source++;
|
||||
}
|
||||
else
|
||||
{
|
||||
pWork->out_buff[pWork->outputPos++] = (unsigned char)next_literal;
|
||||
}
|
||||
|
||||
// Flush the output buffer, if number of extracted bytes has reached the end
|
||||
if(pWork->outputPos >= 0x2000)
|
||||
{
|
||||
// Copy decompressed data into user buffer
|
||||
copyBytes = 0x1000;
|
||||
pWork->write_buf((char *)&pWork->out_buff[0x1000], ©Bytes, pWork->param);
|
||||
|
||||
// Now copy the decompressed data to the first half of the buffer.
|
||||
// This is needed because the decompression might reuse them as repetitions.
|
||||
// Note that if the output buffer overflowed previously, the extra decompressed bytes
|
||||
// are stored in "out_buff_overflow", and they will now be
|
||||
// within decompressed part of the output buffer.
|
||||
memmove(pWork->out_buff, &pWork->out_buff[0x1000], pWork->outputPos - 0x1000);
|
||||
pWork->outputPos -= 0x1000;
|
||||
}
|
||||
}
|
||||
|
||||
// Flush any remaining decompressed bytes
|
||||
copyBytes = pWork->outputPos - 0x1000;
|
||||
pWork->write_buf((char *)&pWork->out_buff[0x1000], ©Bytes, pWork->param);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Main exploding function.
|
||||
|
||||
unsigned int PKWAREAPI explode(
|
||||
unsigned int (PKWAREAPI *read_buf)(char *buf, unsigned int *size, void *param),
|
||||
void (PKWAREAPI *write_buf)(char *buf, unsigned int *size, void *param),
|
||||
char *work_buf,
|
||||
void *param)
|
||||
{
|
||||
TDcmpStruct * pWork = (TDcmpStruct *)work_buf;
|
||||
|
||||
// Initialize work struct and load compressed data
|
||||
// Note: The caller must zero the "work_buff" before passing it to explode
|
||||
pWork->read_buf = read_buf;
|
||||
pWork->write_buf = write_buf;
|
||||
pWork->param = param;
|
||||
pWork->in_pos = sizeof(pWork->in_buff);
|
||||
pWork->in_bytes = pWork->read_buf((char *)pWork->in_buff, &pWork->in_pos, pWork->param);
|
||||
if(pWork->in_bytes <= 4)
|
||||
return CMP_BAD_DATA;
|
||||
|
||||
pWork->ctype = pWork->in_buff[0]; // Get the compression type (CMP_BINARY or CMP_ASCII)
|
||||
pWork->dsize_bits = pWork->in_buff[1]; // Get the dictionary size
|
||||
pWork->bit_buff = pWork->in_buff[2]; // Initialize 16-bit bit buffer
|
||||
pWork->extra_bits = 0; // Extra (over 8) bits
|
||||
pWork->in_pos = 3; // Position in input buffer
|
||||
|
||||
// Test for the valid dictionary size
|
||||
if(4 > pWork->dsize_bits || pWork->dsize_bits > 6)
|
||||
return CMP_INVALID_DICTSIZE;
|
||||
|
||||
pWork->dsize_mask = 0xFFFF >> (0x10 - pWork->dsize_bits); // Shifted by 'sar' instruction
|
||||
|
||||
if(pWork->ctype != CMP_BINARY)
|
||||
{
|
||||
if(pWork->ctype != CMP_ASCII)
|
||||
return CMP_INVALID_MODE;
|
||||
|
||||
memcpy(pWork->ChBitsAsc, ChBitsAsc, sizeof(pWork->ChBitsAsc));
|
||||
GenAscTabs(pWork);
|
||||
}
|
||||
|
||||
memcpy(pWork->LenBits, LenBits, sizeof(pWork->LenBits));
|
||||
GenDecodeTabs(pWork->LengthCodes, LenCode, pWork->LenBits, sizeof(pWork->LenBits));
|
||||
memcpy(pWork->ExLenBits, ExLenBits, sizeof(pWork->ExLenBits));
|
||||
memcpy(pWork->LenBase, LenBase, sizeof(pWork->LenBase));
|
||||
memcpy(pWork->DistBits, DistBits, sizeof(pWork->DistBits));
|
||||
GenDecodeTabs(pWork->DistPosCodes, DistCode, pWork->DistBits, sizeof(pWork->DistBits));
|
||||
if(Expand(pWork) != 0x306)
|
||||
return CMP_NO_ERROR;
|
||||
|
||||
return CMP_ABORT;
|
||||
}
|
||||
Vendored
-775
@@ -1,775 +0,0 @@
|
||||
/*****************************************************************************/
|
||||
/* implode.cpp Copyright (c) Ladislav Zezula 2003 */
|
||||
/*---------------------------------------------------------------------------*/
|
||||
/* Implode function of PKWARE Data Compression library */
|
||||
/*---------------------------------------------------------------------------*/
|
||||
/* Date Ver Who Comment */
|
||||
/* -------- ---- --- ------- */
|
||||
/* 11.04.03 1.00 Lad First version of implode.cpp */
|
||||
/* 02.05.03 1.00 Lad Stress test done */
|
||||
/* 22.04.10 1.01 Lad Documented */
|
||||
/*****************************************************************************/
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "pkware.h"
|
||||
|
||||
#if ((1200 < _MSC_VER) && (_MSC_VER < 1400))
|
||||
#pragma optimize("", off)
|
||||
#endif
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Defines
|
||||
|
||||
#define MAX_REP_LENGTH 0x204 // The longest allowed repetition
|
||||
|
||||
static char CopyrightPkware[] = "PKWARE Data Compression Library for Win32\r\n"
|
||||
"Copyright 1989-1995 PKWARE Inc. All Rights Reserved\r\n"
|
||||
"Patent No. 5,051,745\r\n"
|
||||
"PKWARE Data Compression Library Reg. U.S. Pat. and Tm. Off.\r\n"
|
||||
"Version 1.11\r\n";
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Tables
|
||||
|
||||
static unsigned char DistBits[] =
|
||||
{
|
||||
0x02, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
|
||||
0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
|
||||
0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
|
||||
0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08
|
||||
};
|
||||
|
||||
static unsigned char DistCode[] =
|
||||
{
|
||||
0x03, 0x0D, 0x05, 0x19, 0x09, 0x11, 0x01, 0x3E, 0x1E, 0x2E, 0x0E, 0x36, 0x16, 0x26, 0x06, 0x3A,
|
||||
0x1A, 0x2A, 0x0A, 0x32, 0x12, 0x22, 0x42, 0x02, 0x7C, 0x3C, 0x5C, 0x1C, 0x6C, 0x2C, 0x4C, 0x0C,
|
||||
0x74, 0x34, 0x54, 0x14, 0x64, 0x24, 0x44, 0x04, 0x78, 0x38, 0x58, 0x18, 0x68, 0x28, 0x48, 0x08,
|
||||
0xF0, 0x70, 0xB0, 0x30, 0xD0, 0x50, 0x90, 0x10, 0xE0, 0x60, 0xA0, 0x20, 0xC0, 0x40, 0x80, 0x00
|
||||
};
|
||||
|
||||
static unsigned char ExLenBits[] =
|
||||
{
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08
|
||||
};
|
||||
|
||||
static unsigned char LenBits[] =
|
||||
{
|
||||
0x03, 0x02, 0x03, 0x03, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 0x07, 0x07
|
||||
};
|
||||
|
||||
static unsigned char LenCode[] =
|
||||
{
|
||||
0x05, 0x03, 0x01, 0x06, 0x0A, 0x02, 0x0C, 0x14, 0x04, 0x18, 0x08, 0x30, 0x10, 0x20, 0x40, 0x00
|
||||
};
|
||||
|
||||
static unsigned char ChBitsAsc[] =
|
||||
{
|
||||
0x0B, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x08, 0x07, 0x0C, 0x0C, 0x07, 0x0C, 0x0C,
|
||||
0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0D, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
|
||||
0x04, 0x0A, 0x08, 0x0C, 0x0A, 0x0C, 0x0A, 0x08, 0x07, 0x07, 0x08, 0x09, 0x07, 0x06, 0x07, 0x08,
|
||||
0x07, 0x06, 0x07, 0x07, 0x07, 0x07, 0x08, 0x07, 0x07, 0x08, 0x08, 0x0C, 0x0B, 0x07, 0x09, 0x0B,
|
||||
0x0C, 0x06, 0x07, 0x06, 0x06, 0x05, 0x07, 0x08, 0x08, 0x06, 0x0B, 0x09, 0x06, 0x07, 0x06, 0x06,
|
||||
0x07, 0x0B, 0x06, 0x06, 0x06, 0x07, 0x09, 0x08, 0x09, 0x09, 0x0B, 0x08, 0x0B, 0x09, 0x0C, 0x08,
|
||||
0x0C, 0x05, 0x06, 0x06, 0x06, 0x05, 0x06, 0x06, 0x06, 0x05, 0x0B, 0x07, 0x05, 0x06, 0x05, 0x05,
|
||||
0x06, 0x0A, 0x05, 0x05, 0x05, 0x05, 0x08, 0x07, 0x08, 0x08, 0x0A, 0x0B, 0x0B, 0x0C, 0x0C, 0x0C,
|
||||
0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D,
|
||||
0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D,
|
||||
0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D,
|
||||
0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
|
||||
0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
|
||||
0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
|
||||
0x0D, 0x0C, 0x0D, 0x0D, 0x0D, 0x0C, 0x0D, 0x0D, 0x0D, 0x0C, 0x0D, 0x0D, 0x0D, 0x0D, 0x0C, 0x0D,
|
||||
0x0D, 0x0D, 0x0C, 0x0C, 0x0C, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D
|
||||
};
|
||||
|
||||
static unsigned short ChCodeAsc[] =
|
||||
{
|
||||
0x0490, 0x0FE0, 0x07E0, 0x0BE0, 0x03E0, 0x0DE0, 0x05E0, 0x09E0,
|
||||
0x01E0, 0x00B8, 0x0062, 0x0EE0, 0x06E0, 0x0022, 0x0AE0, 0x02E0,
|
||||
0x0CE0, 0x04E0, 0x08E0, 0x00E0, 0x0F60, 0x0760, 0x0B60, 0x0360,
|
||||
0x0D60, 0x0560, 0x1240, 0x0960, 0x0160, 0x0E60, 0x0660, 0x0A60,
|
||||
0x000F, 0x0250, 0x0038, 0x0260, 0x0050, 0x0C60, 0x0390, 0x00D8,
|
||||
0x0042, 0x0002, 0x0058, 0x01B0, 0x007C, 0x0029, 0x003C, 0x0098,
|
||||
0x005C, 0x0009, 0x001C, 0x006C, 0x002C, 0x004C, 0x0018, 0x000C,
|
||||
0x0074, 0x00E8, 0x0068, 0x0460, 0x0090, 0x0034, 0x00B0, 0x0710,
|
||||
0x0860, 0x0031, 0x0054, 0x0011, 0x0021, 0x0017, 0x0014, 0x00A8,
|
||||
0x0028, 0x0001, 0x0310, 0x0130, 0x003E, 0x0064, 0x001E, 0x002E,
|
||||
0x0024, 0x0510, 0x000E, 0x0036, 0x0016, 0x0044, 0x0030, 0x00C8,
|
||||
0x01D0, 0x00D0, 0x0110, 0x0048, 0x0610, 0x0150, 0x0060, 0x0088,
|
||||
0x0FA0, 0x0007, 0x0026, 0x0006, 0x003A, 0x001B, 0x001A, 0x002A,
|
||||
0x000A, 0x000B, 0x0210, 0x0004, 0x0013, 0x0032, 0x0003, 0x001D,
|
||||
0x0012, 0x0190, 0x000D, 0x0015, 0x0005, 0x0019, 0x0008, 0x0078,
|
||||
0x00F0, 0x0070, 0x0290, 0x0410, 0x0010, 0x07A0, 0x0BA0, 0x03A0,
|
||||
0x0240, 0x1C40, 0x0C40, 0x1440, 0x0440, 0x1840, 0x0840, 0x1040,
|
||||
0x0040, 0x1F80, 0x0F80, 0x1780, 0x0780, 0x1B80, 0x0B80, 0x1380,
|
||||
0x0380, 0x1D80, 0x0D80, 0x1580, 0x0580, 0x1980, 0x0980, 0x1180,
|
||||
0x0180, 0x1E80, 0x0E80, 0x1680, 0x0680, 0x1A80, 0x0A80, 0x1280,
|
||||
0x0280, 0x1C80, 0x0C80, 0x1480, 0x0480, 0x1880, 0x0880, 0x1080,
|
||||
0x0080, 0x1F00, 0x0F00, 0x1700, 0x0700, 0x1B00, 0x0B00, 0x1300,
|
||||
0x0DA0, 0x05A0, 0x09A0, 0x01A0, 0x0EA0, 0x06A0, 0x0AA0, 0x02A0,
|
||||
0x0CA0, 0x04A0, 0x08A0, 0x00A0, 0x0F20, 0x0720, 0x0B20, 0x0320,
|
||||
0x0D20, 0x0520, 0x0920, 0x0120, 0x0E20, 0x0620, 0x0A20, 0x0220,
|
||||
0x0C20, 0x0420, 0x0820, 0x0020, 0x0FC0, 0x07C0, 0x0BC0, 0x03C0,
|
||||
0x0DC0, 0x05C0, 0x09C0, 0x01C0, 0x0EC0, 0x06C0, 0x0AC0, 0x02C0,
|
||||
0x0CC0, 0x04C0, 0x08C0, 0x00C0, 0x0F40, 0x0740, 0x0B40, 0x0340,
|
||||
0x0300, 0x0D40, 0x1D00, 0x0D00, 0x1500, 0x0540, 0x0500, 0x1900,
|
||||
0x0900, 0x0940, 0x1100, 0x0100, 0x1E00, 0x0E00, 0x0140, 0x1600,
|
||||
0x0600, 0x1A00, 0x0E40, 0x0640, 0x0A40, 0x0A00, 0x1200, 0x0200,
|
||||
0x1C00, 0x0C00, 0x1400, 0x0400, 0x1800, 0x0800, 0x1000, 0x0000
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Macros
|
||||
|
||||
// Macro for calculating hash of the current byte pair.
|
||||
// Note that most exact byte pair hash would be buffer[0] + buffer[1] << 0x08,
|
||||
// but even this way gives nice indication of equal byte pairs, with significantly
|
||||
// smaller size of the array that holds numbers of those hashes
|
||||
#define BYTE_PAIR_HASH(buffer) ((buffer[0] * 4) + (buffer[1] * 5))
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Local functions
|
||||
|
||||
// Builds the "hash_to_index" table and "pair_hash_offsets" table.
|
||||
// Every element of "hash_to_index" will contain lowest index to the
|
||||
// "pair_hash_offsets" table, effectively giving offset of the first
|
||||
// occurence of the given PAIR_HASH in the input data.
|
||||
static void PKWAREAPI SortBuffer(TCmpStruct * pWork, unsigned char * buffer_begin, unsigned char * buffer_end)
|
||||
{
|
||||
unsigned short * phash_to_index;
|
||||
unsigned char * buffer_ptr;
|
||||
unsigned short total_sum = 0;
|
||||
unsigned long byte_pair_hash; // Hash value of the byte pair
|
||||
unsigned short byte_pair_offs; // Offset of the byte pair, relative to "work_buff"
|
||||
|
||||
// Zero the entire "phash_to_index" table
|
||||
memset(pWork->phash_to_index, 0, sizeof(pWork->phash_to_index));
|
||||
|
||||
// Step 1: Count amount of each PAIR_HASH in the input buffer
|
||||
// The table will look like this:
|
||||
// offs 0x000: Number of occurences of PAIR_HASH 0
|
||||
// offs 0x001: Number of occurences of PAIR_HASH 1
|
||||
// ...
|
||||
// offs 0x8F7: Number of occurences of PAIR_HASH 0x8F7 (the highest hash value)
|
||||
for(buffer_ptr = buffer_begin; buffer_ptr < buffer_end; buffer_ptr++)
|
||||
pWork->phash_to_index[BYTE_PAIR_HASH(buffer_ptr)]++;
|
||||
|
||||
// Step 2: Convert the table to the array of PAIR_HASH amounts.
|
||||
// Each element contains count of PAIR_HASHes that is less or equal
|
||||
// to element index
|
||||
// The table will look like this:
|
||||
// offs 0x000: Number of occurences of PAIR_HASH 0 or lower
|
||||
// offs 0x001: Number of occurences of PAIR_HASH 1 or lower
|
||||
// ...
|
||||
// offs 0x8F7: Number of occurences of PAIR_HASH 0x8F7 or lower
|
||||
for(phash_to_index = pWork->phash_to_index; phash_to_index < &pWork->phash_to_index_end; phash_to_index++)
|
||||
{
|
||||
total_sum = total_sum + phash_to_index[0];
|
||||
phash_to_index[0] = total_sum;
|
||||
}
|
||||
|
||||
// Step 3: Convert the table to the array of indexes.
|
||||
// Now, each element contains index to the first occurence of given PAIR_HASH
|
||||
for(buffer_end--; buffer_end >= buffer_begin; buffer_end--)
|
||||
{
|
||||
byte_pair_hash = BYTE_PAIR_HASH(buffer_end);
|
||||
byte_pair_offs = (unsigned short)(buffer_end - pWork->work_buff);
|
||||
|
||||
pWork->phash_to_index[byte_pair_hash]--;
|
||||
pWork->phash_offs[pWork->phash_to_index[byte_pair_hash]] = byte_pair_offs;
|
||||
}
|
||||
}
|
||||
|
||||
static void PKWAREAPI FlushBuf(TCmpStruct * pWork)
|
||||
{
|
||||
unsigned char save_ch1;
|
||||
unsigned char save_ch2;
|
||||
unsigned int size = 0x800;
|
||||
|
||||
pWork->write_buf(pWork->out_buff, &size, pWork->param);
|
||||
|
||||
save_ch1 = pWork->out_buff[0x800];
|
||||
save_ch2 = pWork->out_buff[pWork->out_bytes];
|
||||
pWork->out_bytes -= 0x800;
|
||||
|
||||
memset(pWork->out_buff, 0, sizeof(pWork->out_buff));
|
||||
|
||||
if(pWork->out_bytes != 0)
|
||||
pWork->out_buff[0] = save_ch1;
|
||||
if(pWork->out_bits != 0)
|
||||
pWork->out_buff[pWork->out_bytes] = save_ch2;
|
||||
}
|
||||
|
||||
static void PKWAREAPI OutputBits(TCmpStruct * pWork, unsigned int nbits, unsigned long bit_buff)
|
||||
{
|
||||
unsigned int out_bits;
|
||||
|
||||
// If more than 8 bits to output, do recursion
|
||||
if(nbits > 8)
|
||||
{
|
||||
OutputBits(pWork, 8, bit_buff);
|
||||
bit_buff >>= 8;
|
||||
nbits -= 8;
|
||||
}
|
||||
|
||||
// Add bits to the last out byte in out_buff;
|
||||
out_bits = pWork->out_bits;
|
||||
pWork->out_buff[pWork->out_bytes] |= (unsigned char)(bit_buff << out_bits);
|
||||
pWork->out_bits += nbits;
|
||||
|
||||
// If 8 or more bits, increment number of bytes
|
||||
if(pWork->out_bits > 8)
|
||||
{
|
||||
pWork->out_bytes++;
|
||||
bit_buff >>= (8 - out_bits);
|
||||
|
||||
pWork->out_buff[pWork->out_bytes] = (unsigned char)bit_buff;
|
||||
pWork->out_bits &= 7;
|
||||
}
|
||||
else
|
||||
{
|
||||
pWork->out_bits &= 7;
|
||||
if(pWork->out_bits == 0)
|
||||
pWork->out_bytes++;
|
||||
}
|
||||
|
||||
// If there is enough compressed bytes, flush them
|
||||
if(pWork->out_bytes >= 0x800)
|
||||
FlushBuf(pWork);
|
||||
}
|
||||
|
||||
// This function searches for a repetition
|
||||
// (a previous occurence of the current byte sequence)
|
||||
// Returns length of the repetition, and stores the backward distance
|
||||
// to pWork structure.
|
||||
static unsigned int PKWAREAPI FindRep(TCmpStruct * pWork, unsigned char * input_data)
|
||||
{
|
||||
unsigned short * phash_to_index; // Pointer into pWork->phash_to_index table
|
||||
unsigned short * phash_offs; // Pointer to the table containing offsets of each PAIR_HASH
|
||||
unsigned char * repetition_limit; // An eventual repetition must be at position below this pointer
|
||||
unsigned char * prev_repetition; // Pointer to the previous occurence of the current PAIR_HASH
|
||||
unsigned char * prev_rep_end; // End of the previous repetition
|
||||
unsigned char * input_data_ptr;
|
||||
unsigned short phash_offs_index; // Index to the table with PAIR_HASH positions
|
||||
unsigned short min_phash_offs; // The lowest allowed hash offset
|
||||
unsigned short offs_in_rep; // Offset within found repetition
|
||||
unsigned int equal_byte_count; // Number of bytes that are equal to the previous occurence
|
||||
unsigned int rep_length = 1; // Length of the found repetition
|
||||
unsigned int rep_length2; // Secondary repetition
|
||||
unsigned char pre_last_byte; // Last but one byte from a repetion
|
||||
unsigned short di_val;
|
||||
|
||||
// Calculate the previous position of the PAIR_HASH
|
||||
phash_to_index = pWork->phash_to_index + BYTE_PAIR_HASH(input_data);
|
||||
min_phash_offs = (unsigned short)((input_data - pWork->work_buff) - pWork->dsize_bytes + 1);
|
||||
phash_offs_index = phash_to_index[0];
|
||||
|
||||
// If the PAIR_HASH offset is below the limit, find a next one
|
||||
phash_offs = pWork->phash_offs + phash_offs_index;
|
||||
if(*phash_offs < min_phash_offs)
|
||||
{
|
||||
while(*phash_offs < min_phash_offs)
|
||||
{
|
||||
phash_offs_index++;
|
||||
phash_offs++;
|
||||
}
|
||||
*phash_to_index = phash_offs_index;
|
||||
}
|
||||
|
||||
// Get the first location of the PAIR_HASH,
|
||||
// and thus the first eventual location of byte repetition
|
||||
phash_offs = pWork->phash_offs + phash_offs_index;
|
||||
prev_repetition = pWork->work_buff + phash_offs[0];
|
||||
repetition_limit = input_data - 1;
|
||||
|
||||
// If the current PAIR_HASH was not encountered before,
|
||||
// we haven't found a repetition.
|
||||
if(prev_repetition >= repetition_limit)
|
||||
return 0;
|
||||
|
||||
// We have found a match of a PAIR_HASH. Now we have to make sure
|
||||
// that it is also a byte match, because PAIR_HASH is not unique.
|
||||
// We compare the bytes and count the length of the repetition
|
||||
input_data_ptr = input_data;
|
||||
for(;;)
|
||||
{
|
||||
// If the first byte of the repetition and the so-far-last byte
|
||||
// of the repetition are equal, we will compare the blocks.
|
||||
if(*input_data_ptr == *prev_repetition && input_data_ptr[rep_length-1] == prev_repetition[rep_length-1])
|
||||
{
|
||||
// Skip the current byte
|
||||
prev_repetition++;
|
||||
input_data_ptr++;
|
||||
equal_byte_count = 2;
|
||||
|
||||
// Now count how many more bytes are equal
|
||||
while(equal_byte_count < MAX_REP_LENGTH)
|
||||
{
|
||||
prev_repetition++;
|
||||
input_data_ptr++;
|
||||
|
||||
// Are the bytes different ?
|
||||
if(*prev_repetition != *input_data_ptr)
|
||||
break;
|
||||
|
||||
equal_byte_count++;
|
||||
}
|
||||
|
||||
// If we found a repetition of at least the same length, take it.
|
||||
// If there are multiple repetitions in the input buffer, this will
|
||||
// make sure that we find the most recent one, which in turn allows
|
||||
// us to store backward length in less amount of bits
|
||||
input_data_ptr = input_data;
|
||||
if(equal_byte_count >= rep_length)
|
||||
{
|
||||
// Calculate the backward distance of the repetition.
|
||||
// Note that the distance is stored as decremented by 1
|
||||
pWork->distance = (unsigned int)(input_data - prev_repetition + equal_byte_count - 1);
|
||||
|
||||
// Repetitions longer than 10 bytes will be stored in more bits,
|
||||
// so they need a bit different handling
|
||||
if((rep_length = equal_byte_count) > 10)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Move forward in the table of PAIR_HASH repetitions.
|
||||
// There might be a more recent occurence of the same repetition.
|
||||
phash_offs_index++;
|
||||
phash_offs++;
|
||||
prev_repetition = pWork->work_buff + phash_offs[0];
|
||||
|
||||
// If the next repetition is beyond the minimum allowed repetition, we are done.
|
||||
if(prev_repetition >= repetition_limit)
|
||||
{
|
||||
// A repetition must have at least 2 bytes, otherwise it's not worth it
|
||||
return (rep_length >= 2) ? rep_length : 0;
|
||||
}
|
||||
}
|
||||
|
||||
// If the repetition has max length of 0x204 bytes, we can't go any fuhrter
|
||||
if(equal_byte_count == MAX_REP_LENGTH)
|
||||
{
|
||||
pWork->distance--;
|
||||
return equal_byte_count;
|
||||
}
|
||||
|
||||
// Check for possibility of a repetition that occurs at more recent position
|
||||
phash_offs = pWork->phash_offs + phash_offs_index;
|
||||
if(pWork->work_buff + phash_offs[1] >= repetition_limit)
|
||||
return rep_length;
|
||||
|
||||
//
|
||||
// The following part checks if there isn't a longer repetition at
|
||||
// a latter offset, that would lead to better compression.
|
||||
//
|
||||
// Example of data that can trigger this optimization:
|
||||
//
|
||||
// "EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEQQQQQQQQQQQQ"
|
||||
// "XYZ"
|
||||
// "EEEEEEEEEEEEEEEEQQQQQQQQQQQQ";
|
||||
//
|
||||
// Description of data in this buffer
|
||||
// [0x00] Single byte "E"
|
||||
// [0x01] Single byte "E"
|
||||
// [0x02] Repeat 0x1E bytes from [0x00]
|
||||
// [0x20] Single byte "X"
|
||||
// [0x21] Single byte "Y"
|
||||
// [0x22] Single byte "Z"
|
||||
// [0x23] 17 possible previous repetitions of length at least 0x10 bytes:
|
||||
// - Repetition of 0x10 bytes from [0x00] "EEEEEEEEEEEEEEEE"
|
||||
// - Repetition of 0x10 bytes from [0x01] "EEEEEEEEEEEEEEEE"
|
||||
// - Repetition of 0x10 bytes from [0x02] "EEEEEEEEEEEEEEEE"
|
||||
// ...
|
||||
// - Repetition of 0x10 bytes from [0x0F] "EEEEEEEEEEEEEEEE"
|
||||
// - Repetition of 0x1C bytes from [0x10] "EEEEEEEEEEEEEEEEQQQQQQQQQQQQ"
|
||||
// The last repetition is the best one.
|
||||
//
|
||||
|
||||
pWork->offs09BC[0] = 0xFFFF;
|
||||
pWork->offs09BC[1] = 0x0000;
|
||||
di_val = 0;
|
||||
|
||||
// Note: I failed to figure out what does the table "offs09BC" mean.
|
||||
// If anyone has an idea, let me know to zezula_at_volny_dot_cz
|
||||
for(offs_in_rep = 1; offs_in_rep < rep_length; )
|
||||
{
|
||||
if(input_data[offs_in_rep] != input_data[di_val])
|
||||
{
|
||||
di_val = pWork->offs09BC[di_val];
|
||||
if(di_val != 0xFFFF)
|
||||
continue;
|
||||
}
|
||||
pWork->offs09BC[++offs_in_rep] = ++di_val;
|
||||
}
|
||||
|
||||
//
|
||||
// Now go through all the repetitions from the first found one
|
||||
// to the current input data, and check if any of them migh be
|
||||
// a start of a greater sequence match.
|
||||
//
|
||||
|
||||
prev_repetition = pWork->work_buff + phash_offs[0];
|
||||
prev_rep_end = prev_repetition + rep_length;
|
||||
rep_length2 = rep_length;
|
||||
|
||||
for(;;)
|
||||
{
|
||||
rep_length2 = pWork->offs09BC[rep_length2];
|
||||
if(rep_length2 == 0xFFFF)
|
||||
rep_length2 = 0;
|
||||
|
||||
// Get the pointer to the previous repetition
|
||||
phash_offs = pWork->phash_offs + phash_offs_index;
|
||||
|
||||
// Skip those repetitions that don't reach the end
|
||||
// of the first found repetition
|
||||
do
|
||||
{
|
||||
phash_offs++;
|
||||
phash_offs_index++;
|
||||
prev_repetition = pWork->work_buff + *phash_offs;
|
||||
if(prev_repetition >= repetition_limit)
|
||||
return rep_length;
|
||||
}
|
||||
while(prev_repetition + rep_length2 < prev_rep_end);
|
||||
|
||||
// Verify if the last but one byte from the repetition matches
|
||||
// the last but one byte from the input data.
|
||||
// If not, find a next repetition
|
||||
pre_last_byte = input_data[rep_length - 2];
|
||||
if(pre_last_byte == prev_repetition[rep_length - 2])
|
||||
{
|
||||
// If the new repetition reaches beyond the end
|
||||
// of previously found repetition, reset the repetition length to zero.
|
||||
if(prev_repetition + rep_length2 != prev_rep_end)
|
||||
{
|
||||
prev_rep_end = prev_repetition;
|
||||
rep_length2 = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
phash_offs = pWork->phash_offs + phash_offs_index;
|
||||
do
|
||||
{
|
||||
phash_offs++;
|
||||
phash_offs_index++;
|
||||
prev_repetition = pWork->work_buff + *phash_offs;
|
||||
if(prev_repetition >= repetition_limit)
|
||||
return rep_length;
|
||||
}
|
||||
while(prev_repetition[rep_length - 2] != pre_last_byte || prev_repetition[0] != input_data[0]);
|
||||
|
||||
// Reset the length of the repetition to 2 bytes only
|
||||
prev_rep_end = prev_repetition + 2;
|
||||
rep_length2 = 2;
|
||||
}
|
||||
|
||||
// Find out how many more characters are equal to the first repetition.
|
||||
while(*prev_rep_end == input_data[rep_length2])
|
||||
{
|
||||
if(++rep_length2 >= 0x204)
|
||||
break;
|
||||
prev_rep_end++;
|
||||
}
|
||||
|
||||
// Is the newly found repetion at least as long as the previous one ?
|
||||
if(rep_length2 >= rep_length)
|
||||
{
|
||||
// Calculate the distance of the new repetition
|
||||
pWork->distance = (unsigned int)(input_data - prev_repetition - 1);
|
||||
if((rep_length = rep_length2) == 0x204)
|
||||
return rep_length;
|
||||
|
||||
// Update the additional elements in the "offs09BC" table
|
||||
// to reflect new rep length
|
||||
while(offs_in_rep < rep_length2)
|
||||
{
|
||||
if(input_data[offs_in_rep] != input_data[di_val])
|
||||
{
|
||||
di_val = pWork->offs09BC[di_val];
|
||||
if(di_val != 0xFFFF)
|
||||
continue;
|
||||
}
|
||||
pWork->offs09BC[++offs_in_rep] = ++di_val;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void PKWAREAPI WriteCmpData(TCmpStruct * pWork)
|
||||
{
|
||||
unsigned char * input_data_end; // Pointer to the end of the input data
|
||||
unsigned char * input_data = pWork->work_buff + pWork->dsize_bytes + 0x204;
|
||||
unsigned int input_data_ended = 0; // If 1, then all data from the input stream have been already loaded
|
||||
unsigned int save_rep_length; // Saved length of current repetition
|
||||
unsigned int save_distance = 0; // Saved distance of current repetition
|
||||
unsigned int rep_length; // Length of the found repetition
|
||||
unsigned int phase = 0; //
|
||||
|
||||
// Store the compression type and dictionary size
|
||||
pWork->out_buff[0] = (char)pWork->ctype;
|
||||
pWork->out_buff[1] = (char)pWork->dsize_bits;
|
||||
pWork->out_bytes = 2;
|
||||
|
||||
// Reset output buffer to zero
|
||||
memset(&pWork->out_buff[2], 0, sizeof(pWork->out_buff) - 2);
|
||||
pWork->out_bits = 0;
|
||||
|
||||
while(input_data_ended == 0)
|
||||
{
|
||||
unsigned int bytes_to_load = 0x1000;
|
||||
int total_loaded = 0;
|
||||
int bytes_loaded;
|
||||
|
||||
// Load the bytes from the input stream, up to 0x1000 bytes
|
||||
while(bytes_to_load != 0)
|
||||
{
|
||||
bytes_loaded = pWork->read_buf((char *)pWork->work_buff + pWork->dsize_bytes + 0x204 + total_loaded,
|
||||
&bytes_to_load,
|
||||
pWork->param);
|
||||
if(bytes_loaded == 0)
|
||||
{
|
||||
if(total_loaded == 0 && phase == 0)
|
||||
goto __Exit;
|
||||
input_data_ended = 1;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
bytes_to_load -= bytes_loaded;
|
||||
total_loaded += bytes_loaded;
|
||||
}
|
||||
}
|
||||
|
||||
input_data_end = pWork->work_buff + pWork->dsize_bytes + total_loaded;
|
||||
if(input_data_ended)
|
||||
input_data_end += 0x204;
|
||||
|
||||
//
|
||||
// Warning: The end of the buffer passed to "SortBuffer" is actually 2 bytes beyond
|
||||
// valid data. It is questionable if this is actually a bug or not,
|
||||
// but it might cause the compressed data output to be dependent on random bytes
|
||||
// that are in the buffer.
|
||||
// To prevent that, the calling application must always zero the compression
|
||||
// buffer before passing it to "implode"
|
||||
//
|
||||
|
||||
// Search the PAIR_HASHes of the loaded blocks. Also, include
|
||||
// previously compressed data, if any.
|
||||
switch(phase)
|
||||
{
|
||||
case 0:
|
||||
SortBuffer(pWork, input_data, input_data_end + 1);
|
||||
phase++;
|
||||
if(pWork->dsize_bytes != 0x1000)
|
||||
phase++;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
SortBuffer(pWork, input_data - pWork->dsize_bytes + 0x204, input_data_end + 1);
|
||||
phase++;
|
||||
break;
|
||||
|
||||
default:
|
||||
SortBuffer(pWork, input_data - pWork->dsize_bytes, input_data_end + 1);
|
||||
break;
|
||||
}
|
||||
|
||||
// Perform the compression of the current block
|
||||
while(input_data < input_data_end)
|
||||
{
|
||||
// Find if the current byte sequence wasn't there before.
|
||||
rep_length = FindRep(pWork, input_data);
|
||||
while(rep_length != 0)
|
||||
{
|
||||
// If we found repetition of 2 bytes, that is 0x100 or fuhrter back,
|
||||
// don't bother. Storing the distance of 0x100 bytes would actually
|
||||
// take more space than storing the 2 bytes as-is.
|
||||
if(rep_length == 2 && pWork->distance >= 0x100)
|
||||
break;
|
||||
|
||||
// When we are at the end of the input data, we cannot allow
|
||||
// the repetition to go past the end of the input data.
|
||||
if(input_data_ended && input_data + rep_length > input_data_end)
|
||||
{
|
||||
// Shorten the repetition length so that it only covers valid data
|
||||
rep_length = (unsigned long)(input_data_end - input_data);
|
||||
if(rep_length < 2)
|
||||
break;
|
||||
|
||||
// If we got repetition of 2 bytes, that is 0x100 or more backward, don't bother
|
||||
if(rep_length == 2 && pWork->distance >= 0x100)
|
||||
break;
|
||||
goto __FlushRepetition;
|
||||
}
|
||||
|
||||
if(rep_length >= 8 || input_data + 1 >= input_data_end)
|
||||
goto __FlushRepetition;
|
||||
|
||||
// Try to find better repetition 1 byte later.
|
||||
// Example: "ARROCKFORT" "AROCKFORT"
|
||||
// When "input_data" points to the second string, FindRep
|
||||
// returns the occurence of "AR". But there is longer repetition "ROCKFORT",
|
||||
// beginning 1 byte after.
|
||||
save_rep_length = rep_length;
|
||||
save_distance = pWork->distance;
|
||||
rep_length = FindRep(pWork, input_data + 1);
|
||||
|
||||
// Only use the new repetition if it's length is greater than the previous one
|
||||
if(rep_length > save_rep_length)
|
||||
{
|
||||
// If the new repetition if only 1 byte better
|
||||
// and the previous distance is less than 0x80 bytes, use the previous repetition
|
||||
if(rep_length > save_rep_length + 1 || save_distance > 0x80)
|
||||
{
|
||||
// Flush one byte, so that input_data will point to the secondary repetition
|
||||
OutputBits(pWork, pWork->nChBits[*input_data], pWork->nChCodes[*input_data]);
|
||||
input_data++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Revert to the previous repetition
|
||||
rep_length = save_rep_length;
|
||||
pWork->distance = save_distance;
|
||||
|
||||
__FlushRepetition:
|
||||
|
||||
OutputBits(pWork, pWork->nChBits[rep_length + 0xFE], pWork->nChCodes[rep_length + 0xFE]);
|
||||
if(rep_length == 2)
|
||||
{
|
||||
OutputBits(pWork, pWork->dist_bits[pWork->distance >> 2],
|
||||
pWork->dist_codes[pWork->distance >> 2]);
|
||||
OutputBits(pWork, 2, pWork->distance & 3);
|
||||
}
|
||||
else
|
||||
{
|
||||
OutputBits(pWork, pWork->dist_bits[pWork->distance >> pWork->dsize_bits],
|
||||
pWork->dist_codes[pWork->distance >> pWork->dsize_bits]);
|
||||
OutputBits(pWork, pWork->dsize_bits, pWork->dsize_mask & pWork->distance);
|
||||
}
|
||||
|
||||
// Move the begin of the input data by the length of the repetition
|
||||
input_data += rep_length;
|
||||
goto _00402252;
|
||||
}
|
||||
|
||||
// If there was no previous repetition for the current position in the input data,
|
||||
// just output the 9-bit literal for the one character
|
||||
OutputBits(pWork, pWork->nChBits[*input_data], pWork->nChCodes[*input_data]);
|
||||
input_data++;
|
||||
_00402252:;
|
||||
}
|
||||
|
||||
if(input_data_ended == 0)
|
||||
{
|
||||
input_data -= 0x1000;
|
||||
memmove(pWork->work_buff, pWork->work_buff + 0x1000, pWork->dsize_bytes + 0x204);
|
||||
}
|
||||
}
|
||||
|
||||
__Exit:
|
||||
|
||||
// Write the termination literal
|
||||
OutputBits(pWork, pWork->nChBits[0x305], pWork->nChCodes[0x305]);
|
||||
if(pWork->out_bits != 0)
|
||||
pWork->out_bytes++;
|
||||
pWork->write_buf(pWork->out_buff, &pWork->out_bytes, pWork->param);
|
||||
return;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Main imploding function
|
||||
|
||||
unsigned int PKWAREAPI implode(
|
||||
unsigned int (PKWAREAPI *read_buf)(char *buf, unsigned int *size, void *param),
|
||||
void (PKWAREAPI *write_buf)(char *buf, unsigned int *size, void *param),
|
||||
char *work_buf,
|
||||
void *param,
|
||||
unsigned int *type,
|
||||
unsigned int *dsize)
|
||||
{
|
||||
TCmpStruct * pWork = (TCmpStruct *)work_buf;
|
||||
unsigned int nChCode;
|
||||
unsigned int nCount;
|
||||
unsigned int i;
|
||||
int nCount2;
|
||||
|
||||
// Fill the work buffer information
|
||||
// Note: The caller must zero the "work_buff" before passing it to implode
|
||||
pWork->read_buf = read_buf;
|
||||
pWork->write_buf = write_buf;
|
||||
pWork->dsize_bytes = *dsize;
|
||||
pWork->ctype = *type;
|
||||
pWork->param = param;
|
||||
pWork->dsize_bits = 4;
|
||||
pWork->dsize_mask = 0x0F;
|
||||
|
||||
// Test dictionary size
|
||||
switch(*dsize)
|
||||
{
|
||||
case CMP_IMPLODE_DICT_SIZE3: // 0x1000 bytes
|
||||
pWork->dsize_bits++;
|
||||
pWork->dsize_mask |= 0x20;
|
||||
// No break here !!!
|
||||
|
||||
case CMP_IMPLODE_DICT_SIZE2: // 0x800 bytes
|
||||
pWork->dsize_bits++;
|
||||
pWork->dsize_mask |= 0x10;
|
||||
// No break here !!!
|
||||
|
||||
case CMP_IMPLODE_DICT_SIZE1: // 0x400
|
||||
break;
|
||||
|
||||
default:
|
||||
return CMP_INVALID_DICTSIZE;
|
||||
}
|
||||
|
||||
// Test the compression type
|
||||
switch(*type)
|
||||
{
|
||||
case CMP_BINARY: // We will compress data with binary compression type
|
||||
for(nChCode = 0, nCount = 0; nCount < 0x100; nCount++)
|
||||
{
|
||||
pWork->nChBits[nCount] = 9;
|
||||
pWork->nChCodes[nCount] = (unsigned short)nChCode;
|
||||
nChCode = (nChCode & 0x0000FFFF) + 2;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case CMP_ASCII: // We will compress data with ASCII compression type
|
||||
for(nCount = 0; nCount < 0x100; nCount++)
|
||||
{
|
||||
pWork->nChBits[nCount] = (unsigned char )(ChBitsAsc[nCount] + 1);
|
||||
pWork->nChCodes[nCount] = (unsigned short)(ChCodeAsc[nCount] * 2);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return CMP_INVALID_MODE;
|
||||
}
|
||||
|
||||
for(i = 0; i < 0x10; i++)
|
||||
{
|
||||
if(1 << ExLenBits[i])
|
||||
{
|
||||
for(nCount2 = 0; nCount2 < (1 << ExLenBits[i]); nCount2++)
|
||||
{
|
||||
pWork->nChBits[nCount] = (unsigned char)(ExLenBits[i] + LenBits[i] + 1);
|
||||
pWork->nChCodes[nCount] = (unsigned short)((nCount2 << (LenBits[i] + 1)) | ((LenCode[i] & 0xFFFF00FF) * 2) | 1);
|
||||
nCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the distance codes and distance bits and perform the compression
|
||||
memcpy(&pWork->dist_codes, DistCode, sizeof(DistCode));
|
||||
memcpy(&pWork->dist_bits, DistBits, sizeof(DistBits));
|
||||
WriteCmpData(pWork);
|
||||
return CMP_NO_ERROR;
|
||||
}
|
||||
Vendored
-142
@@ -1,142 +0,0 @@
|
||||
/*****************************************************************************/
|
||||
/* pkware.h Copyright (c) Ladislav Zezula 2003 */
|
||||
/*---------------------------------------------------------------------------*/
|
||||
/* Header file for PKWARE Data Compression Library */
|
||||
/*---------------------------------------------------------------------------*/
|
||||
/* Date Ver Who Comment */
|
||||
/* -------- ---- --- ------- */
|
||||
/* 31.03.03 1.00 Lad The first version of pkware.h */
|
||||
/*****************************************************************************/
|
||||
|
||||
#ifndef __PKWARE_H__
|
||||
#define __PKWARE_H__
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Defines
|
||||
|
||||
#define CMP_BINARY 0 // Binary compression
|
||||
#define CMP_ASCII 1 // Ascii compression
|
||||
|
||||
#define CMP_NO_ERROR 0
|
||||
#define CMP_INVALID_DICTSIZE 1
|
||||
#define CMP_INVALID_MODE 2
|
||||
#define CMP_BAD_DATA 3
|
||||
#define CMP_ABORT 4
|
||||
|
||||
#define CMP_IMPLODE_DICT_SIZE1 1024 // Dictionary size of 1024
|
||||
#define CMP_IMPLODE_DICT_SIZE2 2048 // Dictionary size of 2048
|
||||
#define CMP_IMPLODE_DICT_SIZE3 4096 // Dictionary size of 4096
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Define calling convention
|
||||
|
||||
#ifndef PKWAREAPI
|
||||
#ifdef WIN32
|
||||
#define PKWAREAPI __cdecl // Use for normal __cdecl calling
|
||||
#else
|
||||
#define PKWAREAPI
|
||||
#endif
|
||||
#endif
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Internal structures
|
||||
|
||||
// Compression structure
|
||||
typedef struct
|
||||
{
|
||||
unsigned int distance; // 0000: Backward distance of the currently found repetition, decreased by 1
|
||||
unsigned int out_bytes; // 0004: # bytes available in out_buff
|
||||
unsigned int out_bits; // 0008: # of bits available in the last out byte
|
||||
unsigned int dsize_bits; // 000C: Number of bits needed for dictionary size. 4 = 0x400, 5 = 0x800, 6 = 0x1000
|
||||
unsigned int dsize_mask; // 0010: Bit mask for dictionary. 0x0F = 0x400, 0x1F = 0x800, 0x3F = 0x1000
|
||||
unsigned int ctype; // 0014: Compression type (CMP_ASCII or CMP_BINARY)
|
||||
unsigned int dsize_bytes; // 0018: Dictionary size in bytes
|
||||
unsigned char dist_bits[0x40]; // 001C: Distance bits
|
||||
unsigned char dist_codes[0x40]; // 005C: Distance codes
|
||||
unsigned char nChBits[0x306]; // 009C: Table of literal bit lengths to be put to the output stream
|
||||
unsigned short nChCodes[0x306]; // 03A2: Table of literal codes to be put to the output stream
|
||||
unsigned short offs09AE; // 09AE:
|
||||
|
||||
void * param; // 09B0: User parameter
|
||||
unsigned int (PKWAREAPI *read_buf)(char *buf, unsigned int *size, void *param); // 9B4
|
||||
void (PKWAREAPI *write_buf)(char *buf, unsigned int *size, void *param); // 9B8
|
||||
|
||||
unsigned short offs09BC[0x204]; // 09BC:
|
||||
unsigned long offs0DC4; // 0DC4:
|
||||
unsigned short phash_to_index[0x900]; // 0DC8: Array of indexes (one for each PAIR_HASH) to the "pair_hash_offsets" table
|
||||
unsigned short phash_to_index_end; // 1FC8: End marker for "phash_to_index" table
|
||||
char out_buff[0x802]; // 1FCA: Compressed data
|
||||
unsigned char work_buff[0x2204]; // 27CC: Work buffer
|
||||
// + DICT_OFFSET => Dictionary
|
||||
// + UNCMP_OFFSET => Uncompressed data
|
||||
unsigned short phash_offs[0x2204]; // 49D0: Table of offsets for each PAIR_HASH
|
||||
} TCmpStruct;
|
||||
|
||||
#define CMP_BUFFER_SIZE sizeof(TCmpStruct) // Size of compression structure.
|
||||
// Defined as 36312 in pkware header file
|
||||
|
||||
|
||||
// Decompression structure
|
||||
typedef struct
|
||||
{
|
||||
unsigned long offs0000; // 0000
|
||||
unsigned long ctype; // 0004: Compression type (CMP_BINARY or CMP_ASCII)
|
||||
unsigned long outputPos; // 0008: Position in output buffer
|
||||
unsigned long dsize_bits; // 000C: Dict size (4, 5, 6 for 0x400, 0x800, 0x1000)
|
||||
unsigned long dsize_mask; // 0010: Dict size bitmask (0x0F, 0x1F, 0x3F for 0x400, 0x800, 0x1000)
|
||||
unsigned long bit_buff; // 0014: 16-bit buffer for processing input data
|
||||
unsigned long extra_bits; // 0018: Number of extra (above 8) bits in bit buffer
|
||||
unsigned int in_pos; // 001C: Position in in_buff
|
||||
unsigned long in_bytes; // 0020: Number of bytes in input buffer
|
||||
void * param; // 0024: Custom parameter
|
||||
unsigned int (PKWAREAPI *read_buf)(char *buf, unsigned int *size, void *param); // Pointer to function that reads data from the input stream
|
||||
void (PKWAREAPI *write_buf)(char *buf, unsigned int *size, void *param);// Pointer to function that writes data to the output stream
|
||||
|
||||
unsigned char out_buff[0x2204]; // 0030: Output circle buffer.
|
||||
// 0x0000 - 0x0FFF: Previous uncompressed data, kept for repetitions
|
||||
// 0x1000 - 0x1FFF: Currently decompressed data
|
||||
// 0x2000 - 0x2203: Reserve space for the longest possible repetition
|
||||
unsigned char in_buff[0x800]; // 2234: Buffer for data to be decompressed
|
||||
unsigned char DistPosCodes[0x100]; // 2A34: Table of distance position codes
|
||||
unsigned char LengthCodes[0x100]; // 2B34: Table of length codes
|
||||
unsigned char offs2C34[0x100]; // 2C34: Buffer for
|
||||
unsigned char offs2D34[0x100]; // 2D34: Buffer for
|
||||
unsigned char offs2E34[0x80]; // 2EB4: Buffer for
|
||||
unsigned char offs2EB4[0x100]; // 2EB4: Buffer for
|
||||
unsigned char ChBitsAsc[0x100]; // 2FB4: Buffer for
|
||||
unsigned char DistBits[0x40]; // 30B4: Numbers of bytes to skip copied block length
|
||||
unsigned char LenBits[0x10]; // 30F4: Numbers of bits for skip copied block length
|
||||
unsigned char ExLenBits[0x10]; // 3104: Number of valid bits for copied block
|
||||
unsigned short LenBase[0x10]; // 3114: Buffer for
|
||||
} TDcmpStruct;
|
||||
|
||||
#define EXP_BUFFER_SIZE sizeof(TDcmpStruct) // Size of decompression structure
|
||||
// Defined as 12596 in pkware headers
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Public functions
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
unsigned int PKWAREAPI implode(
|
||||
unsigned int (PKWAREAPI *read_buf)(char *buf, unsigned int *size, void *param),
|
||||
void (PKWAREAPI *write_buf)(char *buf, unsigned int *size, void *param),
|
||||
char *work_buf,
|
||||
void *param,
|
||||
unsigned int *type,
|
||||
unsigned int *dsize);
|
||||
|
||||
|
||||
unsigned int PKWAREAPI explode(
|
||||
unsigned int (PKWAREAPI *read_buf)(char *buf, unsigned int *size, void *param),
|
||||
void (PKWAREAPI *write_buf)(char *buf, unsigned int *size, void *param),
|
||||
char *work_buf,
|
||||
void *param);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // End of 'extern "C"' declaration
|
||||
#endif
|
||||
|
||||
#endif // __PKWARE_H__
|
||||
Vendored
-38
@@ -1,38 +0,0 @@
|
||||
if(NOT TARGET ZLIB::ZLIB)
|
||||
find_package(ZLIB REQUIRED)
|
||||
endif()
|
||||
|
||||
if(NOT TARGET BZip2::BZip2)
|
||||
find_package(BZip2 REQUIRED)
|
||||
endif()
|
||||
|
||||
include(functions/FetchContent_ExcludeFromAll_backport)
|
||||
|
||||
include(FetchContent)
|
||||
FetchContent_Declare_ExcludeFromAll(libmpq
|
||||
URL https://github.com/diasurgical/libmpq/archive/7c2924d4553513eba1a70bbdb558198dd8c2726a.tar.gz
|
||||
URL_HASH MD5=315c88c02b45851cdfee8460322de044
|
||||
)
|
||||
FetchContent_MakeAvailable_ExcludeFromAll(libmpq)
|
||||
|
||||
add_library(libmpq STATIC
|
||||
${libmpq_SOURCE_DIR}/libmpq/common.c
|
||||
${libmpq_SOURCE_DIR}/libmpq/explode.c
|
||||
${libmpq_SOURCE_DIR}/libmpq/extract.c
|
||||
${libmpq_SOURCE_DIR}/libmpq/huffman.c
|
||||
${libmpq_SOURCE_DIR}/libmpq/mpq.c
|
||||
${libmpq_SOURCE_DIR}/libmpq/wave.c
|
||||
)
|
||||
|
||||
target_include_directories(libmpq PUBLIC ${libmpq_SOURCE_DIR})
|
||||
target_include_directories(libmpq PRIVATE ${CMAKE_CURRENT_LIST_DIR})
|
||||
|
||||
target_link_libraries(libmpq PRIVATE ZLIB::ZLIB BZip2::BZip2)
|
||||
|
||||
if(LIBMPQ_FILE_BUFFER_SIZE)
|
||||
target_compile_definitions(libmpq PRIVATE "LIBMPQ_FILE_BUFFER_SIZE=${LIBMPQ_FILE_BUFFER_SIZE}")
|
||||
endif()
|
||||
|
||||
if(DEVILUTIONX_WINDOWS_NO_WCHAR)
|
||||
target_compile_definitions(libmpq PRIVATE LIBMPQ_WINDOWS_NO_WCHAR)
|
||||
endif()
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
#define VERSION "0.4.2"
|
||||
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
include(functions/FetchContent_ExcludeFromAll_backport)
|
||||
|
||||
include(FetchContent)
|
||||
|
||||
FetchContent_Declare_ExcludeFromAll(mpqfs
|
||||
GIT_REPOSITORY https://github.com/diasurgical/mpqfs.git
|
||||
GIT_TAG 9423bd48d04c8a0c51d3df6ce328e66785e3b441
|
||||
)
|
||||
FetchContent_MakeAvailable_ExcludeFromAll(mpqfs)
|
||||
|
||||
if(MPQFS_FILE_BUFFER_SIZE)
|
||||
target_compile_definitions(mpqfs PRIVATE "MPQFS_FILE_BUFFER_SIZE=${MPQFS_FILE_BUFFER_SIZE}")
|
||||
endif()
|
||||
@@ -13,7 +13,7 @@ else()
|
||||
endif()
|
||||
|
||||
if(SUPPORTS_MPQ)
|
||||
# bzip2 is a libmpq dependency.
|
||||
# bzip2 is an mpqfs dependency.
|
||||
if(EMSCRIPTEN)
|
||||
emscripten_system_library("bzip2" BZip2::BZip2 USE_BZIP2=1)
|
||||
else()
|
||||
@@ -248,7 +248,8 @@ if(WIN32 AND NOT UWP_LIB)
|
||||
endif()
|
||||
|
||||
if(SUPPORTS_MPQ)
|
||||
add_subdirectory(3rdParty/libmpq)
|
||||
set(MPQFS_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||
add_subdirectory(3rdParty/mpqfs)
|
||||
endif()
|
||||
|
||||
add_subdirectory(3rdParty/tl)
|
||||
@@ -301,10 +302,6 @@ else()
|
||||
add_subdirectory(3rdParty/magic_enum)
|
||||
endif()
|
||||
|
||||
if(SUPPORTS_MPQ OR NOT NONET)
|
||||
add_subdirectory(3rdParty/PKWare)
|
||||
endif()
|
||||
|
||||
if(NOT NONET AND NOT DISABLE_TCP)
|
||||
add_subdirectory(3rdParty/asio)
|
||||
endif()
|
||||
|
||||
@@ -8,7 +8,7 @@ set(DEVILUTIONX_SYSTEM_LIBFMT OFF)
|
||||
set(DEVILUTIONX_STATIC_LIBSODIUM ON)
|
||||
set(DEVILUTIONX_STATIC_LIBFMT ON)
|
||||
set(DISABLE_ZERO_TIER ON)
|
||||
set(LIBMPQ_FILE_BUFFER_SIZE 32768)
|
||||
set(MPQFS_FILE_BUFFER_SIZE 32768)
|
||||
set(NOEXIT ON)
|
||||
|
||||
# 3DS libraries and compile definitions
|
||||
|
||||
@@ -7,6 +7,12 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/xbox_nxdk/finders")
|
||||
set(DEVILUTIONX_SYSTEM_BZIP2 OFF)
|
||||
set(DEVILUTIONX_SYSTEM_LIBFMT OFF)
|
||||
|
||||
# nxdk's zlib is built with Z_SOLO, which excludes utility functions like
|
||||
# uncompress() that mpqfs needs for zlib-compressed MPQ sectors.
|
||||
# Diablo/Hellfire MPQs only use PKWARE DCL implode (and bzip2 for fonts.mpq),
|
||||
# so zlib decompression in mpqfs is not needed.
|
||||
set(MPQFS_USE_ZLIB OFF CACHE BOOL "" FORCE)
|
||||
|
||||
set(BUILD_ASSETS_MPQ OFF)
|
||||
set(DEVILUTIONX_ASSETS_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/pkg/assets")
|
||||
set(DEVILUTIONX_WINDOWS_NO_WCHAR ON)
|
||||
|
||||
@@ -590,12 +590,8 @@ if(SUPPORTS_MPQ)
|
||||
target_link_dependencies(libdevilutionx_mpq PUBLIC
|
||||
DevilutionX::SDL
|
||||
fmt::fmt
|
||||
tl
|
||||
libmpq
|
||||
mpqfs::mpqfs
|
||||
libdevilutionx_file_util
|
||||
libdevilutionx_logged_fstream
|
||||
libdevilutionx_pkware_encrypt
|
||||
libdevilutionx_strings
|
||||
)
|
||||
else()
|
||||
add_library(libdevilutionx_mpq INTERFACE)
|
||||
@@ -642,7 +638,7 @@ if(SUPPORTS_MPQ OR NOT NONET)
|
||||
)
|
||||
target_link_dependencies(libdevilutionx_pkware_encrypt PUBLIC
|
||||
DevilutionX::SDL
|
||||
PKWare
|
||||
mpqfs::mpqfs
|
||||
)
|
||||
else()
|
||||
add_library(libdevilutionx_pkware_encrypt INTERFACE)
|
||||
|
||||
+46
-85
@@ -3,114 +3,75 @@
|
||||
*
|
||||
* Implementation of functions for compression and decompressing MPQ data.
|
||||
*/
|
||||
#include <array>
|
||||
#include <cctype>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
#include <pkware.h>
|
||||
#include <mpqfs/mpqfs.h>
|
||||
|
||||
#include "encrypt.h"
|
||||
|
||||
namespace devilution {
|
||||
|
||||
namespace {
|
||||
|
||||
struct TDataInfo {
|
||||
std::byte *srcData;
|
||||
uint32_t srcOffset;
|
||||
uint32_t srcSize;
|
||||
std::byte *destData;
|
||||
uint32_t destOffset;
|
||||
size_t destSize;
|
||||
bool error;
|
||||
};
|
||||
|
||||
unsigned int PkwareBufferRead(char *buf, unsigned int *size, void *param) // NOLINT(readability-non-const-parameter)
|
||||
{
|
||||
auto *pInfo = reinterpret_cast<TDataInfo *>(param);
|
||||
|
||||
uint32_t sSize;
|
||||
if (*size >= pInfo->srcSize - pInfo->srcOffset) {
|
||||
sSize = pInfo->srcSize - pInfo->srcOffset;
|
||||
} else {
|
||||
sSize = *size;
|
||||
}
|
||||
|
||||
memcpy(buf, pInfo->srcData + pInfo->srcOffset, sSize);
|
||||
pInfo->srcOffset += sSize;
|
||||
|
||||
return sSize;
|
||||
}
|
||||
|
||||
void PkwareBufferWrite(char *buf, unsigned int *size, void *param) // NOLINT(readability-non-const-parameter)
|
||||
{
|
||||
auto *pInfo = reinterpret_cast<TDataInfo *>(param);
|
||||
|
||||
pInfo->error = pInfo->error || pInfo->destOffset + *size > pInfo->destSize;
|
||||
if (pInfo->error) {
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(pInfo->destData + pInfo->destOffset, buf, *size);
|
||||
pInfo->destOffset += *size;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
uint32_t PkwareCompress(std::byte *srcData, uint32_t size)
|
||||
{
|
||||
const std::unique_ptr<char[]> ptr = std::make_unique<char[]>(CMP_BUFFER_SIZE);
|
||||
// Stack buffer covers typical network messages and MPQ sectors (≤ 4096).
|
||||
// Falls back to heap for larger payloads.
|
||||
constexpr size_t StackBufSize = 4096 * 2 + 64;
|
||||
uint8_t stackBuf[StackBufSize];
|
||||
|
||||
unsigned destSize = 2 * size;
|
||||
if (destSize < 2 * 4096)
|
||||
destSize = 2 * 4096;
|
||||
size_t dstCap = static_cast<size_t>(size) * 2 + 64;
|
||||
uint8_t *dst;
|
||||
std::unique_ptr<uint8_t[]> heapBuf;
|
||||
|
||||
const std::unique_ptr<std::byte[]> destData { new std::byte[destSize] };
|
||||
|
||||
TDataInfo param;
|
||||
param.srcData = srcData;
|
||||
param.srcOffset = 0;
|
||||
param.srcSize = size;
|
||||
param.destData = destData.get();
|
||||
param.destOffset = 0;
|
||||
param.destSize = destSize;
|
||||
param.error = false;
|
||||
|
||||
unsigned type = 0;
|
||||
unsigned dsize = 4096;
|
||||
implode(PkwareBufferRead, PkwareBufferWrite, ptr.get(), ¶m, &type, &dsize);
|
||||
|
||||
if (param.destOffset < size) {
|
||||
memcpy(srcData, destData.get(), param.destOffset);
|
||||
size = param.destOffset;
|
||||
if (dstCap <= StackBufSize) {
|
||||
dst = stackBuf;
|
||||
} else {
|
||||
heapBuf = std::make_unique<uint8_t[]>(dstCap);
|
||||
dst = heapBuf.get();
|
||||
}
|
||||
|
||||
size_t dstSize = dstCap;
|
||||
int rc = mpqfs_pk_implode(
|
||||
reinterpret_cast<const uint8_t *>(srcData), size,
|
||||
dst, &dstSize, /*dict_bits=*/6);
|
||||
|
||||
if (rc == 0 && dstSize < size) {
|
||||
std::memcpy(srcData, dst, dstSize);
|
||||
return static_cast<uint32_t>(dstSize);
|
||||
}
|
||||
|
||||
// Compression didn't help — return original size.
|
||||
return size;
|
||||
}
|
||||
|
||||
uint32_t PkwareDecompress(std::byte *inBuff, uint32_t recvSize, size_t maxBytes)
|
||||
{
|
||||
const std::unique_ptr<char[]> ptr = std::make_unique<char[]>(CMP_BUFFER_SIZE);
|
||||
const std::unique_ptr<std::byte[]> outBuff { new std::byte[maxBytes] };
|
||||
// Stack buffer covers most decompressed network payloads.
|
||||
// Falls back to heap for larger buffers.
|
||||
constexpr size_t StackBufSize = 8192;
|
||||
uint8_t stackBuf[StackBufSize];
|
||||
|
||||
TDataInfo info;
|
||||
info.srcData = inBuff;
|
||||
info.srcOffset = 0;
|
||||
info.srcSize = recvSize;
|
||||
info.destData = outBuff.get();
|
||||
info.destOffset = 0;
|
||||
info.destSize = maxBytes;
|
||||
info.error = false;
|
||||
uint8_t *out;
|
||||
std::unique_ptr<uint8_t[]> heapBuf;
|
||||
|
||||
explode(PkwareBufferRead, PkwareBufferWrite, ptr.get(), &info);
|
||||
if (info.error) {
|
||||
return 0;
|
||||
if (maxBytes <= StackBufSize) {
|
||||
out = stackBuf;
|
||||
} else {
|
||||
heapBuf = std::make_unique<uint8_t[]>(maxBytes);
|
||||
out = heapBuf.get();
|
||||
}
|
||||
|
||||
memcpy(inBuff, outBuff.get(), info.destOffset);
|
||||
return info.destOffset;
|
||||
size_t outSize = maxBytes;
|
||||
int rc = mpqfs_pk_explode(
|
||||
reinterpret_cast<const uint8_t *>(inBuff), recvSize,
|
||||
out, &outSize);
|
||||
if (rc != 0)
|
||||
return 0;
|
||||
|
||||
std::memcpy(inBuff, out, outSize);
|
||||
return static_cast<uint32_t>(outSize);
|
||||
}
|
||||
|
||||
} // namespace devilution
|
||||
} // namespace devilution
|
||||
@@ -65,13 +65,13 @@ SDL_IOStream *OpenOptionalRWops(const std::string &path)
|
||||
return SDL_IOFromFile(path.c_str(), "rb");
|
||||
};
|
||||
|
||||
bool FindMpqFile(std::string_view filename, MpqArchive **archive, uint32_t *fileNumber)
|
||||
bool FindMpqFile(std::string_view filename, MpqArchive **archive, uint32_t *hashIndex)
|
||||
{
|
||||
const MpqFileHash fileHash = CalculateMpqFileHash(filename);
|
||||
|
||||
for (auto &[_, mpqArchive] : MpqArchives) {
|
||||
if (mpqArchive.GetFileNumber(fileHash, *fileNumber)) {
|
||||
uint32_t hash = mpqArchive.FindHash(filename);
|
||||
if (hash != UINT32_MAX) {
|
||||
*archive = &mpqArchive;
|
||||
*hashIndex = hash;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -155,7 +155,7 @@ AssetRef FindAsset(std::string_view filename)
|
||||
}
|
||||
|
||||
// Look for the file in all the MPQ archives:
|
||||
if (FindMpqFile(filename, &result.archive, &result.fileNumber)) {
|
||||
if (FindMpqFile(filename, &result.archive, &result.hashIndex)) {
|
||||
result.filename = filename;
|
||||
return result;
|
||||
}
|
||||
@@ -185,7 +185,7 @@ AssetHandle OpenAsset(AssetRef &&ref, bool threadsafe)
|
||||
return AssetHandle { OpenFile(ref.path, "rb") };
|
||||
#else
|
||||
if (ref.archive != nullptr)
|
||||
return AssetHandle { SDL_RWops_FromMpqFile(*ref.archive, ref.fileNumber, ref.filename, threadsafe) };
|
||||
return AssetHandle { SDL_RWops_FromMpqFile(*ref.archive, ref.hashIndex, ref.filename, threadsafe) };
|
||||
if (ref.directHandle != nullptr) {
|
||||
// Transfer handle ownership:
|
||||
auto *handle = ref.directHandle;
|
||||
@@ -316,7 +316,7 @@ bool LoadMPQ(std::span<const std::string> paths, std::string_view mpqName, int p
|
||||
return true;
|
||||
}
|
||||
if (error != 0) {
|
||||
LogError("Error {}: {}", MpqArchive::ErrorMessage(error), mpqAbsPath);
|
||||
LogError("Error {}: {}", MpqArchive::ErrorMessage(), mpqAbsPath);
|
||||
}
|
||||
}
|
||||
if (error == 0) {
|
||||
|
||||
@@ -118,7 +118,7 @@ struct AssetHandle {
|
||||
struct AssetRef {
|
||||
// An MPQ file reference:
|
||||
MpqArchive *archive = nullptr;
|
||||
uint32_t fileNumber;
|
||||
uint32_t hashIndex = UINT32_MAX;
|
||||
std::string_view filename;
|
||||
|
||||
// Alternatively, a direct SDL_IOStream handle:
|
||||
@@ -128,7 +128,7 @@ struct AssetRef {
|
||||
|
||||
AssetRef(AssetRef &&other) noexcept
|
||||
: archive(other.archive)
|
||||
, fileNumber(other.fileNumber)
|
||||
, hashIndex(other.hashIndex)
|
||||
, filename(other.filename)
|
||||
, directHandle(other.directHandle)
|
||||
{
|
||||
@@ -139,7 +139,7 @@ struct AssetRef {
|
||||
{
|
||||
closeDirectHandle();
|
||||
archive = other.archive;
|
||||
fileNumber = other.fileNumber;
|
||||
hashIndex = other.hashIndex;
|
||||
filename = other.filename;
|
||||
directHandle = other.directHandle;
|
||||
other.directHandle = nullptr;
|
||||
@@ -165,8 +165,9 @@ struct AssetRef {
|
||||
[[nodiscard]] size_t size() const
|
||||
{
|
||||
if (archive != nullptr) {
|
||||
int32_t error;
|
||||
return archive->GetUnpackedFileSize(fileNumber, error);
|
||||
if (hashIndex != UINT32_MAX)
|
||||
return archive->GetFileSizeFromHash(hashIndex);
|
||||
return archive->GetFileSize(filename);
|
||||
}
|
||||
return static_cast<size_t>(SDL_GetIOSize(directHandle));
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "utils/console.h"
|
||||
#include "utils/display.h"
|
||||
#include "utils/endian_stream.hpp"
|
||||
#include "utils/file_util.h"
|
||||
#include "utils/is_of.hpp"
|
||||
#include "utils/paths.h"
|
||||
#include "utils/str_cat.hpp"
|
||||
|
||||
+1
-5
@@ -37,7 +37,6 @@
|
||||
#include "utils/utf8.hpp"
|
||||
|
||||
#ifndef UNPACKED_MPQS
|
||||
#include "mpq/mpq_common.hpp"
|
||||
#include "mpq/mpq_reader.hpp"
|
||||
#endif
|
||||
|
||||
@@ -99,13 +98,10 @@ bool AreExtraFontsOutOfDate(std::string_view path)
|
||||
bool AreExtraFontsOutOfDate(MpqArchive &archive)
|
||||
{
|
||||
const char filename[] = "fonts\\VERSION";
|
||||
const MpqFileHash fileHash = CalculateMpqFileHash(filename);
|
||||
uint32_t fileNumber;
|
||||
if (!archive.GetFileNumber(fileHash, fileNumber))
|
||||
if (!archive.HasFile(filename))
|
||||
return true;
|
||||
AssetRef ref;
|
||||
ref.archive = &archive;
|
||||
ref.fileNumber = fileNumber;
|
||||
ref.filename = filename;
|
||||
return CheckExtraFontsVersion(std::move(ref));
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include <libmpq/mpq.h>
|
||||
#include <mpqfs/mpqfs.h>
|
||||
|
||||
namespace devilution {
|
||||
|
||||
@@ -10,9 +10,9 @@ namespace devilution {
|
||||
MpqFileHash CalculateMpqFileHash(std::string_view filename)
|
||||
{
|
||||
MpqFileHash fileHash;
|
||||
libmpq__file_hash_s(filename.data(), filename.size(), &fileHash[0], &fileHash[1], &fileHash[2]);
|
||||
mpqfs_file_hash_s(filename.data(), filename.size(), &fileHash[0], &fileHash[1], &fileHash[2]);
|
||||
return fileHash;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace devilution
|
||||
} // namespace devilution
|
||||
@@ -5,94 +5,14 @@
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
|
||||
#include "utils/endian_read.hpp"
|
||||
|
||||
namespace devilution {
|
||||
|
||||
constexpr size_t MaxMpqPathSize = 256;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct MpqFileHeader {
|
||||
static constexpr uint32_t DiabloSignature = LoadLE32("MPQ\x1A");
|
||||
static constexpr uint32_t DiabloSize = 32;
|
||||
|
||||
// The signature, always 0x1A51504D ('MPQ\x1A') for Diablo MPQs.
|
||||
uint32_t signature;
|
||||
|
||||
// The size of this header in bytes, always 32 for Diablo MPQs.
|
||||
uint32_t headerSize;
|
||||
|
||||
// The size of the MPQ file in bytes.
|
||||
uint32_t fileSize;
|
||||
|
||||
// Version, always 0 for Diablo MPQs.
|
||||
uint16_t version;
|
||||
|
||||
// Block size is `512 * 2 ^ blockSizeFactor`.
|
||||
// e.g. if `blockSizeFactor` is 3, the block size is 4096 (512 << 3).
|
||||
uint16_t blockSizeFactor;
|
||||
|
||||
// Location of the hash entries table.
|
||||
uint32_t hashEntriesOffset;
|
||||
|
||||
// Location of the block entries table.
|
||||
uint32_t blockEntriesOffset;
|
||||
|
||||
// Size of the hash entries table (number of entries).
|
||||
uint32_t hashEntriesCount;
|
||||
|
||||
// Size of the block entries table (number of entries).
|
||||
uint32_t blockEntriesCount;
|
||||
|
||||
// Empty space after the header. Not included into `headerSize`.
|
||||
uint8_t pad[72];
|
||||
};
|
||||
|
||||
struct MpqHashEntry {
|
||||
// Special values for the `block` field.
|
||||
// Does not point to a block (unassigned hash entry)
|
||||
static constexpr uint32_t NullBlock = -1;
|
||||
|
||||
// Used to point to a block but is now deleted (can be reclaimed)
|
||||
static constexpr uint32_t DeletedBlock = -2;
|
||||
|
||||
// `hashA` and `hashB` are used for resolving hash index collisions.
|
||||
uint32_t hashA;
|
||||
uint32_t hashB;
|
||||
|
||||
// Always `0` in Diablo.
|
||||
uint16_t locale;
|
||||
|
||||
// Always `0` in Diablo.
|
||||
uint16_t platform;
|
||||
|
||||
// Index of the first block in the block entries table, or
|
||||
// -1 for an unused entry, -2 for a deleted entry.
|
||||
uint32_t block;
|
||||
};
|
||||
|
||||
struct MpqBlockEntry {
|
||||
static constexpr uint32_t FlagExists = 0x80000000;
|
||||
static constexpr uint32_t CompressPkZip = 0x00000100;
|
||||
|
||||
// Offset to the start of this block.
|
||||
uint32_t offset;
|
||||
|
||||
// Size in the MPQ.
|
||||
uint32_t packedSize;
|
||||
|
||||
// Uncompressed size.
|
||||
uint32_t unpackedSize;
|
||||
|
||||
// Flags indicating compression type, encryption, etc.
|
||||
uint32_t flags;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
using MpqFileHash = std::array<std::uint32_t, 3>;
|
||||
|
||||
#if !defined(UNPACKED_MPQS) || !defined(UNPACKED_SAVES)
|
||||
MpqFileHash CalculateMpqFileHash(std::string_view filename);
|
||||
#endif
|
||||
|
||||
} // namespace devilution
|
||||
} // namespace devilution
|
||||
+106
-110
@@ -1,149 +1,145 @@
|
||||
#include "mpq/mpq_reader.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
|
||||
#include <libmpq/mpq.h>
|
||||
#include <mpqfs/mpqfs.h>
|
||||
|
||||
#include "utils/file_util.h"
|
||||
|
||||
namespace devilution {
|
||||
|
||||
std::optional<MpqArchive> MpqArchive::Open(const char *path, int32_t &error)
|
||||
// Helper: NUL-terminate a string_view into a stack buffer.
|
||||
// Returns false if the name doesn't fit.
|
||||
static bool CopyToPathBuf(std::string_view sv, char *buf, size_t bufSize)
|
||||
{
|
||||
mpq_archive_s *archive;
|
||||
error = libmpq__archive_open(&archive, path, -1);
|
||||
if (error != 0) {
|
||||
if (error == LIBMPQ_ERROR_EXIST)
|
||||
error = 0;
|
||||
return std::nullopt;
|
||||
}
|
||||
return MpqArchive { std::string(path), archive };
|
||||
if (sv.size() >= bufSize) return false;
|
||||
std::memcpy(buf, sv.data(), sv.size());
|
||||
buf[sv.size()] = '\0';
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<MpqArchive> MpqArchive::Clone(int32_t &error)
|
||||
MpqArchive::MpqArchive(std::string path, mpqfs_archive_t *archive)
|
||||
: path_(std::move(path))
|
||||
, archive_(archive)
|
||||
{
|
||||
mpq_archive_s *copy;
|
||||
error = libmpq__archive_dup(archive_, path_.c_str(), ©);
|
||||
if (error != 0)
|
||||
return std::nullopt;
|
||||
return MpqArchive { path_, copy };
|
||||
}
|
||||
|
||||
const char *MpqArchive::ErrorMessage(int32_t errorCode)
|
||||
MpqArchive::MpqArchive(MpqArchive &&other) noexcept
|
||||
: path_(std::move(other.path_))
|
||||
, archive_(other.archive_)
|
||||
{
|
||||
return libmpq__strerror(errorCode);
|
||||
other.archive_ = nullptr;
|
||||
}
|
||||
|
||||
MpqArchive &MpqArchive::operator=(MpqArchive &&other) noexcept
|
||||
{
|
||||
path_ = std::move(other.path_);
|
||||
if (archive_ != nullptr)
|
||||
libmpq__archive_close(archive_);
|
||||
archive_ = other.archive_;
|
||||
other.archive_ = nullptr;
|
||||
tmp_buf_ = std::move(other.tmp_buf_);
|
||||
if (this != &other) {
|
||||
mpqfs_close(archive_);
|
||||
path_ = std::move(other.path_);
|
||||
archive_ = other.archive_;
|
||||
other.archive_ = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
MpqArchive::~MpqArchive()
|
||||
{
|
||||
if (archive_ != nullptr)
|
||||
libmpq__archive_close(archive_);
|
||||
mpqfs_close(archive_);
|
||||
}
|
||||
|
||||
bool MpqArchive::GetFileNumber(MpqFileHash fileHash, uint32_t &fileNumber)
|
||||
std::optional<MpqArchive> MpqArchive::Open(const char *path, int32_t &error)
|
||||
{
|
||||
return libmpq__file_number_from_hash(archive_, fileHash[0], fileHash[1], fileHash[2], &fileNumber) == 0;
|
||||
}
|
||||
|
||||
std::unique_ptr<std::byte[]> MpqArchive::ReadFile(std::string_view filename, std::size_t &fileSize, int32_t &error)
|
||||
{
|
||||
std::unique_ptr<std::byte[]> result;
|
||||
std::uint32_t fileNumber;
|
||||
error = libmpq__file_number_s(archive_, filename.data(), filename.size(), &fileNumber);
|
||||
if (error != 0)
|
||||
return result;
|
||||
|
||||
libmpq__off_t unpackedSize;
|
||||
error = libmpq__file_size_unpacked(archive_, fileNumber, &unpackedSize);
|
||||
if (error != 0)
|
||||
return result;
|
||||
|
||||
error = OpenBlockOffsetTable(fileNumber, filename);
|
||||
if (error != 0)
|
||||
return result;
|
||||
|
||||
result = std::make_unique<std::byte[]>(static_cast<size_t>(unpackedSize));
|
||||
|
||||
const std::size_t blockSize = GetBlockSize(fileNumber, 0, error);
|
||||
if (error != 0)
|
||||
return result;
|
||||
|
||||
std::vector<std::uint8_t> &tmp = GetTemporaryBuffer(blockSize);
|
||||
if (error != 0)
|
||||
return result;
|
||||
|
||||
error = libmpq__file_read_with_filename_and_temporary_buffer_s(
|
||||
archive_, fileNumber, filename.data(), filename.size(), reinterpret_cast<std::uint8_t *>(result.get()), unpackedSize,
|
||||
tmp.data(), static_cast<libmpq__off_t>(blockSize), nullptr);
|
||||
if (error != 0) {
|
||||
result = nullptr;
|
||||
CloseBlockOffsetTable(fileNumber);
|
||||
return result;
|
||||
if (!FileExists(path)) {
|
||||
error = 0;
|
||||
return std::nullopt;
|
||||
}
|
||||
CloseBlockOffsetTable(fileNumber);
|
||||
|
||||
fileSize = static_cast<size_t>(unpackedSize);
|
||||
return result;
|
||||
mpqfs_archive_t *handle = mpqfs_open(path);
|
||||
if (!handle) {
|
||||
error = -1;
|
||||
return std::nullopt;
|
||||
}
|
||||
error = 0;
|
||||
return MpqArchive(path, handle);
|
||||
}
|
||||
|
||||
int32_t MpqArchive::ReadBlock(uint32_t fileNumber, uint32_t blockNumber, uint8_t *out, size_t outSize)
|
||||
std::optional<MpqArchive> MpqArchive::Clone(int32_t &error)
|
||||
{
|
||||
std::vector<std::uint8_t> &tmpBuf = GetTemporaryBuffer(outSize);
|
||||
return libmpq__block_read_with_temporary_buffer(
|
||||
archive_, fileNumber, blockNumber, out, static_cast<libmpq__off_t>(outSize),
|
||||
tmpBuf.data(), outSize,
|
||||
/*transferred=*/nullptr);
|
||||
mpqfs_archive_t *clone = mpqfs_clone(archive_);
|
||||
if (!clone) {
|
||||
error = -1;
|
||||
return std::nullopt;
|
||||
}
|
||||
error = 0;
|
||||
return MpqArchive(path_, clone);
|
||||
}
|
||||
|
||||
std::size_t MpqArchive::GetUnpackedFileSize(uint32_t fileNumber, int32_t &error)
|
||||
const char *MpqArchive::ErrorMessage()
|
||||
{
|
||||
libmpq__off_t unpackedSize;
|
||||
error = libmpq__file_size_unpacked(archive_, fileNumber, &unpackedSize);
|
||||
return static_cast<size_t>(unpackedSize);
|
||||
}
|
||||
|
||||
uint32_t MpqArchive::GetNumBlocks(uint32_t fileNumber, int32_t &error)
|
||||
{
|
||||
uint32_t numBlocks;
|
||||
error = libmpq__file_blocks(archive_, fileNumber, &numBlocks);
|
||||
return numBlocks;
|
||||
}
|
||||
|
||||
int32_t MpqArchive::OpenBlockOffsetTable(uint32_t fileNumber, std::string_view filename)
|
||||
{
|
||||
return libmpq__block_open_offset_with_filename_s(archive_, fileNumber, filename.data(), filename.size());
|
||||
}
|
||||
|
||||
int32_t MpqArchive::CloseBlockOffsetTable(uint32_t fileNumber)
|
||||
{
|
||||
return libmpq__block_close_offset(archive_, fileNumber);
|
||||
}
|
||||
|
||||
// Requires the block offset table to be open
|
||||
std::size_t MpqArchive::GetBlockSize(uint32_t fileNumber, uint32_t blockNumber, int32_t &error)
|
||||
{
|
||||
libmpq__off_t blockSize;
|
||||
error = libmpq__block_size_unpacked(archive_, fileNumber, blockNumber, &blockSize);
|
||||
return static_cast<size_t>(blockSize);
|
||||
const char *msg = mpqfs_last_error();
|
||||
return msg ? msg : "Unknown error";
|
||||
}
|
||||
|
||||
bool MpqArchive::HasFile(std::string_view filename) const
|
||||
{
|
||||
std::uint32_t fileNumber;
|
||||
const int32_t error = libmpq__file_number_s(archive_, filename.data(), filename.size(), &fileNumber);
|
||||
return error == 0;
|
||||
char buf[256];
|
||||
if (!CopyToPathBuf(filename, buf, sizeof(buf)))
|
||||
return false;
|
||||
return mpqfs_has_file(archive_, buf);
|
||||
}
|
||||
|
||||
size_t MpqArchive::GetFileSize(std::string_view filename) const
|
||||
{
|
||||
char buf[256];
|
||||
if (!CopyToPathBuf(filename, buf, sizeof(buf)))
|
||||
return 0;
|
||||
return mpqfs_file_size(archive_, buf);
|
||||
}
|
||||
|
||||
uint32_t MpqArchive::FindHash(std::string_view filename) const
|
||||
{
|
||||
char buf[256];
|
||||
if (!CopyToPathBuf(filename, buf, sizeof(buf)))
|
||||
return UINT32_MAX;
|
||||
return mpqfs_find_hash(archive_, buf);
|
||||
}
|
||||
|
||||
bool MpqArchive::HasFileHash(uint32_t hash) const
|
||||
{
|
||||
return mpqfs_has_file_hash(archive_, hash);
|
||||
}
|
||||
|
||||
size_t MpqArchive::GetFileSizeFromHash(uint32_t hash) const
|
||||
{
|
||||
return mpqfs_file_size_from_hash(archive_, hash);
|
||||
}
|
||||
|
||||
std::unique_ptr<std::byte[]> MpqArchive::ReadFile(
|
||||
std::string_view filename, std::size_t &fileSize, int32_t &error)
|
||||
{
|
||||
char buf[256];
|
||||
if (!CopyToPathBuf(filename, buf, sizeof(buf))) {
|
||||
error = -1;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const size_t size = mpqfs_file_size(archive_, buf);
|
||||
if (size == 0) {
|
||||
error = -1;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto result = std::make_unique<std::byte[]>(size);
|
||||
const size_t read = mpqfs_read_file_into(archive_, buf,
|
||||
result.get(), size);
|
||||
if (read == 0) {
|
||||
error = -1;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
error = 0;
|
||||
fileSize = read;
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace devilution
|
||||
|
||||
+22
-52
@@ -6,76 +6,46 @@
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "mpq/mpq_common.hpp"
|
||||
|
||||
// Forward-declare so that we can avoid exposing libmpq.
|
||||
struct mpq_archive;
|
||||
using mpq_archive_s = struct mpq_archive;
|
||||
// Forward-declare so that we can avoid exposing mpqfs.h to all consumers.
|
||||
struct mpqfs_archive;
|
||||
typedef struct mpqfs_archive mpqfs_archive_t;
|
||||
|
||||
namespace devilution {
|
||||
|
||||
class MpqArchive {
|
||||
public:
|
||||
// If the file does not exist, returns nullopt without an error.
|
||||
static std::optional<MpqArchive> Open(const char *path, int32_t &error);
|
||||
|
||||
std::optional<MpqArchive> Clone(int32_t &error);
|
||||
static const char *ErrorMessage();
|
||||
|
||||
static const char *ErrorMessage(int32_t errorCode);
|
||||
|
||||
MpqArchive(MpqArchive &&other) noexcept
|
||||
: path_(std::move(other.path_))
|
||||
, archive_(other.archive_)
|
||||
, tmp_buf_(std::move(other.tmp_buf_))
|
||||
{
|
||||
other.archive_ = nullptr;
|
||||
}
|
||||
|
||||
MpqArchive(MpqArchive &&other) noexcept;
|
||||
MpqArchive &operator=(MpqArchive &&other) noexcept;
|
||||
|
||||
~MpqArchive();
|
||||
|
||||
// Returns false if the file does not exit.
|
||||
bool GetFileNumber(MpqFileHash fileHash, uint32_t &fileNumber);
|
||||
|
||||
std::unique_ptr<std::byte[]> ReadFile(std::string_view filename, std::size_t &fileSize, int32_t &error);
|
||||
|
||||
// Returns error code.
|
||||
int32_t ReadBlock(uint32_t fileNumber, uint32_t blockNumber, uint8_t *out, size_t outSize);
|
||||
|
||||
std::size_t GetUnpackedFileSize(uint32_t fileNumber, int32_t &error);
|
||||
|
||||
uint32_t GetNumBlocks(uint32_t fileNumber, int32_t &error);
|
||||
|
||||
int32_t OpenBlockOffsetTable(uint32_t fileNumber, std::string_view filename);
|
||||
|
||||
int32_t CloseBlockOffsetTable(uint32_t fileNumber);
|
||||
|
||||
// Requires the block offset table to be open
|
||||
std::size_t GetBlockSize(uint32_t fileNumber, uint32_t blockNumber, int32_t &error);
|
||||
MpqArchive(const MpqArchive &) = delete;
|
||||
MpqArchive &operator=(const MpqArchive &) = delete;
|
||||
|
||||
bool HasFile(std::string_view filename) const;
|
||||
size_t GetFileSize(std::string_view filename) const;
|
||||
|
||||
// Hash-based lookup: resolve once, reuse the index.
|
||||
uint32_t FindHash(std::string_view filename) const;
|
||||
bool HasFileHash(uint32_t hash) const;
|
||||
size_t GetFileSizeFromHash(uint32_t hash) const;
|
||||
|
||||
std::unique_ptr<std::byte[]> ReadFile(
|
||||
std::string_view filename,
|
||||
std::size_t &fileSize,
|
||||
int32_t &error);
|
||||
|
||||
mpqfs_archive_t *handle() const { return archive_; }
|
||||
|
||||
private:
|
||||
MpqArchive(std::string path, mpq_archive_s *archive)
|
||||
: path_(std::move(path))
|
||||
, archive_(archive)
|
||||
{
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> &GetTemporaryBuffer(std::size_t size)
|
||||
{
|
||||
if (tmp_buf_.size() < size)
|
||||
tmp_buf_.resize(size);
|
||||
return tmp_buf_;
|
||||
}
|
||||
MpqArchive(std::string path, mpqfs_archive_t *archive);
|
||||
|
||||
std::string path_;
|
||||
mpq_archive_s *archive_;
|
||||
std::vector<std::uint8_t> tmp_buf_;
|
||||
mpqfs_archive_t *archive_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace devilution
|
||||
|
||||
+232
-225
@@ -1,16 +1,18 @@
|
||||
#include "mpq/mpq_sdl_rwops.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <mpqfs/mpqfs.h>
|
||||
|
||||
#ifdef USE_SDL3
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3/SDL_iostream.h>
|
||||
#else
|
||||
#include <SDL.h>
|
||||
|
||||
#ifndef USE_SDL1
|
||||
#include <SDL_rwops.h>
|
||||
#endif
|
||||
#include "utils/sdl_compat.h"
|
||||
#endif
|
||||
|
||||
@@ -18,35 +20,156 @@ namespace devilution {
|
||||
|
||||
namespace {
|
||||
|
||||
struct Data {
|
||||
// File information:
|
||||
std::optional<MpqArchive> ownedArchive;
|
||||
MpqArchive *mpqArchive;
|
||||
uint32_t fileNumber;
|
||||
size_t blockSize;
|
||||
size_t lastBlockSize;
|
||||
uint32_t numBlocks;
|
||||
size_t size;
|
||||
constexpr size_t MaxMpqPathSize = 256;
|
||||
|
||||
// State:
|
||||
size_t position;
|
||||
bool blockRead;
|
||||
std::unique_ptr<uint8_t[]> blockData;
|
||||
/* -----------------------------------------------------------------------
|
||||
* Context structure shared by both SDL2 and SDL3 implementations.
|
||||
*
|
||||
* Wraps an mpqfs_stream_t (sector-based, on-demand decompression) and,
|
||||
* for the threadsafe variant, an independently cloned archive so that
|
||||
* reads don't race with the main thread's archive FILE*.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
struct MpqStreamCtx {
|
||||
mpqfs_stream_t *stream; /* Sector-based stream (owned) */
|
||||
mpqfs_archive_t *ownedClone; /* Non-null if we cloned for threadsafe */
|
||||
};
|
||||
|
||||
#ifdef USE_SDL3
|
||||
Data *GetData(void *userdata) { return reinterpret_cast<Data *>(userdata); }
|
||||
#else
|
||||
Data *GetData(struct SDL_RWops *context)
|
||||
static void DestroyCtx(MpqStreamCtx *ctx)
|
||||
{
|
||||
return reinterpret_cast<Data *>(context->hidden.unknown.data1);
|
||||
if (ctx == nullptr)
|
||||
return;
|
||||
mpqfs_stream_close(ctx->stream);
|
||||
if (ctx->ownedClone != nullptr)
|
||||
mpqfs_close(ctx->ownedClone);
|
||||
delete ctx;
|
||||
}
|
||||
|
||||
void SetData(struct SDL_RWops *context, Data *data)
|
||||
/* -----------------------------------------------------------------------
|
||||
* Helper: create the MpqStreamCtx, optionally cloning the archive for
|
||||
* thread-safety. Tries hash-based open first, falls back to filename.
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
static MpqStreamCtx *CreateCtx(mpqfs_archive_t *archive,
|
||||
uint32_t hashIndex,
|
||||
const char *filename,
|
||||
bool threadsafe)
|
||||
{
|
||||
context->hidden.unknown.data1 = data;
|
||||
mpqfs_archive_t *target = archive;
|
||||
mpqfs_archive_t *clone = nullptr;
|
||||
|
||||
if (threadsafe) {
|
||||
clone = mpqfs_clone(archive);
|
||||
if (clone == nullptr)
|
||||
return nullptr;
|
||||
target = clone;
|
||||
}
|
||||
|
||||
mpqfs_stream_t *stream = nullptr;
|
||||
|
||||
/* Try the fast hash-based path first (avoids re-hashing). */
|
||||
if (hashIndex != UINT32_MAX)
|
||||
stream = mpqfs_stream_open_from_hash(target, hashIndex);
|
||||
|
||||
/* Fall back to filename-based open (needed for encrypted files and
|
||||
* when hashIndex is not available). */
|
||||
if (stream == nullptr)
|
||||
stream = mpqfs_stream_open(target, filename);
|
||||
|
||||
if (stream == nullptr) {
|
||||
if (clone != nullptr)
|
||||
mpqfs_close(clone);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto *ctx = new (std::nothrow) MpqStreamCtx { stream, clone };
|
||||
if (ctx == nullptr) {
|
||||
mpqfs_stream_close(stream);
|
||||
if (clone != nullptr)
|
||||
mpqfs_close(clone);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return ctx;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* =======================================================================
|
||||
* SDL3 implementation
|
||||
* ======================================================================= */
|
||||
|
||||
#ifdef USE_SDL3
|
||||
|
||||
static Sint64 SDLCALL MpqStream_Size(void *userdata)
|
||||
{
|
||||
auto *ctx = static_cast<MpqStreamCtx *>(userdata);
|
||||
return static_cast<Sint64>(mpqfs_stream_size(ctx->stream));
|
||||
}
|
||||
|
||||
static Sint64 SDLCALL MpqStream_Seek(void *userdata, Sint64 offset, SDL_IOWhence whence)
|
||||
{
|
||||
auto *ctx = static_cast<MpqStreamCtx *>(userdata);
|
||||
int w;
|
||||
switch (whence) {
|
||||
case SDL_IO_SEEK_SET: w = SEEK_SET; break;
|
||||
case SDL_IO_SEEK_CUR: w = SEEK_CUR; break;
|
||||
case SDL_IO_SEEK_END: w = SEEK_END; break;
|
||||
default:
|
||||
SDL_SetError("MpqStream_Seek: unknown whence");
|
||||
return -1;
|
||||
}
|
||||
int64_t pos = mpqfs_stream_seek(ctx->stream, offset, w);
|
||||
if (pos < 0) {
|
||||
SDL_SetError("MpqStream_Seek: seek failed");
|
||||
return -1;
|
||||
}
|
||||
return static_cast<Sint64>(pos);
|
||||
}
|
||||
|
||||
static size_t SDLCALL MpqStream_Read(void *userdata, void *ptr, size_t size, SDL_IOStatus *status)
|
||||
{
|
||||
auto *ctx = static_cast<MpqStreamCtx *>(userdata);
|
||||
size_t n = mpqfs_stream_read(ctx->stream, ptr, size);
|
||||
if (n == static_cast<size_t>(-1)) {
|
||||
if (status != nullptr)
|
||||
*status = SDL_IO_STATUS_ERROR;
|
||||
SDL_SetError("MpqStream_Read: read failed");
|
||||
return 0;
|
||||
}
|
||||
if (n == 0 && size > 0) {
|
||||
if (status != nullptr)
|
||||
*status = SDL_IO_STATUS_EOF;
|
||||
} else {
|
||||
if (status != nullptr)
|
||||
*status = SDL_IO_STATUS_READY;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
static size_t SDLCALL MpqStream_Write(void * /*userdata*/, const void * /*ptr*/,
|
||||
size_t /*size*/, SDL_IOStatus *status)
|
||||
{
|
||||
if (status != nullptr)
|
||||
*status = SDL_IO_STATUS_ERROR;
|
||||
SDL_SetError("MpqStream_Write: read-only stream");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool SDLCALL MpqStream_Close(void *userdata)
|
||||
{
|
||||
auto *ctx = static_cast<MpqStreamCtx *>(userdata);
|
||||
DestroyCtx(ctx);
|
||||
return true;
|
||||
}
|
||||
|
||||
#else /* SDL1 or SDL2 */
|
||||
|
||||
/* =======================================================================
|
||||
* SDL1 / SDL2 implementation
|
||||
*
|
||||
* SDL1 uses int for offsets and sizes; SDL2 uses Sint64 and size_t.
|
||||
* SDL1 has no ->size callback and no SDL_RWOPS_UNKNOWN.
|
||||
* SDL1's SDL_SetError returns void; SDL2's returns int.
|
||||
* ======================================================================= */
|
||||
|
||||
#ifndef USE_SDL1
|
||||
using OffsetType = Sint64;
|
||||
@@ -56,241 +179,125 @@ using OffsetType = int;
|
||||
using SizeType = int;
|
||||
#endif
|
||||
|
||||
extern "C" {
|
||||
|
||||
#ifndef USE_SDL1
|
||||
static Sint64 MpqFileRwSize(
|
||||
#ifdef USE_SDL3
|
||||
void *
|
||||
#else
|
||||
struct SDL_RWops *
|
||||
#endif
|
||||
context)
|
||||
static Sint64 SDLCALL MpqStream_Size(SDL_RWops *rw)
|
||||
{
|
||||
return static_cast<Sint64>(GetData(context)->size);
|
||||
auto *ctx = static_cast<MpqStreamCtx *>(rw->hidden.unknown.data1);
|
||||
return static_cast<Sint64>(mpqfs_stream_size(ctx->stream));
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SDL3
|
||||
static Sint64 MpqFileRwSeek(void *context, Sint64 offset, SDL_IOWhence whence)
|
||||
#else
|
||||
static OffsetType MpqFileRwSeek(struct SDL_RWops *context, OffsetType offset, int whence)
|
||||
#endif
|
||||
static OffsetType SDLCALL MpqStream_Seek(SDL_RWops *rw, OffsetType offset, int whence)
|
||||
{
|
||||
Data &data = *GetData(context);
|
||||
OffsetType newPosition;
|
||||
auto *ctx = static_cast<MpqStreamCtx *>(rw->hidden.unknown.data1);
|
||||
|
||||
int w;
|
||||
switch (whence) {
|
||||
case SDL_IO_SEEK_SET:
|
||||
newPosition = offset;
|
||||
break;
|
||||
case SDL_IO_SEEK_CUR:
|
||||
newPosition = static_cast<OffsetType>(data.position + offset);
|
||||
break;
|
||||
case SDL_IO_SEEK_END:
|
||||
newPosition = static_cast<OffsetType>(data.size + offset);
|
||||
break;
|
||||
case SDL_IO_SEEK_SET: w = SEEK_SET; break;
|
||||
case SDL_IO_SEEK_CUR: w = SEEK_CUR; break;
|
||||
case SDL_IO_SEEK_END: w = SEEK_END; break;
|
||||
default:
|
||||
SDL_SetError("MpqStream_Seek: unknown whence");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (newPosition == static_cast<OffsetType>(data.position))
|
||||
return newPosition;
|
||||
|
||||
if (newPosition > static_cast<OffsetType>(data.size)) {
|
||||
SDL_SetError("MpqFileRwSeek beyond EOF (%d > %u)", static_cast<int>(newPosition), static_cast<unsigned>(data.size));
|
||||
int64_t pos = mpqfs_stream_seek(ctx->stream, offset, w);
|
||||
if (pos < 0) {
|
||||
SDL_SetError("MpqStream_Seek: seek failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (newPosition < 0) {
|
||||
SDL_SetError("MpqFileRwSeek beyond BOF (%d < 0)", static_cast<int>(newPosition));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (data.position / data.blockSize != static_cast<size_t>(newPosition) / data.blockSize)
|
||||
data.blockRead = false;
|
||||
|
||||
data.position = static_cast<size_t>(newPosition);
|
||||
|
||||
return newPosition;
|
||||
return static_cast<OffsetType>(pos);
|
||||
}
|
||||
|
||||
#ifdef USE_SDL3
|
||||
static SizeType MpqFileRwRead(void *context, void *ptr, size_t size, SDL_IOStatus *status)
|
||||
#else
|
||||
static SizeType MpqFileRwRead(struct SDL_RWops *context, void *ptr, SizeType size, SizeType maxnum)
|
||||
#endif
|
||||
static SizeType SDLCALL MpqStream_Read(SDL_RWops *rw, void *ptr,
|
||||
SizeType size, SizeType maxnum)
|
||||
{
|
||||
#ifdef USE_SDL3
|
||||
const size_t maxnum = 1;
|
||||
#endif
|
||||
Data &data = *GetData(context);
|
||||
const size_t totalSize = size * maxnum;
|
||||
size_t remainingSize = totalSize;
|
||||
auto *ctx = static_cast<MpqStreamCtx *>(rw->hidden.unknown.data1);
|
||||
|
||||
auto *out = static_cast<uint8_t *>(ptr);
|
||||
size_t totalBytes = static_cast<size_t>(size) * static_cast<size_t>(maxnum);
|
||||
size_t n = mpqfs_stream_read(ctx->stream, ptr, totalBytes);
|
||||
if (n == static_cast<size_t>(-1))
|
||||
return 0;
|
||||
|
||||
if (data.blockData == nullptr) {
|
||||
data.blockData = std::unique_ptr<uint8_t[]> { new uint8_t[data.blockSize] };
|
||||
}
|
||||
|
||||
auto blockNumber = static_cast<uint32_t>(data.position / data.blockSize);
|
||||
while (remainingSize > 0) {
|
||||
if (data.position == data.size) {
|
||||
#ifdef USE_SDL3
|
||||
*status = SDL_IO_STATUS_EOF;
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
const size_t currentBlockSize = blockNumber + 1 == data.numBlocks ? data.lastBlockSize : data.blockSize;
|
||||
|
||||
if (!data.blockRead) {
|
||||
const int32_t error = data.mpqArchive->ReadBlock(data.fileNumber, blockNumber, data.blockData.get(), currentBlockSize);
|
||||
if (error != 0) {
|
||||
SDL_SetError("MpqFileRwRead ReadBlock: %s", MpqArchive::ErrorMessage(error));
|
||||
return 0;
|
||||
}
|
||||
data.blockRead = true;
|
||||
}
|
||||
|
||||
const size_t blockPosition = data.position - (blockNumber * data.blockSize);
|
||||
const size_t remainingBlockSize = currentBlockSize - blockPosition;
|
||||
|
||||
if (remainingSize < remainingBlockSize) {
|
||||
std::memcpy(out, data.blockData.get() + blockPosition, remainingSize);
|
||||
data.position += remainingSize;
|
||||
#ifdef USE_SDL3
|
||||
return size;
|
||||
#else
|
||||
return maxnum;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::memcpy(out, data.blockData.get() + blockPosition, remainingBlockSize);
|
||||
out += remainingBlockSize;
|
||||
data.position += remainingBlockSize;
|
||||
remainingSize -= remainingBlockSize;
|
||||
++blockNumber;
|
||||
data.blockRead = false;
|
||||
}
|
||||
|
||||
#ifdef USE_SDL3
|
||||
return static_cast<SizeType>(totalSize - remainingSize);
|
||||
#else
|
||||
return static_cast<SizeType>((totalSize - remainingSize) / size);
|
||||
#endif
|
||||
/* Return number of whole objects read. */
|
||||
return (size > 0) ? static_cast<SizeType>(n / static_cast<size_t>(size)) : 0;
|
||||
}
|
||||
|
||||
#ifdef USE_SDL3
|
||||
static bool MpqFileRwClose(void *context)
|
||||
#else
|
||||
static int MpqFileRwClose(struct SDL_RWops *context)
|
||||
#endif
|
||||
static SizeType SDLCALL MpqStream_Write(SDL_RWops * /*rw*/, const void * /*ptr*/,
|
||||
SizeType /*size*/, SizeType /*num*/)
|
||||
{
|
||||
Data *data = GetData(context);
|
||||
data->mpqArchive->CloseBlockOffsetTable(data->fileNumber);
|
||||
delete data;
|
||||
#ifdef USE_SDL3
|
||||
return true;
|
||||
#else
|
||||
delete context;
|
||||
SDL_SetError("MpqStream_Write: read-only stream");
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
static int SDLCALL MpqStream_Close(SDL_RWops *rw)
|
||||
{
|
||||
if (rw != nullptr) {
|
||||
auto *ctx = static_cast<MpqStreamCtx *>(rw->hidden.unknown.data1);
|
||||
DestroyCtx(ctx);
|
||||
SDL_FreeRW(rw);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* !USE_SDL3 */
|
||||
|
||||
} // namespace
|
||||
|
||||
SDL_IOStream *SDL_RWops_FromMpqFile(MpqArchive &mpqArchive, uint32_t fileNumber, std::string_view filename, bool threadsafe)
|
||||
SdlRwopsType *SDL_RWops_FromMpqFile(MpqArchive &archive,
|
||||
uint32_t hashIndex,
|
||||
std::string_view filename,
|
||||
bool threadsafe)
|
||||
{
|
||||
/* NUL-terminate the filename for the C API. */
|
||||
char pathBuf[MaxMpqPathSize];
|
||||
if (filename.size() >= sizeof(pathBuf))
|
||||
return nullptr;
|
||||
std::memcpy(pathBuf, filename.data(), filename.size());
|
||||
pathBuf[filename.size()] = '\0';
|
||||
|
||||
MpqStreamCtx *ctx = CreateCtx(archive.handle(), hashIndex, pathBuf, threadsafe);
|
||||
if (ctx == nullptr)
|
||||
return nullptr;
|
||||
|
||||
#ifdef USE_SDL3
|
||||
SDL_IOStreamInterface interface;
|
||||
SDL_INIT_INTERFACE(&interface);
|
||||
SDL_IOStreamInterface *result = &interface;
|
||||
SDL_IOStreamInterface iface = {};
|
||||
iface.version = sizeof(iface);
|
||||
iface.size = MpqStream_Size;
|
||||
iface.seek = MpqStream_Seek;
|
||||
iface.read = MpqStream_Read;
|
||||
iface.write = MpqStream_Write;
|
||||
iface.close = MpqStream_Close;
|
||||
|
||||
SdlRwopsType *rwops = SDL_OpenIO(&iface, ctx);
|
||||
if (rwops == nullptr) {
|
||||
DestroyCtx(ctx);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return rwops;
|
||||
#else
|
||||
auto result = std::make_unique<SDL_RWops>();
|
||||
std::memset(result.get(), 0, sizeof(*result));
|
||||
#endif
|
||||
SDL_RWops *rwops = SDL_AllocRW();
|
||||
if (rwops == nullptr) {
|
||||
DestroyCtx(ctx);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#ifndef USE_SDL1
|
||||
result->size = &MpqFileRwSize;
|
||||
#ifndef USE_SDL3
|
||||
result->type = SDL_RWOPS_UNKNOWN;
|
||||
#endif
|
||||
rwops->type = SDL_RWOPS_UNKNOWN;
|
||||
rwops->size = MpqStream_Size;
|
||||
#else
|
||||
result->type = 0;
|
||||
rwops->type = 0;
|
||||
#endif
|
||||
rwops->seek = MpqStream_Seek;
|
||||
rwops->read = MpqStream_Read;
|
||||
rwops->write = MpqStream_Write;
|
||||
rwops->close = MpqStream_Close;
|
||||
rwops->hidden.unknown.data1 = ctx;
|
||||
|
||||
result->seek = &MpqFileRwSeek;
|
||||
result->read = &MpqFileRwRead;
|
||||
result->write = nullptr;
|
||||
result->close = &MpqFileRwClose;
|
||||
#ifdef USE_SDL3
|
||||
result->flush = nullptr;
|
||||
#endif
|
||||
|
||||
auto data = std::make_unique<Data>();
|
||||
int32_t error = 0;
|
||||
|
||||
if (threadsafe) {
|
||||
data->ownedArchive = mpqArchive.Clone(error);
|
||||
if (error != 0) {
|
||||
SDL_SetError("MpqFileRwRead Clone: %s", MpqArchive::ErrorMessage(error));
|
||||
return nullptr;
|
||||
}
|
||||
data->mpqArchive = &*data->ownedArchive;
|
||||
} else {
|
||||
data->mpqArchive = &mpqArchive;
|
||||
}
|
||||
data->fileNumber = fileNumber;
|
||||
MpqArchive &archive = *data->mpqArchive;
|
||||
|
||||
error = archive.OpenBlockOffsetTable(fileNumber, filename);
|
||||
if (error != 0) {
|
||||
SDL_SetError("MpqFileRwRead OpenBlockOffsetTable: %s", MpqArchive::ErrorMessage(error));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
data->size = archive.GetUnpackedFileSize(fileNumber, error);
|
||||
if (error != 0) {
|
||||
SDL_SetError("MpqFileRwRead GetUnpackedFileSize: %s", MpqArchive::ErrorMessage(error));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const std::uint32_t numBlocks = archive.GetNumBlocks(fileNumber, error);
|
||||
if (error != 0) {
|
||||
SDL_SetError("MpqFileRwRead GetNumBlocks: %s", MpqArchive::ErrorMessage(error));
|
||||
return nullptr;
|
||||
}
|
||||
data->numBlocks = numBlocks;
|
||||
|
||||
const size_t blockSize = archive.GetBlockSize(fileNumber, 0, error);
|
||||
if (error != 0) {
|
||||
SDL_SetError("MpqFileRwRead GetBlockSize: %s", MpqArchive::ErrorMessage(error));
|
||||
return nullptr;
|
||||
}
|
||||
data->blockSize = blockSize;
|
||||
|
||||
if (numBlocks > 1) {
|
||||
data->lastBlockSize = archive.GetBlockSize(fileNumber, numBlocks - 1, error);
|
||||
if (error != 0) {
|
||||
SDL_SetError("MpqFileRwRead GetBlockSize: %s", MpqArchive::ErrorMessage(error));
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
data->lastBlockSize = blockSize;
|
||||
}
|
||||
|
||||
data->position = 0;
|
||||
data->blockRead = false;
|
||||
|
||||
#ifdef USE_SDL3
|
||||
return SDL_OpenIO(&interface, data.release());
|
||||
#else
|
||||
SetData(result.get(), data.release());
|
||||
return result.release();
|
||||
return rwops;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace devilution
|
||||
} // namespace devilution
|
||||
@@ -1,20 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
|
||||
#ifdef USE_SDL3
|
||||
#include <SDL3/SDL_iostream.h>
|
||||
#else
|
||||
#include <SDL.h>
|
||||
|
||||
#include "utils/sdl_compat.h"
|
||||
#endif
|
||||
|
||||
#include "mpq/mpq_reader.hpp"
|
||||
|
||||
// Forward-declare the SDL type for the active SDL version.
|
||||
#ifdef USE_SDL3
|
||||
struct SDL_IOStream;
|
||||
using SdlRwopsType = SDL_IOStream;
|
||||
#else
|
||||
struct SDL_RWops;
|
||||
using SdlRwopsType = SDL_RWops;
|
||||
#endif
|
||||
|
||||
namespace devilution {
|
||||
|
||||
SDL_IOStream *SDL_RWops_FromMpqFile(MpqArchive &mpqArchive, uint32_t fileNumber, std::string_view filename, bool threadsafe);
|
||||
SdlRwopsType *SDL_RWops_FromMpqFile(MpqArchive &archive,
|
||||
uint32_t hashIndex,
|
||||
std::string_view filename,
|
||||
bool threadsafe);
|
||||
|
||||
} // namespace devilution
|
||||
|
||||
+99
-477
@@ -1,508 +1,123 @@
|
||||
#include "mpq/mpq_writer.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include <libmpq/mpq.h>
|
||||
#include <mpqfs/mpqfs.h>
|
||||
|
||||
#include "appfat.h"
|
||||
#include "encrypt.h"
|
||||
#include "utils/endian_swap.hpp"
|
||||
#include "mpq/mpq_common.hpp"
|
||||
#include "utils/file_util.h"
|
||||
#include "utils/language.h"
|
||||
#include "utils/log.hpp"
|
||||
#include "utils/str_cat.hpp"
|
||||
|
||||
namespace devilution {
|
||||
|
||||
namespace {
|
||||
|
||||
// Validates that a Type is of a particular size and that its alignment is <= the size of the type.
|
||||
// Done with templates so that error messages include actual size.
|
||||
template <size_t A, size_t B>
|
||||
struct AssertEq : std::true_type {
|
||||
static_assert(A == B, "A == B not satisfied");
|
||||
};
|
||||
template <size_t A, size_t B>
|
||||
struct AssertLte : std::true_type {
|
||||
static_assert(A <= B, "A <= B not satisfied");
|
||||
};
|
||||
template <typename T, size_t S>
|
||||
struct CheckSize : AssertEq<sizeof(T), S>, AssertLte<alignof(T), sizeof(T)> {
|
||||
};
|
||||
|
||||
// Check sizes and alignments of the structs that we decrypt and encrypt.
|
||||
// The decryption algorithm treats them as a stream of 32-bit uints, so the
|
||||
// sizes must be exact as there cannot be any padding.
|
||||
static_assert(CheckSize<MpqHashEntry, static_cast<size_t>(4 * 4)>::value, "sizeof(MpqHashEntry) == 4 * 4 && alignof(MpqHashEntry) <= 4 * 4 not satisfied");
|
||||
static_assert(CheckSize<MpqBlockEntry, static_cast<size_t>(4 * 4)>::value, "sizeof(MpqBlockEntry) == 4 * 4 && alignof(MpqBlockEntry) <= 4 * 4 not satisfied");
|
||||
|
||||
// We use fixed size block and hash entry tables.
|
||||
constexpr uint32_t HashEntriesCount = 2048;
|
||||
constexpr uint32_t BlockEntriesCount = 2048;
|
||||
constexpr uint32_t BlockEntrySize = HashEntriesCount * sizeof(MpqBlockEntry);
|
||||
constexpr uint32_t HashEntrySize = BlockEntriesCount * sizeof(MpqHashEntry);
|
||||
|
||||
// We store the block and the hash entry tables immediately after the header.
|
||||
// This is unlike most other MPQ archives, that store these at the end of the file.
|
||||
constexpr long MpqBlockEntryOffset = sizeof(MpqFileHeader);
|
||||
constexpr long MpqHashEntryOffset = MpqBlockEntryOffset + BlockEntrySize;
|
||||
|
||||
// Special return value for `GetHashIndex` and `GetHandle`.
|
||||
constexpr uint32_t HashEntryNotFound = -1;
|
||||
|
||||
// We use 4096-byte blocks, generally.
|
||||
constexpr uint16_t BlockSizeFactor = 3;
|
||||
constexpr uint32_t BlockSize = 512 << BlockSizeFactor; // 4096
|
||||
|
||||
// Sometimes we can end up with smaller blocks.
|
||||
constexpr uint32_t MinBlockSize = 1024;
|
||||
|
||||
void ByteSwapHdr(MpqFileHeader *hdr)
|
||||
{
|
||||
hdr->signature = Swap32LE(hdr->signature);
|
||||
hdr->headerSize = Swap32LE(hdr->headerSize);
|
||||
hdr->fileSize = Swap32LE(hdr->fileSize);
|
||||
hdr->version = Swap16LE(hdr->version);
|
||||
hdr->blockSizeFactor = Swap16LE(hdr->blockSizeFactor);
|
||||
hdr->hashEntriesOffset = Swap32LE(hdr->hashEntriesOffset);
|
||||
hdr->blockEntriesOffset = Swap32LE(hdr->blockEntriesOffset);
|
||||
hdr->hashEntriesCount = Swap32LE(hdr->hashEntriesCount);
|
||||
hdr->blockEntriesCount = Swap32LE(hdr->blockEntriesCount);
|
||||
}
|
||||
|
||||
bool IsAllocatedUnusedBlock(const MpqBlockEntry *block)
|
||||
{
|
||||
return block->offset != 0 && block->flags == 0 && block->unpackedSize == 0;
|
||||
}
|
||||
|
||||
bool IsUnallocatedBlock(const MpqBlockEntry *block)
|
||||
{
|
||||
return block->offset == 0 && block->packedSize == 0 && block->unpackedSize == 0 && block->flags == 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
MpqWriter::MpqWriter(const char *path)
|
||||
MpqWriter::MpqWriter(const char *path, bool carryForward)
|
||||
: path_(path)
|
||||
{
|
||||
const std::string dir = std::string(Dirname(path));
|
||||
if (!dir.empty()) {
|
||||
RecursivelyCreateDir(dir.c_str());
|
||||
}
|
||||
LogVerbose("Opening {}", path);
|
||||
bool isNewFile = false;
|
||||
std::string error;
|
||||
if (!FileExists(path)) {
|
||||
// FileExists() may return false in the case of an error
|
||||
// so we use "ab" instead of "wb" to avoid accidentally
|
||||
// truncating an existing file
|
||||
stream_.Open(path, "ab");
|
||||
|
||||
// However, we cannot actually use a file handle that was
|
||||
// opened in "ab" mode because we need to be able to seek
|
||||
// and write to the middle of the file
|
||||
stream_.Close();
|
||||
// If the file already exists and we need to preserve its contents,
|
||||
// rename it to a temp path so we can read from it after the writer
|
||||
// truncates the original path.
|
||||
std::string tmpPath;
|
||||
mpqfs_archive_t *oldArchive = nullptr;
|
||||
if (carryForward && FileExists(path)) {
|
||||
tmpPath = std::string(path) + ".tmp";
|
||||
// Remove any stale temp file, then rename old → tmp.
|
||||
::devilution::RemoveFile(tmpPath.c_str());
|
||||
::devilution::RenameFile(path, tmpPath.c_str());
|
||||
oldArchive = mpqfs_open(tmpPath.c_str());
|
||||
// If it fails to open (e.g. corrupt), we proceed without
|
||||
// carry-forward — the file will be recreated from scratch.
|
||||
}
|
||||
|
||||
std::uintmax_t fileSize;
|
||||
if (!GetFileSize(path, &fileSize)) {
|
||||
error = R"(GetFileSize failed: "{}")";
|
||||
LogError(error, path, std::strerror(errno));
|
||||
goto on_error;
|
||||
}
|
||||
size_ = static_cast<uint32_t>(fileSize);
|
||||
isNewFile = size_ == 0;
|
||||
LogVerbose("GetFileSize(\"{}\") = {}", path, size_);
|
||||
|
||||
if (!stream_.Open(path, "r+b")) {
|
||||
stream_.Close();
|
||||
error = "Failed to open file";
|
||||
goto on_error;
|
||||
writer_ = mpqfs_writer_create(path, MpqWriterHashTableSize);
|
||||
if (writer_ == nullptr) {
|
||||
LogError("Failed to create MPQ archive: {}", mpqfs_last_error());
|
||||
if (oldArchive != nullptr)
|
||||
mpqfs_close(oldArchive);
|
||||
if (!tmpPath.empty())
|
||||
::devilution::RemoveFile(tmpPath.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
name_ = path;
|
||||
|
||||
if (blockTable_ == nullptr || hashTable_ == nullptr) {
|
||||
MpqFileHeader fhdr;
|
||||
if (isNewFile) {
|
||||
InitDefaultMpqHeader(&fhdr);
|
||||
} else if (!ReadMPQHeader(&fhdr)) {
|
||||
error = "Failed to read MPQ header";
|
||||
goto on_error;
|
||||
if (oldArchive != nullptr) {
|
||||
if (!mpqfs_writer_carry_forward_all(writer_, oldArchive)) {
|
||||
LogError("Failed to carry forward files from existing archive: {}", mpqfs_last_error());
|
||||
}
|
||||
blockTable_ = std::make_unique<MpqBlockEntry[]>(BlockEntriesCount);
|
||||
std::memset(blockTable_.get(), 0, BlockEntriesCount * sizeof(MpqBlockEntry));
|
||||
if (fhdr.blockEntriesCount > 0) {
|
||||
if (!stream_.Read(reinterpret_cast<char *>(blockTable_.get()), static_cast<size_t>(fhdr.blockEntriesCount * sizeof(MpqBlockEntry)))) {
|
||||
error = "Failed to read block table";
|
||||
goto on_error;
|
||||
}
|
||||
libmpq__decrypt_block(reinterpret_cast<uint32_t *>(blockTable_.get()), fhdr.blockEntriesCount * sizeof(MpqBlockEntry), LIBMPQ_BLOCK_TABLE_HASH_KEY);
|
||||
}
|
||||
hashTable_ = std::make_unique<MpqHashEntry[]>(HashEntriesCount);
|
||||
|
||||
// We fill with 0xFF so that the `block` field defaults to -1 (a null block pointer).
|
||||
std::memset(hashTable_.get(), 0xFF, HashEntriesCount * sizeof(MpqHashEntry));
|
||||
|
||||
if (fhdr.hashEntriesCount > 0) {
|
||||
if (!stream_.Read(reinterpret_cast<char *>(hashTable_.get()), static_cast<size_t>(fhdr.hashEntriesCount * sizeof(MpqHashEntry)))) {
|
||||
error = "Failed to read hash entries";
|
||||
goto on_error;
|
||||
}
|
||||
libmpq__decrypt_block(reinterpret_cast<uint32_t *>(hashTable_.get()), fhdr.hashEntriesCount * sizeof(MpqHashEntry), LIBMPQ_HASH_TABLE_HASH_KEY);
|
||||
}
|
||||
|
||||
#ifndef CAN_SEEKP_BEYOND_EOF
|
||||
if (!stream_.Seekp(0, SEEK_SET))
|
||||
goto on_error;
|
||||
|
||||
// Memorize stream begin, we'll need it for calculations later.
|
||||
if (!stream_.Tellp(&streamBegin_))
|
||||
goto on_error;
|
||||
|
||||
// Write garbage header and tables because some platforms cannot `Seekp` beyond EOF.
|
||||
// The data is incorrect at this point, it will be overwritten on Close.
|
||||
if (isNewFile)
|
||||
WriteHeaderAndTables();
|
||||
#endif
|
||||
mpqfs_close(oldArchive);
|
||||
}
|
||||
return;
|
||||
on_error:
|
||||
app_fatal(StrCat(_("Failed to open archive for writing."), "\n", path, "\n", error));
|
||||
|
||||
if (!tmpPath.empty())
|
||||
::devilution::RemoveFile(tmpPath.c_str());
|
||||
}
|
||||
|
||||
MpqWriter::MpqWriter(MpqWriter &&other) noexcept
|
||||
: path_(std::move(other.path_))
|
||||
, writer_(other.writer_)
|
||||
{
|
||||
other.writer_ = nullptr;
|
||||
}
|
||||
|
||||
MpqWriter &MpqWriter::operator=(MpqWriter &&other) noexcept
|
||||
{
|
||||
if (this != &other) {
|
||||
if (writer_ != nullptr)
|
||||
mpqfs_writer_discard(writer_);
|
||||
path_ = std::move(other.path_);
|
||||
writer_ = other.writer_;
|
||||
other.writer_ = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
MpqWriter::~MpqWriter()
|
||||
{
|
||||
if (!stream_.IsOpen())
|
||||
if (writer_ == nullptr)
|
||||
return;
|
||||
LogVerbose("Closing {}", name_);
|
||||
|
||||
bool result = true;
|
||||
if (!(stream_.Seekp(0, SEEK_SET) && WriteHeaderAndTables()))
|
||||
result = false;
|
||||
stream_.Close();
|
||||
if (result && size_ != 0) {
|
||||
LogVerbose("ResizeFile(\"{}\", {})", name_, size_);
|
||||
result = ResizeFile(name_.c_str(), size_);
|
||||
}
|
||||
if (!result)
|
||||
LogVerbose("Closing failed {}", name_);
|
||||
}
|
||||
LogVerbose("Closing {}", path_);
|
||||
|
||||
uint32_t MpqWriter::FetchHandle(std::string_view filename) const
|
||||
{
|
||||
return GetHashIndex(CalculateMpqFileHash(filename));
|
||||
}
|
||||
|
||||
void MpqWriter::InitDefaultMpqHeader(MpqFileHeader *hdr)
|
||||
{
|
||||
std::memset(hdr, 0, sizeof(*hdr));
|
||||
hdr->signature = MpqFileHeader::DiabloSignature;
|
||||
hdr->headerSize = MpqFileHeader::DiabloSize;
|
||||
hdr->blockSizeFactor = BlockSizeFactor;
|
||||
hdr->version = 0;
|
||||
size_ = MpqHashEntryOffset + HashEntrySize;
|
||||
}
|
||||
|
||||
bool MpqWriter::IsValidMpqHeader(MpqFileHeader *hdr) const
|
||||
{
|
||||
return hdr->signature == MpqFileHeader::DiabloSignature
|
||||
&& hdr->headerSize == MpqFileHeader::DiabloSize
|
||||
&& hdr->version <= 0
|
||||
&& hdr->blockSizeFactor == BlockSizeFactor
|
||||
&& hdr->fileSize == size_
|
||||
&& hdr->hashEntriesOffset == MpqHashEntryOffset
|
||||
&& hdr->blockEntriesOffset == sizeof(MpqFileHeader)
|
||||
&& hdr->hashEntriesCount == HashEntriesCount
|
||||
&& hdr->blockEntriesCount == BlockEntriesCount;
|
||||
}
|
||||
|
||||
bool MpqWriter::ReadMPQHeader(MpqFileHeader *hdr)
|
||||
{
|
||||
const bool hasHdr = size_ >= sizeof(*hdr);
|
||||
if (hasHdr) {
|
||||
if (!stream_.Read(reinterpret_cast<char *>(hdr), sizeof(*hdr)))
|
||||
return false;
|
||||
ByteSwapHdr(hdr);
|
||||
}
|
||||
if (!hasHdr || !IsValidMpqHeader(hdr)) {
|
||||
InitDefaultMpqHeader(hdr);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
MpqBlockEntry *MpqWriter::NewBlock(uint32_t *blockIndex)
|
||||
{
|
||||
MpqBlockEntry *blockEntry = blockTable_.get();
|
||||
|
||||
for (unsigned i = 0; i < BlockEntriesCount; ++i, ++blockEntry) {
|
||||
if (!IsUnallocatedBlock(blockEntry))
|
||||
continue;
|
||||
|
||||
if (blockIndex != nullptr)
|
||||
*blockIndex = i;
|
||||
|
||||
return blockEntry;
|
||||
}
|
||||
|
||||
app_fatal("Out of free block entries");
|
||||
}
|
||||
|
||||
void MpqWriter::AllocBlock(uint32_t blockOffset, uint32_t blockSize)
|
||||
{
|
||||
MpqBlockEntry *block;
|
||||
bool expand;
|
||||
do {
|
||||
block = blockTable_.get();
|
||||
expand = false;
|
||||
for (unsigned i = BlockEntriesCount; i-- != 0; ++block) {
|
||||
// Expand to adjacent blocks.
|
||||
if (!IsAllocatedUnusedBlock(block))
|
||||
continue;
|
||||
if (block->offset + block->packedSize == blockOffset) {
|
||||
blockOffset = block->offset;
|
||||
blockSize += block->packedSize;
|
||||
memset(block, 0, sizeof(MpqBlockEntry));
|
||||
expand = true;
|
||||
break;
|
||||
}
|
||||
if (blockOffset + blockSize == block->offset) {
|
||||
blockSize += block->packedSize;
|
||||
memset(block, 0, sizeof(MpqBlockEntry));
|
||||
expand = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (expand);
|
||||
if (blockOffset + blockSize > size_) {
|
||||
// Expanded beyond EOF, this should never happen.
|
||||
app_fatal("MPQ free list error");
|
||||
}
|
||||
if (blockOffset + blockSize == size_) {
|
||||
size_ = blockOffset;
|
||||
} else {
|
||||
block = NewBlock();
|
||||
block->offset = blockOffset;
|
||||
block->packedSize = blockSize;
|
||||
block->unpackedSize = 0;
|
||||
block->flags = 0;
|
||||
if (!mpqfs_writer_close(writer_)) {
|
||||
LogError("Failed to close MPQ archive: {}", mpqfs_last_error());
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t MpqWriter::FindFreeBlock(uint32_t size)
|
||||
bool MpqWriter::HasFile(std::string_view name) const
|
||||
{
|
||||
uint32_t result;
|
||||
|
||||
MpqBlockEntry *block = blockTable_.get();
|
||||
for (unsigned i = 0; i < BlockEntriesCount; ++i, ++block) {
|
||||
// Find a block entry to use space from.
|
||||
if (!IsAllocatedUnusedBlock(block) || block->packedSize < size)
|
||||
continue;
|
||||
|
||||
result = block->offset;
|
||||
block->offset += size;
|
||||
block->packedSize -= size;
|
||||
|
||||
// Clear the block entry if we used its entire capacity.
|
||||
if (block->packedSize == 0)
|
||||
memset(block, 0, sizeof(*block));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
result = size_;
|
||||
size_ += size;
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t MpqWriter::GetHashIndex(MpqFileHash fileHash) const // NOLINT(bugprone-easily-swappable-parameters)
|
||||
{
|
||||
uint32_t i = HashEntriesCount;
|
||||
for (unsigned idx = fileHash[0] & 0x7FF; hashTable_[idx].block != MpqHashEntry::NullBlock; idx = (idx + 1) & 0x7FF) {
|
||||
if (i-- == 0)
|
||||
break;
|
||||
if (hashTable_[idx].hashA != fileHash[1])
|
||||
continue;
|
||||
if (hashTable_[idx].hashB != fileHash[2])
|
||||
continue;
|
||||
if (hashTable_[idx].block == MpqHashEntry::DeletedBlock)
|
||||
continue;
|
||||
|
||||
return idx;
|
||||
}
|
||||
|
||||
return HashEntryNotFound;
|
||||
}
|
||||
|
||||
bool MpqWriter::WriteHeaderAndTables()
|
||||
{
|
||||
return WriteHeader() && WriteBlockTable() && WriteHashTable();
|
||||
}
|
||||
|
||||
MpqBlockEntry *MpqWriter::AddFile(std::string_view filename, MpqBlockEntry *block, uint32_t blockIndex)
|
||||
{
|
||||
const MpqFileHash fileHash = CalculateMpqFileHash(filename);
|
||||
if (GetHashIndex(fileHash) != HashEntryNotFound)
|
||||
app_fatal(StrCat("Hash collision between \"", filename, "\" and existing file\n"));
|
||||
unsigned int hIdx = fileHash[0] & 0x7FF;
|
||||
|
||||
bool hasSpace = false;
|
||||
for (unsigned i = 0; i < HashEntriesCount; ++i) {
|
||||
if (hashTable_[hIdx].block == MpqHashEntry::NullBlock || hashTable_[hIdx].block == MpqHashEntry::DeletedBlock) {
|
||||
hasSpace = true;
|
||||
break;
|
||||
}
|
||||
hIdx = (hIdx + 1) & 0x7FF;
|
||||
}
|
||||
if (!hasSpace)
|
||||
app_fatal("Out of hash space");
|
||||
|
||||
if (block == nullptr)
|
||||
block = NewBlock(&blockIndex);
|
||||
|
||||
MpqHashEntry &entry = hashTable_[hIdx];
|
||||
entry.hashA = fileHash[1];
|
||||
entry.hashB = fileHash[2];
|
||||
entry.locale = 0;
|
||||
entry.platform = 0;
|
||||
entry.block = blockIndex;
|
||||
|
||||
return block;
|
||||
}
|
||||
|
||||
bool MpqWriter::WriteFileContents(const std::byte *fileData, uint32_t fileSize, MpqBlockEntry *block)
|
||||
{
|
||||
const uint32_t numSectors = (fileSize + (BlockSize - 1)) / BlockSize;
|
||||
const uint32_t offsetTableByteSize = sizeof(uint32_t) * (numSectors + 1);
|
||||
block->offset = FindFreeBlock(fileSize + offsetTableByteSize);
|
||||
// `packedSize` is reduced at the end of the function if it turns out to be smaller.
|
||||
block->packedSize = fileSize + offsetTableByteSize;
|
||||
block->unpackedSize = fileSize;
|
||||
block->flags = MpqBlockEntry::FlagExists | MpqBlockEntry::CompressPkZip;
|
||||
|
||||
// We populate the table of sector offsets while we write the data.
|
||||
// We can't pre-populate it because we don't know the compressed sector sizes yet.
|
||||
// First offset is the start of the first sector, last offset is the end of the last sector.
|
||||
const std::unique_ptr<uint32_t[]> offsetTable { new uint32_t[numSectors + 1] };
|
||||
|
||||
#ifdef CAN_SEEKP_BEYOND_EOF
|
||||
if (!stream_.Seekp(block->offset + offsetTableByteSize, SEEK_SET))
|
||||
return false;
|
||||
#else
|
||||
// Ensure we do not Seekp beyond EOF by filling the missing space.
|
||||
long stream_end;
|
||||
if (!stream_.Seekp(0, SEEK_END) || !stream_.Tellp(&stream_end))
|
||||
return false;
|
||||
const std::uintmax_t cur_size = stream_end - streamBegin_;
|
||||
if (cur_size < block->offset + offsetTableByteSize) {
|
||||
if (cur_size < block->offset) {
|
||||
std::unique_ptr<char[]> filler { new char[block->offset - cur_size] };
|
||||
if (!stream_.Write(filler.get(), block->offset - cur_size))
|
||||
return false;
|
||||
}
|
||||
if (!stream_.Write(reinterpret_cast<const char *>(offsetTable.get()), offsetTableByteSize))
|
||||
return false;
|
||||
} else {
|
||||
if (!stream_.Seekp(block->offset + offsetTableByteSize, SEEK_SET))
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
uint32_t destSize = offsetTableByteSize;
|
||||
std::byte mpqBuf[BlockSize];
|
||||
size_t curSector = 0;
|
||||
while (true) {
|
||||
uint32_t len = std::min<uint32_t>(fileSize, BlockSize);
|
||||
memcpy(mpqBuf, fileData, len);
|
||||
fileData += len;
|
||||
len = PkwareCompress(mpqBuf, len);
|
||||
if (!stream_.Write(reinterpret_cast<const char *>(&mpqBuf[0]), len))
|
||||
return false;
|
||||
offsetTable[curSector++] = Swap32LE(destSize);
|
||||
destSize += len; // compressed length
|
||||
if (fileSize <= BlockSize)
|
||||
break;
|
||||
|
||||
fileSize -= BlockSize;
|
||||
}
|
||||
|
||||
offsetTable[numSectors] = Swap32LE(destSize);
|
||||
if (!stream_.Seekp(block->offset, SEEK_SET))
|
||||
return false;
|
||||
if (!stream_.Write(reinterpret_cast<const char *>(offsetTable.get()), offsetTableByteSize))
|
||||
return false;
|
||||
if (!stream_.Seekp(destSize - offsetTableByteSize, SEEK_CUR))
|
||||
if (writer_ == nullptr)
|
||||
return false;
|
||||
|
||||
if (destSize < block->packedSize) {
|
||||
const uint32_t remainingBlockSize = block->packedSize - destSize;
|
||||
if (remainingBlockSize >= MinBlockSize) {
|
||||
// Allocate another block if we didn't use all of this one.
|
||||
block->packedSize = destSize;
|
||||
AllocBlock(block->packedSize + block->offset, remainingBlockSize);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
char buf[MaxMpqPathSize];
|
||||
if (name.size() >= sizeof(buf))
|
||||
return false;
|
||||
std::memcpy(buf, name.data(), name.size());
|
||||
buf[name.size()] = '\0';
|
||||
|
||||
bool MpqWriter::WriteHeader()
|
||||
{
|
||||
MpqFileHeader fhdr;
|
||||
|
||||
memset(&fhdr, 0, sizeof(fhdr));
|
||||
fhdr.signature = MpqFileHeader::DiabloSignature;
|
||||
fhdr.headerSize = MpqFileHeader::DiabloSize;
|
||||
fhdr.fileSize = size_;
|
||||
fhdr.version = 0;
|
||||
fhdr.blockSizeFactor = BlockSizeFactor;
|
||||
fhdr.hashEntriesOffset = MpqHashEntryOffset;
|
||||
fhdr.blockEntriesOffset = MpqBlockEntryOffset;
|
||||
fhdr.hashEntriesCount = HashEntriesCount;
|
||||
fhdr.blockEntriesCount = BlockEntriesCount;
|
||||
ByteSwapHdr(&fhdr);
|
||||
|
||||
return stream_.Write(reinterpret_cast<const char *>(&fhdr), sizeof(fhdr));
|
||||
}
|
||||
|
||||
bool MpqWriter::WriteBlockTable()
|
||||
{
|
||||
libmpq__encrypt_block(reinterpret_cast<uint32_t *>(blockTable_.get()), BlockEntrySize, LIBMPQ_BLOCK_TABLE_HASH_KEY);
|
||||
const bool success = stream_.Write(reinterpret_cast<const char *>(blockTable_.get()), BlockEntrySize);
|
||||
libmpq__decrypt_block(reinterpret_cast<uint32_t *>(blockTable_.get()), BlockEntrySize, LIBMPQ_BLOCK_TABLE_HASH_KEY);
|
||||
return success;
|
||||
}
|
||||
|
||||
bool MpqWriter::WriteHashTable()
|
||||
{
|
||||
libmpq__encrypt_block(reinterpret_cast<uint32_t *>(hashTable_.get()), HashEntrySize, LIBMPQ_HASH_TABLE_HASH_KEY);
|
||||
const bool success = stream_.Write(reinterpret_cast<const char *>(hashTable_.get()), HashEntrySize);
|
||||
libmpq__decrypt_block(reinterpret_cast<uint32_t *>(hashTable_.get()), HashEntrySize, LIBMPQ_HASH_TABLE_HASH_KEY);
|
||||
return success;
|
||||
return mpqfs_writer_has_file(writer_, buf);
|
||||
}
|
||||
|
||||
void MpqWriter::RemoveHashEntry(std::string_view filename)
|
||||
{
|
||||
const uint32_t hIdx = FetchHandle(filename);
|
||||
if (hIdx == HashEntryNotFound) {
|
||||
if (writer_ == nullptr)
|
||||
return;
|
||||
}
|
||||
|
||||
MpqHashEntry *hashEntry = &hashTable_[hIdx];
|
||||
MpqBlockEntry *block = &blockTable_[hashEntry->block];
|
||||
hashEntry->block = MpqHashEntry::DeletedBlock;
|
||||
const uint32_t blockOffset = block->offset;
|
||||
const uint32_t blockSize = block->packedSize;
|
||||
memset(block, 0, sizeof(*block));
|
||||
AllocBlock(blockOffset, blockSize);
|
||||
char buf[MaxMpqPathSize];
|
||||
if (filename.size() >= sizeof(buf))
|
||||
return;
|
||||
std::memcpy(buf, filename.data(), filename.size());
|
||||
buf[filename.size()] = '\0';
|
||||
|
||||
mpqfs_writer_remove_file(writer_, buf);
|
||||
}
|
||||
|
||||
void MpqWriter::RemoveHashEntries(bool (*fnGetName)(uint8_t, char *))
|
||||
{
|
||||
char pszFileName[MaxMpqPathSize];
|
||||
|
||||
for (uint8_t i = 0; fnGetName(i, pszFileName); i++) {
|
||||
RemoveHashEntry(pszFileName);
|
||||
}
|
||||
@@ -510,34 +125,41 @@ void MpqWriter::RemoveHashEntries(bool (*fnGetName)(uint8_t, char *))
|
||||
|
||||
bool MpqWriter::WriteFile(std::string_view filename, const std::byte *data, size_t size)
|
||||
{
|
||||
MpqBlockEntry *blockEntry;
|
||||
if (writer_ == nullptr)
|
||||
return false;
|
||||
|
||||
RemoveHashEntry(filename);
|
||||
blockEntry = AddFile(filename, nullptr, 0);
|
||||
if (!WriteFileContents(data, static_cast<uint32_t>(size), blockEntry)) {
|
||||
RemoveHashEntry(filename);
|
||||
char buf[MaxMpqPathSize];
|
||||
if (filename.size() >= sizeof(buf))
|
||||
return false;
|
||||
std::memcpy(buf, filename.data(), filename.size());
|
||||
buf[filename.size()] = '\0';
|
||||
|
||||
// Remove any previous entry with this name so that
|
||||
// only the latest write is included in the archive.
|
||||
mpqfs_writer_remove_file(writer_, buf);
|
||||
|
||||
if (!mpqfs_writer_add_file(writer_, buf, data, size)) {
|
||||
LogError("Failed to write file '{}' to MPQ: {}", filename, mpqfs_last_error());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void MpqWriter::RenameFile(std::string_view name, std::string_view newName) // NOLINT(bugprone-easily-swappable-parameters)
|
||||
void MpqWriter::RenameFile(std::string_view name, std::string_view newName)
|
||||
{
|
||||
const uint32_t index = FetchHandle(name);
|
||||
if (index == HashEntryNotFound) {
|
||||
if (writer_ == nullptr)
|
||||
return;
|
||||
}
|
||||
|
||||
MpqHashEntry *hashEntry = &hashTable_[index];
|
||||
const uint32_t block = hashEntry->block;
|
||||
MpqBlockEntry *blockEntry = &blockTable_[block];
|
||||
hashEntry->block = MpqHashEntry::DeletedBlock;
|
||||
AddFile(newName, blockEntry, block);
|
||||
char oldBuf[MaxMpqPathSize];
|
||||
char newBuf[MaxMpqPathSize];
|
||||
if (name.size() >= sizeof(oldBuf) || newName.size() >= sizeof(newBuf))
|
||||
return;
|
||||
std::memcpy(oldBuf, name.data(), name.size());
|
||||
oldBuf[name.size()] = '\0';
|
||||
std::memcpy(newBuf, newName.data(), newName.size());
|
||||
newBuf[newName.size()] = '\0';
|
||||
|
||||
mpqfs_writer_rename_file(writer_, oldBuf, newBuf);
|
||||
}
|
||||
|
||||
bool MpqWriter::HasFile(std::string_view name) const
|
||||
{
|
||||
return FetchHandle(name) != HashEntryNotFound;
|
||||
}
|
||||
|
||||
} // namespace devilution
|
||||
} // namespace devilution
|
||||
+18
-47
@@ -7,69 +7,40 @@
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "mpq/mpq_common.hpp"
|
||||
#include "utils/logged_fstream.hpp"
|
||||
// Forward-declare so that we can avoid exposing mpqfs.h to all consumers.
|
||||
struct mpqfs_writer;
|
||||
typedef struct mpqfs_writer mpqfs_writer_t;
|
||||
|
||||
namespace devilution {
|
||||
|
||||
constexpr uint32_t MpqWriterHashTableSize = 2048;
|
||||
|
||||
class MpqWriter {
|
||||
public:
|
||||
explicit MpqWriter(const char *path);
|
||||
explicit MpqWriter(const std::string &path)
|
||||
: MpqWriter(path.c_str())
|
||||
explicit MpqWriter(const char *path, bool carryForward = true);
|
||||
explicit MpqWriter(const std::string &path, bool carryForward = true)
|
||||
: MpqWriter(path.c_str(), carryForward)
|
||||
{
|
||||
}
|
||||
MpqWriter(MpqWriter &&other) = default;
|
||||
MpqWriter &operator=(MpqWriter &&other) = default;
|
||||
MpqWriter(MpqWriter &&other) noexcept;
|
||||
MpqWriter &operator=(MpqWriter &&other) noexcept;
|
||||
~MpqWriter();
|
||||
|
||||
bool HasFile(std::string_view name) const;
|
||||
MpqWriter(const MpqWriter &) = delete;
|
||||
MpqWriter &operator=(const MpqWriter &) = delete;
|
||||
|
||||
bool HasFile(std::string_view name) const;
|
||||
void RemoveHashEntry(std::string_view filename);
|
||||
void RemoveHashEntries(bool (*fnGetName)(uint8_t, char *));
|
||||
bool WriteFile(std::string_view filename, const std::byte *data, size_t size);
|
||||
void RenameFile(std::string_view name, std::string_view newName);
|
||||
|
||||
private:
|
||||
bool IsValidMpqHeader(MpqFileHeader *hdr) const;
|
||||
uint32_t GetHashIndex(MpqFileHash fileHash) const;
|
||||
uint32_t FetchHandle(std::string_view filename) const;
|
||||
|
||||
bool ReadMPQHeader(MpqFileHeader *hdr);
|
||||
MpqBlockEntry *AddFile(std::string_view filename, MpqBlockEntry *block, uint32_t blockIndex);
|
||||
bool WriteFileContents(const std::byte *fileData, uint32_t fileSize, MpqBlockEntry *block);
|
||||
|
||||
// Returns an unused entry in the block entry table.
|
||||
MpqBlockEntry *NewBlock(uint32_t *blockIndex = nullptr);
|
||||
|
||||
// Marks space at `blockOffset` of size `blockSize` as free (unused) space.
|
||||
void AllocBlock(uint32_t blockOffset, uint32_t blockSize);
|
||||
|
||||
// Returns the file offset that is followed by empty space of at least the given size.
|
||||
uint32_t FindFreeBlock(uint32_t size);
|
||||
|
||||
bool WriteHeaderAndTables();
|
||||
bool WriteHeader();
|
||||
bool WriteBlockTable();
|
||||
bool WriteHashTable();
|
||||
void InitDefaultMpqHeader(MpqFileHeader *hdr);
|
||||
|
||||
LoggedFStream stream_;
|
||||
std::string name_;
|
||||
uint32_t size_ {};
|
||||
std::unique_ptr<MpqHashEntry[]> hashTable_;
|
||||
std::unique_ptr<MpqBlockEntry[]> blockTable_;
|
||||
|
||||
// Amiga cannot Seekp beyond EOF.
|
||||
// See https://github.com/bebbo/libnix/issues/30
|
||||
#ifndef __AMIGA__
|
||||
#define CAN_SEEKP_BEYOND_EOF
|
||||
#endif
|
||||
|
||||
#ifndef CAN_SEEKP_BEYOND_EOF
|
||||
long streamBegin_;
|
||||
#endif
|
||||
std::string path_;
|
||||
mpqfs_writer_t *writer_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace devilution
|
||||
} // namespace devilution
|
||||
+6
-6
@@ -160,14 +160,14 @@ void EncodeHero(SaveWriter &saveWriter, const PlayerPack *pack)
|
||||
saveWriter.WriteFile("hero", packed.get(), packedLen);
|
||||
}
|
||||
|
||||
SaveWriter GetSaveWriter(uint32_t saveNum)
|
||||
SaveWriter GetSaveWriter(uint32_t saveNum, bool carryForward = true)
|
||||
{
|
||||
return SaveWriter(GetSavePath(saveNum));
|
||||
return SaveWriter(GetSavePath(saveNum), carryForward);
|
||||
}
|
||||
|
||||
SaveWriter GetStashWriter()
|
||||
{
|
||||
return SaveWriter(GetStashSavePath());
|
||||
return SaveWriter(GetStashSavePath(), /*carryForward=*/true);
|
||||
}
|
||||
|
||||
#ifndef DISABLE_DEMOMODE
|
||||
@@ -625,7 +625,7 @@ const char *pfile_get_password()
|
||||
|
||||
void pfile_write_hero(bool writeGameData)
|
||||
{
|
||||
SaveWriter saveWriter = GetSaveWriter(gSaveNumber);
|
||||
SaveWriter saveWriter = GetSaveWriter(gSaveNumber, /*carryForward=*/writeGameData);
|
||||
pfile_write_hero(saveWriter, writeGameData);
|
||||
}
|
||||
|
||||
@@ -730,7 +730,7 @@ bool pfile_ui_save_create(_uiheroinfo *heroinfo)
|
||||
|
||||
giNumberOfLevels = gbIsHellfire ? 25 : 17;
|
||||
|
||||
SaveWriter saveWriter = GetSaveWriter(saveNum);
|
||||
SaveWriter saveWriter = GetSaveWriter(saveNum, /*carryForward=*/false);
|
||||
saveWriter.RemoveHashEntries(GetFileName);
|
||||
CopyUtf8(hero_names[saveNum], heroinfo->name, sizeof(hero_names[saveNum]));
|
||||
|
||||
@@ -796,7 +796,7 @@ void pfile_remove_temp_files()
|
||||
if (gbIsMultiplayer)
|
||||
return;
|
||||
|
||||
SaveWriter saveWriter = GetSaveWriter(gSaveNumber);
|
||||
SaveWriter saveWriter = GetSaveWriter(gSaveNumber, /*carryForward=*/true);
|
||||
saveWriter.RemoveHashEntries(GetTempSaveNames);
|
||||
}
|
||||
|
||||
|
||||
+65
-40
@@ -87,45 +87,6 @@ std::string_view Dirname(std::string_view path)
|
||||
return std::string_view { path.data(), sep };
|
||||
}
|
||||
|
||||
bool FileExists(const char *path)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
#ifdef DEVILUTIONX_WINDOWS_NO_WCHAR
|
||||
const bool exists = ::GetFileAttributesA(path) != INVALID_FILE_ATTRIBUTES;
|
||||
#else
|
||||
const auto pathUtf16 = ToWideChar(path);
|
||||
if (pathUtf16 == nullptr) {
|
||||
LogError("UTF-8 -> UTF-16 conversion error code {}", ::GetLastError());
|
||||
return false;
|
||||
}
|
||||
const bool exists = ::PathFileExistsW(&pathUtf16[0]);
|
||||
#endif
|
||||
if (!exists) {
|
||||
if (::GetLastError() == ERROR_FILE_NOT_FOUND || ::GetLastError() == ERROR_PATH_NOT_FOUND) {
|
||||
::SetLastError(ERROR_SUCCESS);
|
||||
} else {
|
||||
#ifdef DEVILUTIONX_WINDOWS_NO_WCHAR
|
||||
LogError("GetFileAttributesA({}): error code {}", path, ::GetLastError());
|
||||
#else
|
||||
LogError("PathFileExistsW({}): error code {}", path, ::GetLastError());
|
||||
#endif
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
#elif defined(DVL_HAS_POSIX_2001) && !defined(__ANDROID__)
|
||||
return ::access(path, F_OK) == 0;
|
||||
#elif defined(DVL_HAS_FILESYSTEM)
|
||||
std::error_code ec;
|
||||
return std::filesystem::exists(reinterpret_cast<const char8_t *>(path), ec);
|
||||
#else
|
||||
SDL_IOStream *file = SDL_IOFromFile(path, "rb");
|
||||
if (file == nullptr) return false;
|
||||
SDL_CloseIO(file);
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
namespace {
|
||||
DWORD WindowsGetFileAttributes(const char *path)
|
||||
@@ -153,9 +114,73 @@ DWORD WindowsGetFileAttributes(const char *path)
|
||||
}
|
||||
return attr;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
#ifdef DEVILUTIONX_WINDOWS_NO_WCHAR
|
||||
bool WindowsFindFile(const char *path)
|
||||
#else
|
||||
bool WindowsFindFile(const wchar_t *path)
|
||||
#endif
|
||||
{
|
||||
#ifdef DEVILUTIONX_WINDOWS_NO_WCHAR
|
||||
WIN32_FIND_DATAA findFileData;
|
||||
HANDLE hFind = FindFirstFileA(path, &findFileData);
|
||||
#else
|
||||
WIN32_FIND_DATAW findFileData;
|
||||
HANDLE hFind = FindFirstFileW(path, &findFileData);
|
||||
#endif
|
||||
if (hFind == INVALID_HANDLE_VALUE) {
|
||||
return false;
|
||||
}
|
||||
FindClose(hFind);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WindowsFileExists(const char *path)
|
||||
{
|
||||
#ifdef DEVILUTIONX_WINDOWS_NO_WCHAR
|
||||
const bool exists = ::GetFileAttributesA(path) != INVALID_FILE_ATTRIBUTES;
|
||||
#else
|
||||
const auto pathUtf16 = ToWideChar(path);
|
||||
if (pathUtf16 == nullptr) {
|
||||
LogError("UTF-8 -> UTF-16 conversion error code {}", ::GetLastError());
|
||||
return false;
|
||||
}
|
||||
const bool exists = ::PathFileExistsW(&pathUtf16[0]);
|
||||
#endif
|
||||
if (exists) {
|
||||
return true;
|
||||
}
|
||||
if (::GetLastError() == ERROR_FILE_NOT_FOUND || ::GetLastError() == ERROR_PATH_NOT_FOUND) {
|
||||
::SetLastError(ERROR_SUCCESS);
|
||||
return false;
|
||||
}
|
||||
#ifdef DEVILUTIONX_WINDOWS_NO_WCHAR
|
||||
LogError("GetFileAttributesA({}): error code {}", path, ::GetLastError());
|
||||
return WindowsFindFile(path);
|
||||
#else
|
||||
LogError("PathFileExistsW({}): error code {}", path, ::GetLastError());
|
||||
return WindowsFindFile(pathUtf16.get());
|
||||
#endif
|
||||
}
|
||||
} // namespace
|
||||
#endif
|
||||
|
||||
bool FileExists(const char *path)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return WindowsFileExists(path);
|
||||
#elif defined(DVL_HAS_POSIX_2001) && !defined(__ANDROID__) && !defined(_WIN32)
|
||||
return ::access(path, F_OK) == 0;
|
||||
#elif defined(DVL_HAS_FILESYSTEM) && !defined(_WIN32)
|
||||
std::error_code ec;
|
||||
return std::filesystem::exists(reinterpret_cast<const char8_t *>(path), ec);
|
||||
#else
|
||||
SDL_IOStream *file = SDL_IOFromFile(path, "rb");
|
||||
if (file == nullptr) return false;
|
||||
SDL_CloseIO(file);
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool DirectoryExists(const char *path)
|
||||
{
|
||||
|
||||
@@ -412,8 +412,13 @@ TEST(Writehero, pfile_write_hero)
|
||||
|
||||
std::vector<unsigned char> s(picosha2::k_digest_size);
|
||||
picosha2::hash256(data.get(), data.get() + size, s.begin(), s.end());
|
||||
// The hash changed because the MpqWriter now delegates to mpqfs_writer
|
||||
// which creates fresh archives (no in-place block management) and may
|
||||
// produce a different on-disk layout. The file content is identical;
|
||||
// only the binary representation differs.
|
||||
// To regenerate: run this test, let it fail, and update the hash below.
|
||||
EXPECT_EQ(picosha2::bytes_to_hex_string(s.begin(), s.end()),
|
||||
"a79367caae6192d54703168d82e0316aa289b2a33251255fad8abe34889c1d3a");
|
||||
"b52885393aedc22c856c7c315975c9be89a034b64a2067e22d5387a51998a51b");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -37,7 +37,7 @@ import sys
|
||||
# 2. Require devilutionx forks (all others).
|
||||
_DEPS = [
|
||||
"asio",
|
||||
"libmpq",
|
||||
"mpqfs",
|
||||
"libsmackerdec",
|
||||
"libzt",
|
||||
"magic_enum",
|
||||
@@ -45,7 +45,7 @@ _DEPS = [
|
||||
"sheenbidi",
|
||||
"unordered_dense",
|
||||
]
|
||||
_ALWAYS_VENDORED_DEPS = ['asio', 'libmpq', 'libsmackerdec', 'libzt']
|
||||
_ALWAYS_VENDORED_DEPS = ['asio', 'mpqfs', 'libsmackerdec', 'libzt']
|
||||
|
||||
# These dependencies are not vendored by default.
|
||||
# Run with `--fully_vendored` to include them.
|
||||
|
||||
@@ -73,8 +73,8 @@
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Link>
|
||||
<AdditionalDependencies>sdl_image.lib;libpng16_staticd.lib;pkware.lib;fmtd.lib;zlibstatic.lib;bzip2.lib;libsmackerdec.lib;libmpq.lib;libdevilutionx.lib;lua_static.lib;sdl2.lib;sdl_audiolib.lib;asio.lib;sodium.lib;zt.lib;lwip_pic.lib;miniupnpc_pic.lib;natpmp_pic.lib;zt_pic.lib;zto_pic.lib;shlwapi.lib;shell32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>..\build\SDL\VisualC-WinRT\x64\Debug\SDL-UWP;..\build\3rdParty\SDL_image\Debug;..\build\_deps\zlib-build\Debug;..\build\3rdParty\PKWare\Debug;..\build\3rdParty\bzip2\Debug;..\build\3rdParty\libsmackerdec\Debug;..\build\3rdParty\libmpq\Debug;..\build\_deps\lua-build\lua-5.4.7\Debug;..\build\_deps\sdl_audiolib-build\Debug;..\build\3rdParty\asio\Debug;..\build\_deps\libsodium-build\Debug;..\build\_deps\libzt-build\lib\Debug;..\build\_deps\libfmt-build\Debug;..\build\_deps\libpng-build\Debug;..\build\Source\libdevilutionx.dir\Debug;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
<AdditionalDependencies>sdl_image.lib;libpng16_staticd.lib;mpqfs.lib;fmtd.lib;zlibstatic.lib;bzip2.lib;libsmackerdec.lib;libdevilutionx.lib;lua_static.lib;sdl2.lib;sdl_audiolib.lib;asio.lib;sodium.lib;zt.lib;lwip_pic.lib;miniupnpc_pic.lib;natpmp_pic.lib;zt_pic.lib;zto_pic.lib;shlwapi.lib;shell32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>..\build\SDL\VisualC-WinRT\x64\Debug\SDL-UWP;..\build\3rdParty\SDL_image\Debug;..\build\_deps\zlib-build\Debug;..\build\3rdParty\bzip2\Debug;..\build\3rdParty\libsmackerdec\Debug;..\build\_deps\mpqfs-build\Debug;..\build\_deps\lua-build\lua-5.4.7\Debug;..\build\_deps\sdl_audiolib-build\Debug;..\build\3rdParty\asio\Debug;..\build\_deps\libsodium-build\Debug;..\build\_deps\libzt-build\lib\Debug;..\build\_deps\libfmt-build\Debug;..\build\_deps\libpng-build\Debug;..\build\Source\libdevilutionx.dir\Debug;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
<ClCompile>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
@@ -92,8 +92,8 @@
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Link>
|
||||
<AdditionalDependencies>sdl_image.lib;libpng16_static.lib;pkware.lib;fmt.lib;zlibstatic.lib;bzip2.lib;libsmackerdec.lib;libmpq.lib;libdevilutionx.lib;lua_static.lib;sdl2.lib;sdl_audiolib.lib;sheenbidi.lib;asio.lib;sodium.lib;zt.lib;lwip_pic.lib;miniupnpc_pic.lib;natpmp_pic.lib;zt_pic.lib;zto_pic.lib;shlwapi.lib;shell32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>..\build\SDL\VisualC-WinRT\x64\Release\SDL-UWP;..\build\3rdParty\SDL_image\Release;..\build\_deps\zlib-build\Release;..\build\3rdParty\PKWare\Release;..\build\3rdParty\bzip2\Release;..\build\3rdParty\libsmackerdec\Release;..\build\3rdParty\libmpq\Release;..\build\_deps\lua-build\lua-5.4.7\Release;..\build\_deps\sdl_audiolib-build\Release;..\build\3rdParty\asio\Release;..\build\_deps\libsodium-build\Release;..\build\_deps\libzt-build\lib\Release;..\build\_deps\libfmt-build\Release;..\build\_deps\libpng-build\Release;..\build\_deps\sheenbidi-build\Release;..\build\Source\libdevilutionx.dir\Release;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
<AdditionalDependencies>sdl_image.lib;libpng16_static.lib;mpqfs.lib;fmt.lib;zlibstatic.lib;bzip2.lib;libsmackerdec.lib;libdevilutionx.lib;lua_static.lib;sdl2.lib;sdl_audiolib.lib;sheenbidi.lib;asio.lib;sodium.lib;zt.lib;lwip_pic.lib;miniupnpc_pic.lib;natpmp_pic.lib;zt_pic.lib;zto_pic.lib;shlwapi.lib;shell32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>..\build\SDL\VisualC-WinRT\x64\Release\SDL-UWP;..\build\3rdParty\SDL_image\Release;..\build\_deps\zlib-build\Release;..\build\3rdParty\bzip2\Release;..\build\3rdParty\libsmackerdec\Release;..\build\_deps\mpqfs-build\Release;..\build\_deps\lua-build\lua-5.4.7\Release;..\build\_deps\sdl_audiolib-build\Release;..\build\3rdParty\asio\Release;..\build\_deps\libsodium-build\Release;..\build\_deps\libzt-build\lib\Release;..\build\_deps\libfmt-build\Release;..\build\_deps\libpng-build\Release;..\build\_deps\sheenbidi-build\Release;..\build\Source\libdevilutionx.dir\Release;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
<ClCompile>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
|
||||
Reference in New Issue
Block a user