Full Code of CleasbyCode/pdvzip for AI

main 352c741e6290 cached
21 files
548.5 KB
167.4k tokens
450 symbols
1 requests
Download .txt
Showing preview only (565K chars total). Download the full file or copy to clipboard to get everything.
Repository: CleasbyCode/pdvzip
Branch: main
Commit: 352c741e6290
Files: 21
Total size: 548.5 KB

Directory structure:
gitextract_u94dvs2j/

├── LICENSE
├── README.md
└── src/
    ├── archive_analysis.cpp
    ├── binary_utils.cpp
    ├── compile_pdvzip.sh
    ├── display_info.cpp
    ├── file_io.cpp
    ├── image_processing.cpp
    ├── image_processing_internal.h
    ├── image_resize.cpp
    ├── lodepng/
    │   ├── LICENSE
    │   ├── lodepng.cpp
    │   └── lodepng.h
    ├── main.cpp
    ├── pdvzip.h
    ├── polyglot_assembly.cpp
    ├── program_args.cpp
    ├── script_builder.cpp
    ├── script_builder_internal.h
    ├── script_text_builder.cpp
    └── user_input.cpp

================================================
FILE CONTENTS
================================================

================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2022-2026 Nicholas Cleasby

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# pdvzip  

Embed a ***ZIP*** or ***JAR*** file within a ***PNG*** image to create a ***tweetable*** and "[***executable***](https://github.com/CleasbyCode/pdvzip#extracting-your-embedded-files)" ***PNG*** polyglot file.  
Share the image on ***X-Twitter*** and a few other compatible platforms, which retains the embedded archive.  

*Note: For compatibility reasons, please ***do not*** use encrypted / password protected ZIP files.*

There is also a [***Web edition***](https://cleasbycode.co.uk/pdvzip/app/), which you can use immediately, as a convenient alternative to downloading and compiling the CLI source code. Web file uploads are limited to **20MB**. 

Based on the similar idea by [***David Buchanan***](https://www.da.vidbuchanan.co.uk/), from his original ***Python*** program [***tweetable-polyglot-png***](https://github.com/DavidBuchanan314/tweetable-polyglot-png),  
***pdvzip*** uses different methods for [***storing***](https://github.com/CleasbyCode/pdvzip#png-image-requirements-for-arbitrary-data-preservation) and [***extracting***](https://github.com/CleasbyCode/pdvzip#extracting-your-embedded-files) embedded files within a ***PNG*** image.  
  
![Demo Image](https://github.com/CleasbyCode/pdvzip/blob/main/demo_image/HDO2mHoWYAAUxIy.png)  
***Credits:*** *Image* - [***@KCP228***](https://x.com/KCP228) *PowerShell Script* - [***@gierrofo***](https://x.com/gierrofo)

The ***Linux/Windows*** extraction script is stored within the ***iCCP*** chunk of the ***PNG*** image. The embedded ***ZIP/JAR*** file is stored within its own ***IDAT*** chunk, which will be the last ***IDAT*** chunk of the image file.  

With ***pdvzip***, you can embed a ***ZIP/JAR*** file up to a maximum size of ***2GB** (cover image + archive file). Compatible sites, ***listed below***, have their own ***much smaller*** size limits.

## Compatible Platforms
*Posting size limit measured by the combined size of the cover image + compressed data file.* 

* ***X-Twitter*** (**5MB**), ***Flickr*** (**200MB**), ***ImgBB*** (**32MB**), ***PostImage*** (**32MB**), ***ImgPile*** (**8MB**).  

*Image dimension size limits:*  

* ***PNG-32/24*** (*Truecolor*) **68x68** Min. - **900x900** Max.
* ***PNG-8*** (*Indexed-color*) **68x68** Min. - **4096x4096** Max.

## Usage (Linux)

```console
$ chmod +x compile_pdvzip.sh
$ ./compile_pdvzip.sh

Compiling pdvzip...
Compilation successful. Executable 'pdvzip' created.

$ sudo cp pdvzip /usr/bin
$ pdvzip

Usage: pdvzip <cover_image> <zip/jar>
       pdvzip --info

$ pdvzip my_cover_image.png document_pdf.zip

Created PNG-ZIP polyglot image file: pzip_55183.png (4038367 bytes).

Complete!

$ pdvzip my_cover_image.png hello_world.jar

Created PNG-JAR polyglot image file: pjar_19662.png (1016336 bytes).

Complete!

``` 
## Extracting Your Embedded File(s)  
**Important:** When saving images from ***X-Twitter***, click the image in the post to ***fully expand it***, before saving.  

The following section covers the extraction of embedded ***ZIP*** files. ***JAR*** files are covered later.

***pdvzip*** (for ***Linux***) will attempt to ***automatically set executable permissions*** on newly created polyglot image files.  

You will need to manually set executable permissions using ***chmod*** on these polyglot images downloaded from hosting sites or copied from another machine.

https://github.com/user-attachments/assets/8feca575-b135-4f58-839a-2159fce09b44  

https://github.com/user-attachments/assets/77472a02-52dd-4a5c-a035-b30dcc842cce  

https://github.com/user-attachments/assets/a143e694-31f5-4235-ace1-30217fe8ab41

***Linux - using bash (or sh) shell environment.***
```console

$ ./pzip_55183.png

```
**For any other Linux shell environment, you will probably need to invoke bash (or sh) to run the image file.**
```console

mx% bash ./pzip_55183.png 

``` 
Alternative extraction (***Linux***).  Using ***wget*** to download and run the image directly from the hosting site.  
***X-Twitter*** ***wget*** example: **Image with embedded ***python*** script**.
```console

$ wget -O Fibo.png "https://pbs.twimg.com/media/GLXTYeCWMAAA6B_.png";chmod +x Fibo.png;bash ./Fibo.png

```   

**Windows** ***(Rename the image file extension to '.cmd')***
```console

G:\demo> ren pzip_55183.png pzip_55183.cmd
G:\demo> .\pzip_55183.cmd

```
Alternative extraction (***Windows***).  Using ***iwr*** to download and run the image directly from the hosting site.  
***Flickr*** ***iwr*** example: **Image with embedded mp4 video file.**
```console

iwr -o swing.cmd "https://live.staticflickr.com/65535/54025688614_2f9d474cba_o_d.png";.\swing.cmd

```

Opening the ***.cmd*** file from the desktop, on its first run, ***Windows*** may display a security warning.  
Clear this by clicking '***More info***' then select '***Run anyway***'.  

To avoid security warnings, run the file from a ***Windows console***, as shown in the above example.  

***The file (or folder) within the ZIP archive that appears first within the ZIP file record, determines what extraction script, based on file type, is used.***

For common ***video & audio*** files, ***Linux*** will first attempt to use the media player ***mpv*** or ***vlc*** if no ***mpv***. ***Firefox*** is used as a last resort. ***Windows*** uses the default media player.  

***PDF*** - ***Linux*** will use ***evince*** or ***firefox***. ***Windows*** uses the default ***PDF*** viewer.  
***Python*** - ***Linux*** & ***Windows*** use ***python3*** to run these programs.  
***PowerShell*** - ***Linux*** uses ***pwsh*** (if installed), ***Windows*** uses either ***powershell.exe*** or ***pwsh.exe*** to run these scripts.
***Folder*** - ***Linux*** uses ***xdg-open***, ***Windows*** uses ***powershell.exe*** with II (***Invoke-Item***) command, to open zipped folders.

For any other file type within your ***ZIP*** file, ***Linux*** & ***Windows*** will rely on the operating system's set default method/application. Obviously, the compressed/embedded file needs to be compatible with the operating system you run it on.

If the archive file is ***JAR*** or the compressed file type within the ***ZIP*** archive is ***PowerShell***, ***Python***, ***Shell Script*** or a ***Windows/Linux Executable, pdvzip*** will give you the option to provide command-line arguments for your file, if required. 

The ***command-line arguments*** will be added to the ***Linux/Windows*** extraction script, embedded within the ***iCCP*** chunk of your ***PNG*** cover image. 

Make sure to enclose arguments containing spaces, such as file & directory names, within "quotation" marks. e.g.
```console
$ ./pdvzip my_cover_image.png jdvrif_linux_executable.zip

For this file type you can provide command-line arguments here, if required.

Linux: -e ../my_cover_image.jpg "../my document file.pdf"

```
Also, be aware when using arguments for the compressed ***ZIP*** file types (not ***JAR***), that you are always working from within a created subdirectory "***pdvzip_xxxx (e.g. pdvzip_5444)***".  

https://github.com/user-attachments/assets/e55e9671-423c-4439-89e6-356c0080b4c1

https://github.com/user-attachments/assets/8d6d97c1-4c70-4f60-bba5-b01fad08b60e

To just get access to the file(s) within the ***ZIP*** archive, rename the '***.png***' file extension to '***.zip***'.  
Treat the ***ZIP*** archive as read-only, do not add or remove files from the ***PNG-ZIP*** polyglot file.  

## Executing Embedded Java Programs

***Linux Option 1:***
```console
$ java -jar pjar_19662.png
Note: If you use this method to run your embedded Java program, you will have to manually add command-line
      arguments (if required) to the end of the command, as your embedded arguments will not work with
      this method. e.g.
      user1@mx:~/Desktop$ java -jar ./pjar_19662.png -u john_s -a 42 -f "John Smith"
```
***Linux Option 2a, using bash (or sh) shell environment:***
```console
$ ./pjar_19662.png
Note: This method will execute the embedded Java program and also use any embedded
      command-line arguments with the Java program.
```
***Linux Option 2b, using any other shell environment, you will need to invoke bash (or sh) to execute the image:***
```console
mx% bash ./pjar_19662.png
```
***Windows Option 1:***
```console
PS C:\Users\Nick\Desktop\jar_demo> java -jar .\pjar_19662.png 
Note: If you use this method to run your embedded Java program, you will have to manually add command-line
      arguments (if required) to the end of the command, as your embedded arguments will not work with
      this method. e.g.
      PS C:\Users\Nick\Desktop\jar_demo> java -jar .\pjar_19662.png -u john_s -a 42 -f "John Smith"
```
***Windows Option 2:***
```console
PS C:\Users\Nick\Desktop\jar_demo> ren .\pjar_19662.png .\pjar_19662.cmd
PS C:\Users\Nick\Desktop\jar_demo> .\pjar_19662.cmd
Note: This method will execute the embedded Java program and will also use any
      embedded command-line arguments with the Java program.
```
https://github.com/user-attachments/assets/9451ad50-4c7c-4fa3-a1be-3854189bde00

## PNG Image Requirements for Arbitrary Data Preservation

***PNG*** file size (image + archive file) must not exceed the platform's size limit. 

The site will either refuse to upload your image or it will convert your image to ***jpg***, such as ***X-Twitter***,
and you will lose the embedded content.

***Dimensions:***

***PNG-32/24 (Truecolor)***

Image dimensions can be set between a minimum of **68 x 68** and a maximum of **900 x 900**.

***Note:*** *A cover image that is detected as ***PNG-32/24 Truecolor (color type 6 or 2),*** 
with less than 257 colors, 
will be converted by ***pdvzip*** to a ***PNG-8 Indexed-color (color type 3)*** image. 
This is done for compatiblity reasons as it should prevent platforms such as ***X-Twitter***
from also converting your image, which would result in the loss of the embedded archive file.*

***PNG-8 (Indexed-color)***

Image dimensions can be set between a minimum of **68 x 68** and a maximum of **4096 x 4096**.
        
***PNG Chunks:***  

For example, with ***X-Twitter*** you can ***overfill*** the following ***PNG*** chunks with arbitrary data,  
in which the platform will preserve as long as you keep within the image dimension & file size limits.  

***bKGD, cHRM, gAMA, hIST,*** *iCCP (Limited size chunk. 10KB Max. with X-Twitter)*,  
***IDAT (Use as last IDAT chunk, after the final image IDAT chunk),  
PLTE (Use only with ***PNG-32/24*** images),  
pHYs, sBIT, sPLT, sRGB,*** *tRNS (Use only with PNG-32 images).* 

*Other platforms may differ in what chunks they preserve and which ones you can overfill.*
  
***pdvzip*** uses the chunks ***iCCP*** (stores extraction script) and ***IDAT*** (stores the ***ZIP/JAR*** file) for your arbitrary data.

## ***ZIP/JAR*** File Size & Other Important Information

To work out the maximum ***ZIP/JAR*** file size, start with the size limit, minus the image size, minus ***1500*** bytes (extraction script size).  
  
***X-Twitter*** example: (**5MB** limit) **5,242,880** - (**307,200** [image] + **1500** [extraction script]) = **4,934,180 bytes** available for your ***ZIP/JAR*** file.  

* Make sure your ***ZIP/JAR*** file is a standard ***ZIP/JAR*** archive, compatible with ***Linux*** unzip & ***Windows*** Explorer.
* Do not include more than one ***.zip*** file within the main ***ZIP*** archive. (***.rar*** files are ok).
* Do not include other ***pdvzip*** created ***PNG*** image files within the main ***ZIP*** archive, as they are essentially ***.zip*** files.
* Always use file extensions for your file(s) within the ***ZIP*** archive: ***my_doc.pdf***, ***my_video.mp4***, ***my_program.py***, etc.
  A file without an extension within a ***ZIP*** archive will be considered a ***Linux*** executable.      

## An 8 File Tweetable PNG Polyglot

![Polyglot Image](https://github.com/CleasbyCode/pdvzip/blob/main/demo_image/Png_P0wershell_Z1p.png)  

https://github.com/user-attachments/assets/d1b5888c-fe91-4a58-8ad6-731d9165a57c

A while ago I put together an 8 file (tweetable) PNG polyglot file. I recently found it again, so thought I would share it here. 
I have made some changes to the polyglot, such as improving the PowerShell script and using a different MP4 video file that is embedded
within the image (this is extracted and played by the PowerShell script).  

The PNG polyglot file consists of the image itself - PNG, a Web page, ZIP archive, MP3 audio file, JAR (executable Java program),
RAR archive, PDF and a PS1 (PowerShell) script.  

To open and view the PDF document within the PNG image, just change the file extension to .pdf, under Windows you should be able to 
view the PDF using most Web browsers, such as Brave, Chrome & Firefox. It is the same with Linux, including most Linux PDF viewers.

To play the audio file, change the extension to .mp3, and open the file under Windows with the VLC media player or the Windows
legacy media player. With Linux, use the VLC media player.  

To run the Java program, change the extension to .jar. From the Desktop in both Windows and Linux, you should be able to just double-click
the icon to run. It should open and display the Calculator app on your Desktop.  

To access the ZIP archive with Windows, change the extension to .zip and open it with Windows Explorer. You can also
use the Expand-Archive command from a Windows terminal. With Linux use the CLI tool ***unzip***.  

To access the RAR archive with Windows, change the extension to .rar and use the WinRAR application. With Linux use
the ***unrar*** CLI tool (e.g. $ unrar e image_file.rar).  

To view the embedded Web page, just change the file extension to .htm, open and view it with any browser.  

To run the embedded PowerShell script on Windows, change the file extension to .ps1 and from the terminal run the following command:  

```console
PS C:\Users\Nick\Desktop> powershell -ExecutionPolicy Bypass -File .\image_file.ps1
```

To run the PowerShell script on Linux, first make sure you have PowerShell installed as it is not installed by default.
Again, change the file extension to .ps1. From the Linux terminal, run the following command:  

```console
$ pwsh ./image_file.ps1
```
The PowerShell script will extract and play an MP4 video file embedded within the image.  

Video credit: The video file used in the PowerShell example is the work of [***@doopiidoop***](https://x.com/doopiidoop)

## Third-Party Libraries

This project includes the following third-party library:

- **LodePNG** by Lode Vandevenne
  - License: zlib/libpng (see [***LICENSE***](https://github.com/lvandeve/lodepng/blob/master/LICENSE) file)
  - Copyright (c) 2005-2024 Lode Vandevenne

##


================================================
FILE: src/archive_analysis.cpp
================================================
#include "pdvzip.h"

namespace {

template <typename T>
[[nodiscard]] T readZipField(std::span<const Byte> data, std::size_t offset, std::string_view context) {
	static_assert(sizeof(T) == 2 || sizeof(T) == 4);
	try {
		if constexpr (sizeof(T) == 2) {
			return static_cast<T>(readLe16(data, offset));
		} else {
			return static_cast<T>(readLe32(data, offset));
		}
	}
	catch (const std::exception&) {
		throw std::runtime_error(std::format("{}: Truncated ZIP record.", context));
	}
}

[[nodiscard]] bool containsControlCharacters(std::string_view value) {
	return std::ranges::any_of(value, [](unsigned char c) {
		return std::iscntrl(c) != 0;
	});
}

[[nodiscard]] char toLowerAscii(char ch) {
	if (ch >= 'A' && ch <= 'Z') {
		return static_cast<char>(ch - 'A' + 'a');
	}
	return ch;
}

[[nodiscard]] std::string_view readZipStringView(std::span<const Byte> data, std::size_t start, std::size_t length,
                                                 std::string_view overflow_error, std::string_view bounds_error) {
	const std::size_t end = checkedAdd(start, length, overflow_error);
	if (end > data.size()) {
		throw std::runtime_error(std::string(bounds_error));
	}
	return std::string_view(reinterpret_cast<const char*>(data.data() + start), length);
}

[[nodiscard]] bool hasWindowsReservedSegmentName(std::string_view segment) {
	const std::size_t dot_pos = segment.find('.');
	const std::string_view stem = segment.substr(0, dot_pos);

	auto equalsReservedName = [](std::string_view lhs, std::string_view rhs) {
		return std::ranges::equal(lhs, rhs, [](char a, char b) {
			return toLowerAscii(a) == b;
		});
	};

	if (equalsReservedName(stem, "con"sv)
		|| equalsReservedName(stem, "prn"sv)
		|| equalsReservedName(stem, "aux"sv)
		|| equalsReservedName(stem, "nul"sv)) {
		return true;
	}
	return stem.size() == 4
		&& stem[3] >= '1' && stem[3] <= '9'
		&& ((toLowerAscii(stem[0]) == 'c' && toLowerAscii(stem[1]) == 'o' && toLowerAscii(stem[2]) == 'm')
			|| (toLowerAscii(stem[0]) == 'l' && toLowerAscii(stem[1]) == 'p' && toLowerAscii(stem[2]) == 't'));
}

[[nodiscard]] bool hasWindowsInvalidPathCharacter(std::string_view segment) {
	return std::ranges::any_of(segment, [](char ch) {
		return ch == '<' || ch == '>' || ch == ':' || ch == '"' || ch == '|' || ch == '?' || ch == '*';
	});
}

[[nodiscard]] std::string makePortableEntryKey(std::string_view path) {
	std::string key;
	key.reserve(path.size());
	for (char ch : path) {
		key.push_back(ch == '\\' ? '/' : toLowerAscii(ch));
	}
	while (!key.empty() && key.back() == '/') {
		key.pop_back();
	}
	return key;
}

[[nodiscard]] bool isUnsafeEntryPath(std::string_view path);

struct LocalEntrySpan {
	std::size_t begin;
	std::size_t end;
};

constexpr std::size_t
	ZIP_WRAP_PREFIX_SIZE          = 8,
	ZIP_WRAP_TRAILER_SIZE         = 4,
	LOCAL_RECORD_MIN_SIZE         = 30,
	LOCAL_RECORD_NAME_INDEX       = 30,
	CENTRAL_RECORD_MIN_SIZE       = 46,
	CENTRAL_RECORD_NAME_INDEX     = 46,
	CENTRAL_VERSION_MADE_BY       = 4,
	CENTRAL_FLAGS_OFFSET          = 8,
	CENTRAL_CRC32                 = 16,
	CENTRAL_COMPRESSED_SIZE       = 20,
	CENTRAL_UNCOMPRESSED_SIZE     = 24,
	CENTRAL_NAME_LENGTH_OFFSET    = 28,
	CENTRAL_EXTRA_LENGTH_OFFSET   = 30,
	CENTRAL_COMMENT_LENGTH_OFFSET = 32,
	CENTRAL_DISK_START            = 34,
	CENTRAL_EXTERNAL_ATTRIBUTES   = 38,
	CENTRAL_LOCAL_OFFSET          = 42;

struct CentralDirectoryBounds {
	std::size_t start;
	std::size_t end;
	uint16_t total_records;
};

struct CentralEntryMetadata {
	std::size_t entry_number;
	uint16_t version_made_by;
	uint16_t flags;
	uint32_t crc32;
	uint32_t compressed_size;
	uint32_t uncompressed_size;
	uint16_t disk_start;
	uint32_t external_attributes;
	std::size_t local_header_offset;
	std::string_view name;
	std::size_t record_size;
};

struct ArchiveEntryTracking {
	std::uint64_t total_uncompressed = 0;
	std::unordered_set<std::string> seen_entries;
	std::unordered_set<std::string> file_entries;
	std::unordered_set<std::string> directory_entries;
	std::vector<LocalEntrySpan> local_spans;

	void reserve(std::size_t total_records) {
		seen_entries.reserve(total_records);
		file_entries.reserve(total_records);
		directory_entries.reserve(total_records);
		local_spans.reserve(total_records);
	}
};

void validateEntryName(std::string_view entry_name, std::string_view control_label, std::string_view unsafe_error,
                       std::optional<std::size_t> entry_number = std::nullopt) {
	if (containsControlCharacters(entry_name)) {
		throw std::runtime_error(entry_number
			? std::format("{} {} contains unsupported control characters.", control_label, *entry_number)
			: std::string(control_label));
	}
	if (isUnsafeEntryPath(entry_name)) {
		throw std::runtime_error(std::format("{}: \"{}\".", unsafe_error, entry_name));
	}
}

[[nodiscard]] bool isUnixLikeZipHost(uint16_t version_made_by) {
	const Byte host = static_cast<Byte>(version_made_by >> 8);
	return host == 3   // UNIX
		|| host == 19; // macOS/OS X
}

void validateEntryAttributes(uint16_t version_made_by, uint16_t flags, uint32_t external_attributes,
                             std::string_view entry_name, std::size_t entry_number) {
	constexpr uint16_t
		GENERAL_PURPOSE_ENCRYPTED = 1u << 0,
		GENERAL_PURPOSE_STRONG_ENCRYPTION = 1u << 6;
	constexpr uint32_t
		UNIX_FILE_TYPE_MASK = 0170000,
		UNIX_REGULAR_FILE   = 0100000,
		UNIX_DIRECTORY      = 0040000,
		UNIX_SYMLINK        = 0120000;

	if ((flags & (GENERAL_PURPOSE_ENCRYPTED | GENERAL_PURPOSE_STRONG_ENCRYPTION)) != 0) {
		throw std::runtime_error(std::format(
			"Archive Security Error: Encrypted archive entry {} is not supported.", entry_number));
	}

	if (!isUnixLikeZipHost(version_made_by)) {
		return;
	}

	const uint32_t mode_type = (external_attributes >> 16) & UNIX_FILE_TYPE_MASK;
	if (mode_type == 0) {
		return;
	}
	if (mode_type == UNIX_SYMLINK) {
		throw std::runtime_error(std::format(
			"Archive Security Error: Symlink archive entry {} is not supported: \"{}\".",
			entry_number, entry_name));
	}
	if (mode_type != UNIX_REGULAR_FILE && mode_type != UNIX_DIRECTORY) {
		throw std::runtime_error(std::format(
			"Archive Security Error: Special archive entry {} is not supported: \"{}\".",
			entry_number, entry_name));
	}
	if (mode_type == UNIX_DIRECTORY && !entry_name.ends_with('/')) {
		throw std::runtime_error(std::format(
			"Archive File Error: Directory metadata does not match archive entry path {}.", entry_number));
	}
	if (mode_type == UNIX_REGULAR_FILE && entry_name.ends_with('/')) {
		throw std::runtime_error(std::format(
			"Archive File Error: File metadata does not match archive entry path {}.", entry_number));
	}
}

void validateEntrySizeMetadata(uint32_t compressed_size, uint32_t uncompressed_size, std::uint64_t& total_uncompressed,
                               bool is_directory, std::size_t entry_number) {
	constexpr std::uint64_t MAX_TOTAL_UNCOMPRESSED_SIZE = 2ULL * 1024 * 1024 * 1024;

	if (compressed_size == UINT32_MAX || uncompressed_size == UINT32_MAX) {
		throw std::runtime_error(std::format(
			"Archive File Error: ZIP64 size metadata is not supported for entry {}.", entry_number));
	}
	if (is_directory) {
		// A directory entry must have no actual content (uncompressed_size == 0).
		// compressed_size may legitimately be non-zero — Java's jar tool deflates
		// directory entries, producing a 2-byte empty-stream marker (0x03 0x00).
		if (uncompressed_size != 0) {
			throw std::runtime_error(std::format(
				"Archive File Error: Directory entry {} has non-zero uncompressed size.", entry_number));
		}
		return;
	}
	if (static_cast<std::uint64_t>(uncompressed_size) > MAX_TOTAL_UNCOMPRESSED_SIZE
		|| total_uncompressed > MAX_TOTAL_UNCOMPRESSED_SIZE - static_cast<std::uint64_t>(uncompressed_size)) {
		throw std::runtime_error("Archive Security Error: Total uncompressed archive size exceeds the safety limit.");
	}
	total_uncompressed += uncompressed_size;
}

void validateEntryPathCollision(std::string_view entry_name, std::unordered_set<std::string>& seen_entries,
                                std::unordered_set<std::string>& file_entries,
                                std::unordered_set<std::string>& directory_entries,
                                std::size_t entry_number) {
	const bool is_directory = entry_name.ends_with('/');
	const std::string key = makePortableEntryKey(entry_name);
	if (key.empty()) {
		throw std::runtime_error(std::format(
			"Archive Security Error: Empty normalized path for archive entry {}.", entry_number));
	}
	if (!seen_entries.insert(key).second) {
		throw std::runtime_error(std::format(
			"Archive Security Error: Duplicate or case-conflicting archive entry path detected: \"{}\".",
			entry_name));
	}

	std::string parent;
	parent.reserve(key.size());
	for (char ch : key) {
		if (ch == '/') {
			if (file_entries.contains(parent)) {
				throw std::runtime_error(std::format(
					"Archive Security Error: Archive entry {} conflicts with file path \"{}\".",
					entry_number, parent));
			}
			directory_entries.insert(parent);
		}
		parent.push_back(ch);
	}

	if (is_directory) {
		if (file_entries.contains(key)) {
			throw std::runtime_error(std::format(
				"Archive Security Error: Directory entry {} conflicts with an existing file path.", entry_number));
		}
		directory_entries.insert(key);
		return;
	}

	if (directory_entries.contains(key)) {
		throw std::runtime_error(std::format(
			"Archive Security Error: File entry {} conflicts with an existing directory path.", entry_number));
	}
	file_entries.insert(key);
}

[[nodiscard]] bool descriptor32Matches(std::span<const Byte> archive_data, std::size_t offset,
                                       uint32_t crc32, uint32_t compressed_size, uint32_t uncompressed_size) {
	return readLe32(archive_data, offset) == crc32
		&& readLe32(archive_data, offset + 4) == compressed_size
		&& readLe32(archive_data, offset + 8) == uncompressed_size;
}

[[nodiscard]] std::size_t readDataDescriptorLength(std::span<const Byte> archive_data, std::size_t descriptor_start,
                                                   std::size_t central_start, uint32_t crc32,
                                                   uint32_t compressed_size, uint32_t uncompressed_size,
                                                   std::size_t entry_number) {
	constexpr std::size_t
		DESCRIPTOR_WITHOUT_SIGNATURE_SIZE = 12,
		DESCRIPTOR_WITH_SIGNATURE_SIZE    = 16;

	if (descriptor_start > central_start) {
		throw std::runtime_error(std::format(
			"Archive File Error: Compressed data for entry {} extends past the central directory.", entry_number));
	}

	const std::size_t available = central_start - descriptor_start;
	if (available >= DESCRIPTOR_WITH_SIGNATURE_SIZE
		&& hasLe32Signature(archive_data, descriptor_start, ZIP_DATA_DESCRIPTOR_SIGNATURE)
		&& descriptor32Matches(archive_data, descriptor_start + 4, crc32, compressed_size, uncompressed_size)) {
		return DESCRIPTOR_WITH_SIGNATURE_SIZE;
	}
	if (available >= DESCRIPTOR_WITHOUT_SIGNATURE_SIZE
		&& descriptor32Matches(archive_data, descriptor_start, crc32, compressed_size, uncompressed_size)) {
		return DESCRIPTOR_WITHOUT_SIGNATURE_SIZE;
	}

	throw std::runtime_error(std::format(
		"Archive File Error: Data descriptor for entry {} is missing or inconsistent.", entry_number));
}

void validateLocalEntryPayload(std::span<const Byte> archive_data, std::size_t local_header_start,
                               std::size_t local_record_end, std::size_t central_start,
                               uint16_t central_flags, uint32_t crc32,
                               uint32_t compressed_size, uint32_t uncompressed_size,
                               std::vector<LocalEntrySpan>& local_spans, std::size_t entry_number) {
	constexpr uint16_t
		GENERAL_PURPOSE_ENCRYPTED         = 1u << 0,
		GENERAL_PURPOSE_DATA_DESCRIPTOR   = 1u << 3,
		GENERAL_PURPOSE_STRONG_ENCRYPTION = 1u << 6;
	constexpr uint16_t FLAGS_THAT_AFFECT_LAYOUT =
		GENERAL_PURPOSE_ENCRYPTED | GENERAL_PURPOSE_DATA_DESCRIPTOR | GENERAL_PURPOSE_STRONG_ENCRYPTION;

	const uint16_t local_flags = readZipField<uint16_t>(archive_data, local_header_start + 6, "Archive File Error");
	if ((local_flags & FLAGS_THAT_AFFECT_LAYOUT) != (central_flags & FLAGS_THAT_AFFECT_LAYOUT)) {
		throw std::runtime_error(std::format(
			"Archive File Error: Local and central ZIP flags differ for entry {}.", entry_number));
	}

	const bool has_data_descriptor = (central_flags & GENERAL_PURPOSE_DATA_DESCRIPTOR) != 0;
	if (!has_data_descriptor) {
		const uint32_t local_crc32 = readZipField<uint32_t>(archive_data, local_header_start + 14, "Archive File Error");
		const uint32_t local_compressed_size = readZipField<uint32_t>(archive_data, local_header_start + 18, "Archive File Error");
		const uint32_t local_uncompressed_size = readZipField<uint32_t>(archive_data, local_header_start + 22, "Archive File Error");
		if (local_crc32 != crc32
			|| local_compressed_size != compressed_size
			|| local_uncompressed_size != uncompressed_size) {
			throw std::runtime_error(std::format(
				"Archive File Error: Local and central size metadata differ for entry {}.", entry_number));
		}
	}

	const std::size_t compressed_end = checkedAdd(
		local_record_end,
		static_cast<std::size_t>(compressed_size),
		"Archive File Error: Local compressed data size overflow.");
	if (compressed_end > central_start) {
		throw std::runtime_error(std::format(
			"Archive File Error: Compressed data for entry {} extends into the central directory.", entry_number));
	}

	std::size_t local_payload_end = compressed_end;
	if (has_data_descriptor) {
		local_payload_end = checkedAdd(
			compressed_end,
			readDataDescriptorLength(
				archive_data,
				compressed_end,
				central_start,
				crc32,
				compressed_size,
				uncompressed_size,
				entry_number),
			"Archive File Error: Local data descriptor size overflow.");
		if (local_payload_end > central_start) {
			throw std::runtime_error(std::format(
				"Archive File Error: Data descriptor for entry {} extends into the central directory.", entry_number));
		}
	}

	local_spans.push_back(LocalEntrySpan{
		.begin = local_header_start,
		.end   = local_payload_end
	});
}

void validateLocalEntrySpans(std::vector<LocalEntrySpan>& local_spans) {
	std::ranges::sort(local_spans, [](const LocalEntrySpan& lhs, const LocalEntrySpan& rhs) {
		return lhs.begin < rhs.begin;
	});

	for (std::size_t i = 1; i < local_spans.size(); ++i) {
		if (local_spans[i].begin < local_spans[i - 1].end) {
			throw std::runtime_error("Archive File Error: Local ZIP entry payloads overlap.");
		}
	}
}

[[nodiscard]] std::size_t centralRecordSize(std::size_t name_length, std::size_t extra_length,
                                            std::size_t comment_length) {
	return checkedAdd(
		CENTRAL_RECORD_MIN_SIZE,
		checkedAdd(
			name_length,
			checkedAdd(
				extra_length,
				comment_length,
				"Archive File Error: Central directory metadata length overflow."),
			"Archive File Error: Central directory metadata length overflow."),
		"Archive File Error: Central directory record size overflow.");
}

[[nodiscard]] CentralDirectoryBounds readCentralDirectoryBounds(std::span<const Byte> archive_data,
                                                                std::size_t eocd_index) {
	const uint16_t disk_number = readZipField<uint16_t>(archive_data, eocd_index + 4, "Archive File Error");
	const uint16_t central_dir_disk = readZipField<uint16_t>(archive_data, eocd_index + 6, "Archive File Error");
	const uint16_t records_on_disk = readZipField<uint16_t>(archive_data, eocd_index + 8, "Archive File Error");
	const uint16_t total_records = readZipField<uint16_t>(archive_data, eocd_index + 10, "Archive File Error");
	const uint32_t central_size = readZipField<uint32_t>(archive_data, eocd_index + 12, "Archive File Error");
	const uint32_t central_offset = readZipField<uint32_t>(archive_data, eocd_index + 16, "Archive File Error");

	if (disk_number != 0 || central_dir_disk != 0 || records_on_disk != total_records) {
		throw std::runtime_error("Archive File Error: Multi-disk ZIP archives are not supported.");
	}
	if (total_records == 0) {
		throw std::runtime_error("Archive File Error: Archive contains no central directory entries.");
	}
	if (total_records == UINT16_MAX || central_size == UINT32_MAX || central_offset == UINT32_MAX) {
		throw std::runtime_error("Archive File Error: ZIP64 archives are not supported.");
	}

	const std::size_t central_start = checkedAdd(
		ZIP_WRAP_PREFIX_SIZE,
		static_cast<std::size_t>(central_offset),
		"Archive File Error: Central directory offset overflow.");
	const std::size_t central_end = checkedAdd(
		central_start,
		static_cast<std::size_t>(central_size),
		"Archive File Error: Central directory size overflow.");

	if (central_start > archive_data.size() || central_end > archive_data.size() || central_end > eocd_index) {
		throw std::runtime_error("Archive File Error: Central directory bounds are invalid.");
	}
	if (central_end != eocd_index) {
		throw std::runtime_error("Archive File Error: Central directory does not end at the EOCD record.");
	}

	return CentralDirectoryBounds{
		.start = central_start,
		.end = central_end,
		.total_records = total_records
	};
}

[[nodiscard]] CentralEntryMetadata readCentralEntryMetadata(std::span<const Byte> archive_data, std::size_t cursor,
                                                            std::size_t entry_number) {
	if (cursor > archive_data.size() || CENTRAL_RECORD_MIN_SIZE > archive_data.size() - cursor) {
		throw std::runtime_error("Archive File Error: Truncated central directory file header.");
	}

	if (!hasLe32Signature(archive_data, cursor, ZIP_CENTRAL_DIRECTORY_SIGNATURE)) {
		throw std::runtime_error("Archive File Error: Invalid central directory file header signature.");
	}

	const uint16_t version_made_by = readZipField<uint16_t>(archive_data, cursor + CENTRAL_VERSION_MADE_BY, "Archive File Error");
	const uint16_t flags = readZipField<uint16_t>(archive_data, cursor + CENTRAL_FLAGS_OFFSET, "Archive File Error");
	const uint32_t crc32 = readZipField<uint32_t>(archive_data, cursor + CENTRAL_CRC32, "Archive File Error");
	const uint32_t compressed_size = readZipField<uint32_t>(archive_data, cursor + CENTRAL_COMPRESSED_SIZE, "Archive File Error");
	const uint32_t uncompressed_size = readZipField<uint32_t>(archive_data, cursor + CENTRAL_UNCOMPRESSED_SIZE, "Archive File Error");
	const std::size_t name_length = readZipField<uint16_t>(archive_data, cursor + CENTRAL_NAME_LENGTH_OFFSET, "Archive File Error");
	const std::size_t extra_length = readZipField<uint16_t>(archive_data, cursor + CENTRAL_EXTRA_LENGTH_OFFSET, "Archive File Error");
	const std::size_t comment_length = readZipField<uint16_t>(archive_data, cursor + CENTRAL_COMMENT_LENGTH_OFFSET, "Archive File Error");
	const uint16_t disk_start = readZipField<uint16_t>(archive_data, cursor + CENTRAL_DISK_START, "Archive File Error");
	const uint32_t external_attributes = readZipField<uint32_t>(archive_data, cursor + CENTRAL_EXTERNAL_ATTRIBUTES, "Archive File Error");
	const std::size_t local_header_offset = readZipField<uint32_t>(archive_data, cursor + CENTRAL_LOCAL_OFFSET, "Archive File Error");

	const std::size_t name_start = checkedAdd(
		cursor,
		CENTRAL_RECORD_NAME_INDEX,
		"Archive File Error: Central directory filename offset overflow.");
	const std::string_view name = readZipStringView(
		archive_data,
		name_start,
		name_length,
		"Archive File Error: Central directory filename length overflow.",
		"Archive File Error: Central directory filename exceeds archive bounds.");

	const std::size_t record_size = centralRecordSize(name_length, extra_length, comment_length);

	return CentralEntryMetadata{
		.entry_number = entry_number,
		.version_made_by = version_made_by,
		.flags = flags,
		.crc32 = crc32,
		.compressed_size = compressed_size,
		.uncompressed_size = uncompressed_size,
		.disk_start = disk_start,
		.external_attributes = external_attributes,
		.local_header_offset = local_header_offset,
		.name = name,
		.record_size = record_size
	};
}

void validateCentralRecordSpan(std::size_t cursor, std::size_t record_size, std::size_t central_end,
                               std::size_t archive_size) {
	if (record_size > archive_size - cursor) {
		throw std::runtime_error("Archive File Error: Central directory entry exceeds archive bounds.");
	}
	if (cursor > central_end || record_size > central_end - cursor) {
		throw std::runtime_error("Archive File Error: Central directory entry exceeds declared directory size.");
	}
}

void validateCentralEntryMetadata(const CentralEntryMetadata& entry, ArchiveEntryTracking& tracking) {
	if (entry.disk_start != 0) {
		throw std::runtime_error(std::format(
			"Archive File Error: Multi-disk local header reference on entry {} is not supported.",
			entry.entry_number));
	}

	validateEntryName(
		entry.name,
		"Archive File Error: Entry",
		"Archive Security Error: Unsafe archive entry path detected",
		entry.entry_number);
	validateEntryAttributes(
		entry.version_made_by,
		entry.flags,
		entry.external_attributes,
		entry.name,
		entry.entry_number);
	validateEntrySizeMetadata(
		entry.compressed_size,
		entry.uncompressed_size,
		tracking.total_uncompressed,
		entry.name.ends_with('/'),
		entry.entry_number);
	validateEntryPathCollision(
		entry.name,
		tracking.seen_entries,
		tracking.file_entries,
		tracking.directory_entries,
		entry.entry_number);
}

void validateLocalEntryForCentralEntry(std::span<const Byte> archive_data, std::size_t central_start,
                                       const CentralEntryMetadata& entry, std::vector<LocalEntrySpan>& local_spans) {
	const std::size_t local_header_start = checkedAdd(
		ZIP_WRAP_PREFIX_SIZE,
		entry.local_header_offset,
		"Archive File Error: Local file header offset overflow.");
	if (local_header_start >= central_start) {
		throw std::runtime_error("Archive File Error: Local file header points inside the central directory.");
	}
	if (local_header_start > archive_data.size()
		|| LOCAL_RECORD_MIN_SIZE > archive_data.size() - local_header_start) {
		throw std::runtime_error("Archive File Error: Truncated local file header.");
	}
	if (!hasLe32Signature(archive_data, local_header_start, ZIP_LOCAL_FILE_HEADER_SIGNATURE)) {
		throw std::runtime_error(std::format(
			"Archive File Error: Invalid local file header signature for entry {}.", entry.entry_number));
	}

	const std::size_t local_name_length = readZipField<uint16_t>(archive_data, local_header_start + 26, "Archive File Error");
	const std::size_t local_extra_length = readZipField<uint16_t>(archive_data, local_header_start + 28, "Archive File Error");
	const std::size_t local_name_start = checkedAdd(
		local_header_start,
		LOCAL_RECORD_NAME_INDEX,
		"Archive File Error: Local filename offset overflow.");
	const std::size_t local_record_end = checkedAdd(
		checkedAdd(
			local_name_start,
			local_name_length,
			"Archive File Error: Local filename length overflow."),
		local_extra_length,
		"Archive File Error: Local header extra-field length overflow.");
	if (local_record_end > archive_data.size()) {
		throw std::runtime_error("Archive File Error: Local file header exceeds archive bounds.");
	}

	const std::string_view local_entry_name = readZipStringView(
		archive_data,
		local_name_start,
		local_name_length,
		"Archive File Error: Local filename length overflow.",
		"Archive File Error: Local file header exceeds archive bounds.");
	validateEntryName(
		local_entry_name,
		"Archive File Error: Local entry",
		"Archive Security Error: Unsafe local archive entry path detected",
		entry.entry_number);
	if (local_entry_name != entry.name) {
		throw std::runtime_error(std::format(
			"Archive Security Error: Local and central directory names differ for entry {}.", entry.entry_number));
	}

	validateLocalEntryPayload(
		archive_data,
		local_header_start,
		local_record_end,
		central_start,
		entry.flags,
		entry.crc32,
		entry.compressed_size,
		entry.uncompressed_size,
		local_spans,
		entry.entry_number);
}

[[nodiscard]] std::string parseFirstZipFilename(std::span<const Byte> archive_data) {
	constexpr std::size_t
		ZIP_LOCAL_HEADER_INDEX    = 8,
		ZIP_LOCAL_HEADER_MIN_SIZE = 30,
		FILENAME_LENGTH_INDEX     = ZIP_LOCAL_HEADER_INDEX + 26,
		EXTRA_LENGTH_INDEX        = ZIP_LOCAL_HEADER_INDEX + 28,
		FILENAME_INDEX            = ZIP_LOCAL_HEADER_INDEX + 30,
		FIRST_FILENAME_MIN_LENGTH = 4;

	if (archive_data.size() < ZIP_LOCAL_HEADER_INDEX + ZIP_LOCAL_HEADER_MIN_SIZE) {
		throw std::runtime_error("Archive File Error: ZIP header is truncated.");
	}

	if (!hasLe32Signature(archive_data, ZIP_LOCAL_HEADER_INDEX, ZIP_LOCAL_FILE_HEADER_SIGNATURE)) {
		throw std::runtime_error("Archive File Error: Missing ZIP local file header signature.");
	}

	const std::size_t filename_length = readZipField<uint16_t>(archive_data, FILENAME_LENGTH_INDEX, "Archive File Error");
	const std::size_t extra_length    = readZipField<uint16_t>(archive_data, EXTRA_LENGTH_INDEX, "Archive File Error");

	if (filename_length < FIRST_FILENAME_MIN_LENGTH) {
		throw std::runtime_error(
			"File Error:\n\nName length of first file within archive is too short.\n"
			"Increase length (minimum 4 characters). Make sure it has a valid extension.");
	}

	const std::string filename(readZipStringView(
		archive_data,
		FILENAME_INDEX,
		filename_length,
		"Archive File Error: First filename length overflow.",
		"Archive File Error: First filename extends past archive bounds."));

	// Extra field bounds validation helps catch malformed local headers early.
	if (checkedAdd(
			FILENAME_INDEX + filename_length,
			extra_length,
			"Archive File Error: First ZIP header extra field length overflow.") > archive_data.size()) {
		throw std::runtime_error("Archive File Error: First ZIP header extra field extends past archive bounds.");
	}

	validateEntryName(
		filename,
		"Archive File Error: First filename contains unsupported control characters.",
		"Archive Security Error: Unsafe first archive entry path detected");

	return filename;
}

[[nodiscard]] bool isUnsafeEntryPath(std::string_view path) {
	if (path.empty()) {
		return true;
	}
	if (path.find('\\') != std::string::npos) {
		return true;
	}
	if (path[0] == '/') {
		return true;
	}
	if (path.size() >= 2 && std::isalpha(static_cast<unsigned char>(path[0])) && path[1] == ':') {
		return true;
	}

	std::size_t segment_start = 0;
	for (std::size_t i = 0; i <= path.size(); ++i) {
		if (i < path.size() && path[i] != '/') {
			continue;
		}
		const std::string_view segment = path.substr(segment_start, i - segment_start);
		const bool trailing_directory_separator = (i == path.size() && segment.empty());
		if (!trailing_directory_separator
			&& (segment.empty()
				|| segment == "."sv
				|| segment == ".."sv
				|| segment.back() == '.'
				|| segment.back() == ' '
				|| hasWindowsInvalidPathCharacter(segment)
				|| hasWindowsReservedSegmentName(segment))) {
			return true;
		}
		segment_start = i + 1;
	}

	return false;
}

[[nodiscard]] std::size_t findEndOfCentralDirectory(std::span<const Byte> archive_data) {
	constexpr std::size_t EOCD_MIN_SIZE = 22;

	if (archive_data.size() < ZIP_WRAP_PREFIX_SIZE + ZIP_WRAP_TRAILER_SIZE + EOCD_MIN_SIZE) {
		throw std::runtime_error("Archive File Error: Archive is too small.");
	}

	if (const auto eocd = findZipEocdLocator(archive_data, ZIP_WRAP_PREFIX_SIZE, archive_data.size() - ZIP_WRAP_TRAILER_SIZE)) {
		return eocd->index;
	}

	throw std::runtime_error("Archive File Error: End of central directory record not found.");
}

} // anonymous namespace

ArchiveMetadata analyzeArchive(std::span<const Byte> archive_data, bool is_zip_file) {
	ArchiveMetadata metadata{ .file_type = FileType::UNKNOWN_FILE_TYPE, .first_filename = parseFirstZipFilename(archive_data) };
	const std::string_view filename = metadata.first_filename;

	if (!is_zip_file) {
		if (filename != "META-INF/MANIFEST.MF" && filename != "META-INF/") {
			throw std::runtime_error("File Type Error: Archive does not appear to be a valid JAR file.");
		}
		metadata.file_type = FileType::JAR;
		return metadata;
	}

	// --- ZIP path: inspect the first record's filename/extension ---

	const bool is_folder = filename.back() == '/';
	const std::size_t dot_pos = filename.rfind('.');

	// Check for folders (entries ending with '/').
	if (dot_pos == std::string_view::npos) {
		metadata.file_type = is_folder ? FileType::FOLDER : FileType::LINUX_EXECUTABLE;
		return metadata;
	}

	// A name with a dot could still be a folder if it ends with '/'.
	if (is_folder) {
		if (filename[filename.size() - 2] == '.') {
			throw std::runtime_error("ZIP File Error: Invalid folder name within ZIP archive.");
		}
		metadata.file_type = FileType::FOLDER;
		return metadata;
	}

	// Match extension against the known list.
	const std::string_view extension = filename.substr(dot_pos + 1);
	for (std::size_t i = 0; i < EXTENSION_LIST.size(); ++i) {
		if (std::ranges::equal(EXTENSION_LIST[i], extension, [](char lhs, char rhs) {
				return lhs == toLowerAscii(rhs);
			})) {
			// Indices 0..28 all map to VIDEO_AUDIO; 29+ map 1:1 with the enum.
			metadata.file_type = static_cast<FileType>(std::max(i, static_cast<std::size_t>(FileType::VIDEO_AUDIO)));
			return metadata;
		}
	}

	return metadata;
}

void validateArchiveEntryPaths(std::span<const Byte> archive_data) {
	const std::size_t eocd_index = findEndOfCentralDirectory(archive_data);
	const CentralDirectoryBounds central_directory = readCentralDirectoryBounds(archive_data, eocd_index);

	std::size_t cursor = central_directory.start;
	ArchiveEntryTracking tracking;
	tracking.reserve(central_directory.total_records);

	for (uint16_t i = 0; i < central_directory.total_records; ++i) {
		const CentralEntryMetadata entry = readCentralEntryMetadata(
			archive_data,
			cursor,
			i + 1);

		validateCentralEntryMetadata(entry, tracking);
		validateLocalEntryForCentralEntry(
			archive_data,
			central_directory.start,
			entry,
			tracking.local_spans);
		validateCentralRecordSpan(cursor, entry.record_size, central_directory.end, archive_data.size());

		cursor = checkedAdd(
			cursor,
			entry.record_size,
			"Archive File Error: Central directory cursor overflow.");
	}

	if (cursor != central_directory.end) {
		throw std::runtime_error("Archive File Error: Central directory size does not match parsed records.");
	}
	validateLocalEntrySpans(tracking.local_spans);
}


================================================
FILE: src/binary_utils.cpp
================================================
#include "pdvzip.h"

namespace binary_utils_detail {

[[noreturn]] void throwOutOfRange(std::string_view fn_name) {
	throw std::out_of_range(std::format("{}: index out of bounds", fn_name));
}

} // namespace binary_utils_detail

namespace {

[[nodiscard]] bool isSupportedFieldLength(std::size_t length) {
	return length == 2 || length == 4;
}

void validateFieldBounds(std::size_t data_size, std::size_t offset, std::size_t length, std::string_view fn_name) {
	if (!isSupportedFieldLength(length)) {
		throw std::invalid_argument(std::format("{}: unsupported length {}", fn_name, length));
	}
	if (offset > data_size || length > (data_size - offset)) {
		throw std::out_of_range(std::format("{}: index out of bounds", fn_name));
	}
}

[[nodiscard]] std::size_t maxFieldValue(std::size_t length) {
	return (length == 2)
		? static_cast<std::size_t>(UINT16_MAX)
		: static_cast<std::size_t>(UINT32_MAX);
}

[[nodiscard]] std::size_t fieldByteShift(std::size_t length, std::size_t index, std::endian byte_order) {
	return (byte_order == std::endian::big)
		? ((length - 1 - index) * 8)
		: (index * 8);
}

} // anonymous namespace

void writeValueAt(
	std::span<Byte> data,
	std::size_t offset,
	std::size_t value,
	std::size_t length,
	std::endian byte_order) {

	validateFieldBounds(data.size(), offset, length, "writeValueAt");

	if (value > maxFieldValue(length)) {
		throw std::out_of_range(std::format("writeValueAt: value {} exceeds {}-byte field", value, length));
	}

	for (std::size_t i = 0; i < length; ++i) {
		data[offset + i] = static_cast<Byte>((value >> fieldByteShift(length, i, byte_order)) & 0xFF);
	}
}

std::size_t readValueAt(
	std::span<const Byte> data,
	std::size_t offset,
	std::size_t length,
	std::endian byte_order) {

	validateFieldBounds(data.size(), offset, length, "readValueAt");

	std::size_t value = 0;
	for (std::size_t i = 0; i < length; ++i) {
		value |= static_cast<std::size_t>(data[offset + i]) << fieldByteShift(length, i, byte_order);
	}
	return value;
}

std::size_t checkedAdd(std::size_t lhs, std::size_t rhs, std::string_view context) {
	if (lhs > std::numeric_limits<std::size_t>::max() - rhs) {
		throw std::runtime_error(std::string(context));
	}
	return lhs + rhs;
}

std::size_t checkedMultiply(std::size_t lhs, std::size_t rhs, std::string_view context) {
	if (lhs != 0 && rhs > std::numeric_limits<std::size_t>::max() / lhs) {
		throw std::runtime_error(std::string(context));
	}
	return lhs * rhs;
}

std::optional<ZipEocdLocator> findZipEocdLocator(
	std::span<const Byte> data,
	std::size_t archive_begin,
	std::size_t archive_end) {
	constexpr std::size_t
		EOCD_MIN_SIZE = 22,
		EOCD_SIGNATURE_SIZE = 4,
		EOCD_COMMENT_LENGTH_OFFSET = 20,
		MAX_EOCD_SEARCH_DISTANCE = EOCD_MIN_SIZE + static_cast<std::size_t>(UINT16_MAX);

	if (archive_end > data.size() || archive_end < archive_begin
		|| archive_end - archive_begin < EOCD_MIN_SIZE) {
		return std::nullopt;
	}

	const std::size_t distance_floor = (archive_end > MAX_EOCD_SEARCH_DISTANCE)
		? archive_end - MAX_EOCD_SEARCH_DISTANCE
		: std::size_t{0};
	const std::size_t search_floor = std::max(archive_begin, distance_floor);
	const std::size_t search_start = archive_end - EOCD_SIGNATURE_SIZE;

	if (search_start < search_floor) {
		return std::nullopt;
	}

	for (std::size_t pos = search_start; ; --pos) {
		if (hasLe32Signature(data, pos, ZIP_END_CENTRAL_DIRECTORY_SIGNATURE)
			&& pos + EOCD_MIN_SIZE <= archive_end) {
			const uint16_t comment_length = readLe16(data, pos + EOCD_COMMENT_LENGTH_OFFSET);
			if (comment_length == archive_end - pos - EOCD_MIN_SIZE) {
				return ZipEocdLocator{ .index = pos, .comment_length = comment_length };
			}
		}

		if (pos == search_floor) {
			break;
		}
	}

	return std::nullopt;
}


================================================
FILE: src/compile_pdvzip.sh
================================================
#!/bin/bash

# compile_pdvzip.sh — Build script for pdvzip (multi-file layout)

set -euo pipefail

CXX="${CXX:-g++}"
CXXFLAGS="-std=c++23 -O3 -mtune=native -pipe -Wall -Wextra -Wpedantic -Wshadow -DNDEBUG -D_FORTIFY_SOURCE=3 -DLODEPNG_NO_COMPILE_DISK -DLODEPNG_NO_COMPILE_ANCILLARY_CHUNKS -s -flto=auto -fuse-linker-plugin -fstack-protector-strong -fPIE"
LDFLAGS="-pie -Wl,-z,relro,-z,now"
TARGET="pdvzip"
SRCDIR="."

SOURCES=(
    "$SRCDIR/main.cpp"
    "$SRCDIR/display_info.cpp"
    "$SRCDIR/program_args.cpp"
    "$SRCDIR/file_io.cpp"
    "$SRCDIR/binary_utils.cpp"
    "$SRCDIR/image_processing.cpp"
    "$SRCDIR/image_resize.cpp"
    "$SRCDIR/archive_analysis.cpp"
    "$SRCDIR/user_input.cpp"
    "$SRCDIR/script_builder.cpp"
    "$SRCDIR/script_text_builder.cpp"
    "$SRCDIR/polyglot_assembly.cpp"
    "$SRCDIR/lodepng/lodepng.cpp"
)

echo "Compiling $TARGET..."
$CXX $CXXFLAGS "${SOURCES[@]}" $LDFLAGS -o "$TARGET"
echo "Compilation successful. Executable '$TARGET' created."


================================================
FILE: src/display_info.cpp
================================================
#include "pdvzip.h"

void displayInfo() {
	std::print(R"(
PNG Data Vehicle ZIP/JAR Edition (PDVZIP v4.5).
Created by Nicholas Cleasby (@CleasbyCode) 6/08/2022.

Use PDVZIP to embed a ZIP/JAR file within a PNG image,
to create a tweetable and "executable" PNG-ZIP/JAR polyglot file.

The supported hosting sites will retain the embedded archive within the PNG image.

PNG image size limits are platform dependant:

X/Twitter (5MB), Flickr (200MB), Imgbb (32MB), PostImage (32MB), ImgPile (8MB).

Once the ZIP file has been embedded within a PNG image, it can be shared on your chosen
hosting site or 'executed' whenever you want to access the embedded file(s).

pdvzip (Linux) will attempt to automatically set executable permissions on newly created polyglot image files.
You will need to manually set executable permissions using chmod on these polyglot images downloaded from hosting sites.

From a Linux terminal: ./pzip_image.png (chmod +x pzip_image.png, if required).
From a Windows terminal: First, rename the '.png' file extension to '.cmd', then .\pzip_image.cmd

For common video/audio files, Linux uses the media player vlc or mpv. Windows uses the set default media player.
PDF, Linux uses either evince or firefox. Windows uses the set default PDF viewer.
Python, Linux & Windows use python3 to run these programs.
PowerShell, Linux uses pwsh command (if PowerShell installed).
Depending on the installed version of PowerShell, Windows uses either pwsh.exe or powershell.exe, to run these scripts.
Folder, Linux uses xdg-open, Windows uses powershell.exe with II (Invoke-Item) command, to open zipped folders.

For any other media type/file extension, Linux & Windows will rely on the operating system's method or set default application for those files.

PNG Image Requirements for Arbitrary Data Preservation

PNG file size (image + embedded content) must not exceed the hosting site's size limits.
The site will either refuse to upload your image or it will convert your image to jpg, such as X/Twitter.

Dimensions:

The following dimension size limits are specific to pdvzip and not necessarily the exact hosting site's size limits.

PNG-32/24 (Truecolor)

Image dimensions can be set between a minimum of 68x68 and a maximum of 900x900.
These dimension size limits are for compatibility reasons, allowing it to work with all the above listed platforms.

Note: Images that are created & saved within your image editor as PNG-32/24 that are either
black & white/grayscale, images with 256 colours or less, will be converted by X/Twitter to
PNG-8 and you will lose the embedded content. If you want to use a simple "single" colour PNG-32/24 image,
then fill an area with a gradient colour instead of a single solid colour.
X/Twitter should then keep the image as PNG-32/24.

PNG-8 (Indexed-colour)

Image dimensions can be set between a minimum of 68x68 and a maximum of 4096x4096.

PNG Chunks:

For example, with X/Twitter, you can overfill the following PNG chunks with arbitrary data,
in which the platform will preserve as long as you keep within the image dimension & file size limits.

Other platforms may differ in what chunks they preserve and which you can overfill.

bKGD, cHRM, gAMA, hIST,
iCCP, (Only 10KB max. with X/Twitter).
IDAT, (Use as last IDAT chunk, after the final image IDAT chunk).
PLTE, (Use only with PNG-32 & PNG-24 for arbitrary data).
pHYs, sBIT, sPLT, sRGB,
tRNS. (PNG-32 only).

This program uses the iCCP (extraction script) and IDAT (zip file) chunk names for storing arbitrary data.

ZIP File Size & Other Information

To work out the maximum ZIP file size, start with the hosting site's size limit,
minus your PNG image size, minus 1500 bytes (extraction script size).

X/Twitter example: (5MB Image Limit) 5,242,880 - (image size 307,200 + extraction script size 1500) = 4,934,180 bytes available for your ZIP file.

Make sure ZIP file is a standard ZIP archive, compatible with Linux unzip & Windows Explorer.
Do not include other .zip files within the main ZIP archive. (.rar files are ok).
Do not include other pdvzip created PNG image files within the main ZIP archive, as they are essentially .zip files.
Use file extensions for your media file within the ZIP archive: my_doc.pdf, my_video.mp4, my_program.py, etc.
A file without an extension will be treated as a Linux executable.
Paint.net application is recommended for easily creating compatible PNG image files.

	)");
}


================================================
FILE: src/file_io.cpp
================================================
#include "pdvzip.h"

namespace {

[[nodiscard]] bool equalsIgnoreAsciiCase(std::string_view lhs, std::string_view rhs) {
	return std::ranges::equal(lhs, rhs, [](unsigned char a, unsigned char b) {
		return std::tolower(a) == std::tolower(b);
	});
}

} // anonymous namespace

bool hasValidFilename(const fs::path& p) {
	const auto filename = p.filename().string();
	return !filename.empty() && std::ranges::none_of(filename, [](unsigned char c) {
		return std::iscntrl(c) != 0;
	});
}

bool hasFileExtension(const fs::path& p, std::initializer_list<std::string_view> exts) {
	const std::string extension = p.extension().string();
	return std::ranges::any_of(exts, [&extension](std::string_view ext) {
		return equalsIgnoreAsciiCase(extension, ext);
	});
}

namespace {

struct ScopedFd {
	int fd{-1};

	explicit ScopedFd(int file_descriptor) noexcept : fd(file_descriptor) {}
	~ScopedFd() {
		if (fd >= 0) {
			::close(fd);
		}
	}

	ScopedFd(const ScopedFd&) = delete;
	ScopedFd& operator=(const ScopedFd&) = delete;

	ScopedFd(ScopedFd&& other) noexcept : fd(other.fd) {
		other.fd = -1;
	}
	ScopedFd& operator=(ScopedFd&& other) noexcept {
		if (this != &other) {
			if (fd >= 0) {
				::close(fd);
			}
			fd = other.fd;
			other.fd = -1;
		}
		return *this;
	}

	[[nodiscard]] int get() const noexcept { return fd; }
};

[[nodiscard]] ScopedFd openFileForReadOrThrow(const fs::path& path) {
	int flags = O_RDONLY | O_CLOEXEC;
#ifdef O_NOFOLLOW
	flags |= O_NOFOLLOW;
#endif
	ScopedFd handle(::open(path.c_str(), flags));
	if (handle.get() < 0) {
		const std::error_code ec(errno, std::generic_category());
		throw std::runtime_error(std::format(
			"Failed to open file: {} ({})", path.string(), ec.message()));
	}
	return handle;
}

[[nodiscard]] std::size_t fdFileSizeChecked(int fd, const fs::path& path) {
	struct stat st{};
	if (::fstat(fd, &st) != 0) {
		const std::error_code ec(errno, std::generic_category());
		throw std::runtime_error(std::format(
			"Error: Failed to stat \"{}\": {}", path.string(), ec.message()));
	}
	if (!S_ISREG(st.st_mode)) {
		throw std::runtime_error(std::format(
			"Error: File \"{}\" not found or not a regular file.", path.string()));
	}
	if (st.st_size < 0) {
		throw std::runtime_error(std::format(
			"Error: Negative file size reported for \"{}\".", path.string()));
	}

	const auto raw_file_size = static_cast<std::uintmax_t>(st.st_size);
	if (raw_file_size > std::numeric_limits<std::size_t>::max()) {
		throw std::runtime_error("Error: File is too large to process on this platform.");
	}
	const std::size_t file_size = static_cast<std::size_t>(raw_file_size);
	if (!file_size) {
		throw std::runtime_error("Error: File is empty.");
	}
	return file_size;
}

void validateCoverImageConstraints(const fs::path& path, std::size_t file_size) {
	// Smallest representable PNG: 8-byte signature + 25-byte IHDR + 12-byte IEND
	// + a minimal IDAT chunk; this is the practical floor below which the file
	// cannot be a valid PNG.
	constexpr std::size_t MIN_PNG_SIZE         = 87;
	constexpr std::size_t MAX_COVER_IMAGE_SIZE = 4 * 1024 * 1024;

	if (!hasFileExtension(path, {".png"})) {
		throw std::runtime_error("Image File Error: Invalid image extension. Only expecting \".png\".");
	}
	if (file_size < MIN_PNG_SIZE) {
		throw std::runtime_error("Image File Error: Cover image too small. Not a valid PNG.");
	}
	if (file_size > MAX_COVER_IMAGE_SIZE) {
		throw std::runtime_error("Image File Error: Cover image exceeds the 4MB size limit.");
	}
}

void validateArchiveConstraints(const fs::path& path, std::size_t file_size) {
	// PNG chunk lengths are 31-bit per the PNG specification. Keep the ZIP
	// payload within the range this program can emit as the final IDAT chunk.
	constexpr std::size_t MAX_ARCHIVE_SIZE = static_cast<std::size_t>(std::numeric_limits<std::int32_t>::max());

	if (!hasFileExtension(path, {".zip", ".jar"})) {
		throw std::runtime_error("Archive File Error: Invalid file extension. Only expecting \".zip\" or \".jar\".");
	}
	if (file_size < 30) {
		throw std::runtime_error("Archive File Error: Invalid file size.");
	}
	if (file_size > MAX_ARCHIVE_SIZE) {
		throw std::runtime_error("Archive File Error: File exceeds maximum size limit.");
	}
}

void validateTypeSpecificConstraints(const fs::path& path, std::size_t file_size, FileTypeCheck check_type) {
	if (check_type == FileTypeCheck::cover_image) {
		validateCoverImageConstraints(path, file_size);
	} else if (check_type == FileTypeCheck::archive_file) {
		validateArchiveConstraints(path, file_size);
	}
}

[[nodiscard]] vBytes makeReadBuffer(std::size_t file_size, bool wrap_archive) {
	const std::size_t prefix_size = wrap_archive ? 8 : 0;
	const std::size_t buffer_size = wrap_archive
		? checkedAdd(file_size, CHUNK_FIELDS_COMBINED_LENGTH, "Archive File Error: Wrapped archive size overflow.")
		: file_size;

	vBytes vec(buffer_size, 0);
	if (wrap_archive) {
		constexpr auto IDAT_MARKER_BYTES = std::to_array<Byte>({ 0x00, 0x00, 0x00, 0x00, 0x49, 0x44, 0x41, 0x54 });
		std::copy(IDAT_MARKER_BYTES.begin(), IDAT_MARKER_BYTES.end(), vec.begin());
	}

	if (prefix_size > vec.size()) {
		throw std::runtime_error("Archive File Error: Wrapped archive prefix overflow.");
	}
	return vec;
}

void readFileContents(int fd, const fs::path& path, vBytes& vec, std::size_t file_size, bool wrap_archive) {
	const std::size_t prefix_size = wrap_archive ? 8 : 0;
	std::size_t total_read = 0;
	while (total_read < file_size) {
		const std::size_t remaining = file_size - total_read;
		const std::size_t chunk = std::min<std::size_t>(
			remaining,
			static_cast<std::size_t>(std::numeric_limits<ssize_t>::max()));
		const ssize_t rc = ::read(fd, vec.data() + prefix_size + total_read, chunk);
		if (rc < 0) {
			if (errno == EINTR) {
				continue;
			}
			const std::error_code ec(errno, std::generic_category());
			throw std::runtime_error(std::format(
				"Failed to read file: {} ({})", path.string(), ec.message()));
		}
		if (rc == 0) {
			throw std::runtime_error("Failed to read full file: partial read");
		}
		total_read += static_cast<std::size_t>(rc);
	}
}

void validateWrappedArchiveSignature(const vBytes& vec, bool wrap_archive) {
	if (wrap_archive && !hasLe32Signature(vec, 8, ZIP_LOCAL_FILE_HEADER_SIGNATURE)) {
		throw std::runtime_error("Archive File Error: Signature check failure. Not a valid archive file.");
	}
}

} // anonymous namespace

vBytes readFile(const fs::path& path, FileTypeCheck check_type) {
	if (!hasValidFilename(path)) {
		throw std::runtime_error("Invalid Input Error: Filename contains unsupported control characters.");
	}

	// Open the file first, then validate via fstat on the resulting fd. This
	// avoids a TOCTOU race where a stat-then-open pair could observe a
	// different file than the one ultimately read.
	const ScopedFd handle = openFileForReadOrThrow(path);
	const std::size_t file_size = fdFileSizeChecked(handle.get(), path);
	validateTypeSpecificConstraints(path, file_size, check_type);

	const bool wrap_archive = (check_type == FileTypeCheck::archive_file);
	vBytes vec = makeReadBuffer(file_size, wrap_archive);
	readFileContents(handle.get(), path, vec, file_size, wrap_archive);
	validateWrappedArchiveSignature(vec, wrap_archive);

	return vec;
}

void writePolyglotFile(const vBytes& image_vec, bool is_zip_file) {
	if (image_vec.size() > static_cast<std::size_t>(std::numeric_limits<std::streamsize>::max())) {
		throw std::runtime_error("Write File Error: Output exceeds maximum writable size.");
	}

	std::random_device rd;
	std::mt19937 gen(rd());
	std::uniform_int_distribution<> dist(10000, 99999);

	const std::string_view prefix = is_zip_file ? "pzip_" : "pjar_";

	constexpr std::size_t MAX_NAME_ATTEMPTS = 256;
	std::string filename;
	std::ofstream ofs;

	for (std::size_t i = 0; i < MAX_NAME_ATTEMPTS; ++i) {
		filename = std::format("{}{}.png", prefix, dist(gen));

		// noreplace (C++23) atomically fails if the file already exists,
		// eliminating the TOCTOU race between exists() and open().
		ofs.open(filename, std::ios::binary | std::ios::out | std::ios::noreplace);
		if (ofs) {
			break;
		}
		ofs.clear();
	}

	if (!ofs) {
		throw std::runtime_error("Write File Error: Unable to create a unique output file.");
	}

	ofs.write(reinterpret_cast<const char*>(image_vec.data()),
		static_cast<std::streamsize>(image_vec.size()));
	if (!ofs) {
		throw std::runtime_error("Write File Error: Failed while writing output file.");
	}
	ofs.close();
	if (!ofs) {
		throw std::runtime_error("Write File Error: Failed while finalizing output file.");
	}

	// Fsync the output to disk so the polyglot survives a power loss between
	// here and the kernel's writeback. The fd is reopened just for the sync —
	// std::ofstream doesn't expose its underlying descriptor portably.
	{
		ScopedFd sync_fd(::open(filename.c_str(), O_RDONLY | O_CLOEXEC));
		if (sync_fd.get() >= 0) {
			::fsync(sync_fd.get());
		}
	}

	std::print("\nCreated {} polyglot image file: {} ({} bytes).\n\nComplete!\n\n",
		is_zip_file ? "PNG-ZIP" : "PNG-JAR", filename, image_vec.size());

	std::error_code ec;
	fs::permissions(
		filename,
		fs::perms::owner_all | fs::perms::group_read | fs::perms::group_exec
			| fs::perms::others_read | fs::perms::others_exec,
		ec);
	if (ec) {
		std::println(stderr,
			"\nWarning: Could not set executable permissions for {}.\n"
			"You may need to do this manually with: chmod +x \"{}\"",
			filename, filename);
	}
}


================================================
FILE: src/image_processing.cpp
================================================
#include "image_processing_internal.h"

using image_processing_internal::copyPalette;
using image_processing_internal::resizeImage;
using image_processing_internal::throwLodepngError;

namespace {

constexpr std::size_t RGBA_COMPONENTS = image_processing_internal::RGBA_COMPONENTS;

[[nodiscard]] constexpr std::uint32_t pngChunkType(char a, char b, char c, char d) {
	return (static_cast<std::uint32_t>(static_cast<Byte>(a)) << 24)
		| (static_cast<std::uint32_t>(static_cast<Byte>(b)) << 16)
		| (static_cast<std::uint32_t>(static_cast<Byte>(c)) << 8)
		| static_cast<std::uint32_t>(static_cast<Byte>(d));
}

[[nodiscard]] constexpr std::uint32_t packRgbaKey(Byte red, Byte green, Byte blue, Byte alpha) {
	return (static_cast<std::uint32_t>(red) << 24) | (static_cast<std::uint32_t>(green) << 16)
		| (static_cast<std::uint32_t>(blue) << 8) | static_cast<std::uint32_t>(alpha);
}

struct PngIhdr {
	std::size_t width;
	std::size_t height;
	Byte bit_depth;
	Byte color_type;
};

constexpr uint16_t
	MIN_RGB_COLORS          = 257,
	MIN_SAFE_DIMENSION      = 68,
	MAX_PLTE_DIMENSION      = 4096,
	MAX_RGB_DIMENSION       = 900,
	MAX_RESIZE_ITERATIONS   = 200;

constexpr std::size_t
	IHDR_WIDTH_START = 0x10,
	IHDR_HEIGHT_END  = 0x18,  // Exclusive: covers width (0x10..0x13) + height (0x14..0x17).
	IHDR_CRC_START   = 0x1D,
	IHDR_CRC_END     = 0x21;  // Exclusive.

class PaletteIndexTable {
	static constexpr std::size_t TABLE_SIZE = 512;
	static constexpr std::size_t TABLE_MASK = TABLE_SIZE - 1;

	std::array<std::uint32_t, TABLE_SIZE> keys_{};
	std::array<Byte, TABLE_SIZE> values_{};
	std::array<bool, TABLE_SIZE> occupied_{};

	[[nodiscard]] static constexpr std::size_t hash(std::uint32_t key) {
		key ^= key >> 16;
		key *= 0x7feb352dU;
		key ^= key >> 15;
		key *= 0x846ca68bU;
		key ^= key >> 16;
		return static_cast<std::size_t>(key) & TABLE_MASK;
	}

public:
	void insertIfAbsent(std::uint32_t key, Byte value) {
		std::size_t slot = hash(key);
		for (std::size_t probe = 0; probe < TABLE_SIZE; ++probe) {
			if (!occupied_[slot]) {
				occupied_[slot] = true;
				keys_[slot] = key;
				values_[slot] = value;
				return;
			}
			if (keys_[slot] == key) {
				return;
			}
			slot = (slot + 1) & TABLE_MASK;
		}
		throw std::runtime_error("convertToPalette: Palette lookup table is full.");
	}

	[[nodiscard]] bool find(std::uint32_t key, Byte& value) const {
		std::size_t slot = hash(key);
		for (std::size_t probe = 0; probe < TABLE_SIZE; ++probe) {
			if (!occupied_[slot]) {
				return false;
			}
			if (keys_[slot] == key) {
				value = values_[slot];
				return true;
			}
			slot = (slot + 1) & TABLE_MASK;
		}
		return false;
	}
};

[[nodiscard]] PngIhdr readPngIhdr(std::span<const Byte> png_data) {
	constexpr auto PNG_SIG = std::to_array<Byte>({
		0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A
	});

	constexpr std::size_t
		MIN_IHDR_TOTAL_SIZE = 33,
		IHDR_LENGTH_INDEX   = 8,
		IHDR_NAME_INDEX     = 12,
		WIDTH_INDEX         = 16,
		HEIGHT_INDEX        = 20,
		BIT_DEPTH_INDEX     = 24,
		COLOR_TYPE_INDEX    = 25;

	if (png_data.size() < MIN_IHDR_TOTAL_SIZE) {
		throw std::runtime_error("PNG Error: File too small to contain a valid IHDR chunk.");
	}

	if (!std::equal(PNG_SIG.begin(), PNG_SIG.end(), png_data.begin())) {
		throw std::runtime_error("PNG Error: Invalid signature.");
	}

	if (readValueAt(png_data, IHDR_NAME_INDEX, 4) != pngChunkType('I', 'H', 'D', 'R')) {
		throw std::runtime_error("PNG Error: First chunk is not IHDR.");
	}
	if (readValueAt(png_data, IHDR_LENGTH_INDEX, 4) != 13) {
		throw std::runtime_error("PNG Error: IHDR chunk length is invalid.");
	}

	return PngIhdr{
		.width      = readValueAt(png_data, WIDTH_INDEX, 4),
		.height     = readValueAt(png_data, HEIGHT_INDEX, 4),
		.bit_depth  = png_data[BIT_DEPTH_INDEX],
		.color_type = png_data[COLOR_TYPE_INDEX]
	};
}

void validateInputPngForDecode(const PngIhdr& ihdr) {
	constexpr std::size_t
		MIN_DIMENSION = 68,
		MAX_DIMENSION = 4096;

	const bool supported_color_type =
		ihdr.color_type == INDEXED_PLTE
		|| ihdr.color_type == TRUECOLOR_RGB
		|| ihdr.color_type == TRUECOLOR_RGBA;
	if (!supported_color_type) {
		throw std::runtime_error("Image File Error: Unsupported PNG color type.");
	}

	const bool supported_bit_depth =
		(ihdr.color_type == INDEXED_PLTE && (ihdr.bit_depth == 1 || ihdr.bit_depth == 2 || ihdr.bit_depth == 4 || ihdr.bit_depth == 8))
		|| ((ihdr.color_type == TRUECOLOR_RGB || ihdr.color_type == TRUECOLOR_RGBA) && ihdr.bit_depth == 8);
	if (!supported_bit_depth) {
		throw std::runtime_error("Image File Error: Unsupported PNG bit depth.");
	}

	if (ihdr.width < MIN_DIMENSION || ihdr.height < MIN_DIMENSION) {
		throw std::runtime_error("Image File Error: Cover image dimensions are too small.");
	}
	if (ihdr.width > MAX_DIMENSION || ihdr.height > MAX_DIMENSION) {
		throw std::runtime_error("Image File Error: Cover image dimensions exceed the supported limit.");
	}
}

// ============================================================================
// Internal: Convert truecolor image to indexed palette
// ============================================================================

void convertToPalette(
	vBytes& image_file_vec,
	const vBytes& image,
	unsigned width,
	unsigned height,
	const LodePNGColorStats& stats,
	LodePNGColorType raw_color_type) {

	constexpr Byte
		PALETTE_BIT_DEPTH = 8,
		ALPHA_OPAQUE      = 255;

	constexpr std::size_t
		RGB_COMPONENTS   = 3,
		MAX_PALETTE_SIZE = 256;

	// Validate color type — this function only handles RGB and RGBA input.
	if (raw_color_type != LCT_RGB && raw_color_type != LCT_RGBA) {
		throw std::runtime_error(std::format(
			"convertToPalette: Unsupported color type {}. Expected RGB or RGBA.",
			static_cast<unsigned>(raw_color_type)));
	}

	const std::size_t palette_size = stats.numcolors;

	if (palette_size == 0) {
		throw std::runtime_error("convertToPalette: Palette is empty.");
	}
	if (palette_size > MAX_PALETTE_SIZE) {
		throw std::runtime_error(std::format(
			"convertToPalette: Palette has {} colors, exceeds maximum of {}.",
			palette_size, MAX_PALETTE_SIZE));
	}

	const std::size_t channels =
		(raw_color_type == LCT_RGBA) ? RGBA_COMPONENTS : RGB_COMPONENTS;

	PaletteIndexTable color_to_index;

	for (std::size_t i = 0; i < palette_size; ++i) {
		const Byte* src = &stats.palette[i * RGBA_COMPONENTS];
		color_to_index.insertIfAbsent(packRgbaKey(src[0], src[1], src[2], src[3]), static_cast<Byte>(i));
	}

	// Map each pixel to its palette index.
	const std::size_t pixel_count = checkedMultiply(
		static_cast<std::size_t>(width),
		static_cast<std::size_t>(height),
		"Image Error: Indexed image dimensions overflow.");
	const std::size_t image_span = checkedMultiply(
		pixel_count,
		channels,
		"Image Error: Indexed image byte span overflow.");
	if (image.size() < image_span) {
		throw std::runtime_error("Image Error: Decoded image buffer is truncated.");
	}
	vBytes indexed_image(pixel_count);

	const Byte* pixel = image.data();
	for (std::size_t i = 0; i < pixel_count; ++i) {
		const std::uint32_t key = packRgbaKey(
			pixel[0],
			pixel[1],
			pixel[2],
			(channels == RGBA_COMPONENTS) ? pixel[3] : ALPHA_OPAQUE
		);

		Byte palette_index = 0;
		if (!color_to_index.find(key, palette_index)) {
			throw std::runtime_error(std::format(
				"convertToPalette: Pixel {} has color 0x{:08X} not found in palette.",
				i, key));
		}
		indexed_image[i] = palette_index;
		pixel += channels;
	}

	// Encode as 8-bit palette PNG.
	lodepng::State encode_state;
	encode_state.info_raw.colortype       = LCT_PALETTE;
	encode_state.info_raw.bitdepth        = PALETTE_BIT_DEPTH;
	encode_state.info_png.color.colortype = LCT_PALETTE;
	encode_state.info_png.color.bitdepth  = PALETTE_BIT_DEPTH;
	encode_state.encoder.auto_convert     = 0;

	copyPalette(stats.palette, palette_size, encode_state.info_png.color);
	copyPalette(stats.palette, palette_size, encode_state.info_raw);

	vBytes output;
	unsigned error = lodepng::encode(output, indexed_image.data(), width, height, encode_state);
	throwLodepngError("LodePNG encode error", error);
	image_file_vec = std::move(output);
}

// ============================================================================
// Internal: Strip non-essential chunks, keeping only IHDR, PLTE, tRNS, IDAT, IEND
// ============================================================================

void stripAndCopyChunks(vBytes& image_file_vec, Byte color_type) {

	constexpr std::uint32_t
		IHDR_TYPE = pngChunkType('I', 'H', 'D', 'R'),
		PLTE_TYPE = pngChunkType('P', 'L', 'T', 'E'),
		TRNS_TYPE = pngChunkType('t', 'R', 'N', 'S'),
		IDAT_TYPE = pngChunkType('I', 'D', 'A', 'T'),
		IEND_TYPE = pngChunkType('I', 'E', 'N', 'D');

	constexpr std::size_t
		PNG_SIGNATURE_SIZE       = 8,
		CHUNK_OVERHEAD           = 12,  // length(4) + name(4) + crc(4)
		LENGTH_FIELD_SIZE        = 4,
		TYPE_FIELD_SIZE          = 4;

	// In-place memmove compaction: walk chunks once, shifting kept chunks toward
	// the front. Avoids allocating a second image-sized buffer.
	std::size_t read_pos = PNG_SIGNATURE_SIZE;
	std::size_t write_pos = PNG_SIGNATURE_SIZE;
	bool saw_idat = false;
	bool saw_iend = false;

	while (read_pos < image_file_vec.size()) {
		if (CHUNK_OVERHEAD > image_file_vec.size() - read_pos) {
			throw std::runtime_error("PNG Error: Truncated chunk header.");
		}

		const std::size_t data_length = readValueAt(image_file_vec, read_pos, LENGTH_FIELD_SIZE);
		if (data_length > image_file_vec.size() - read_pos - CHUNK_OVERHEAD) {
			throw std::runtime_error(std::format(
				"PNG Error: Chunk at offset 0x{:X} exceeds file size.",
				read_pos));
		}

		const std::size_t name_index = read_pos + LENGTH_FIELD_SIZE;
		const std::uint32_t chunk_type = static_cast<std::uint32_t>(
			readValueAt(image_file_vec, name_index, TYPE_FIELD_SIZE));
		const bool is_idat = chunk_type == IDAT_TYPE;
		const bool is_iend = chunk_type == IEND_TYPE;
		const bool keep_chunk = chunk_type == IHDR_TYPE || is_idat || is_iend || chunk_type == TRNS_TYPE
			|| (color_type == INDEXED_PLTE && chunk_type == PLTE_TYPE);
		const std::size_t total_chunk_size = CHUNK_OVERHEAD + data_length;

		if (keep_chunk) {
			if (write_pos != read_pos) {
				std::memmove(
					image_file_vec.data() + write_pos,
					image_file_vec.data() + read_pos,
					total_chunk_size);
			}
			write_pos += total_chunk_size;
		}

		saw_idat = saw_idat || is_idat;
		read_pos += total_chunk_size;

		if (is_iend) {
			saw_iend = true;
			break;
		}
	}

	if (!saw_idat) {
		throw std::runtime_error("PNG Error: No IDAT chunk found.");
	}
	if (!saw_iend) {
		throw std::runtime_error("PNG Error: Missing IEND chunk.");
	}

	image_file_vec.resize(write_pos);
}

[[nodiscard]] bool canConvertToPalette(Byte input_color_type, const LodePNGColorStats& stats) {
	return (input_color_type == TRUECOLOR_RGB || input_color_type == TRUECOLOR_RGBA)
		&& (stats.numcolors < MIN_RGB_COLORS);
}

[[nodiscard]] bool pngRangeHasProblemCharacter(std::span<const Byte> png_data, std::size_t start, std::size_t end) {
	const auto bytes = png_data.subspan(start, end - start);
	return std::ranges::any_of(bytes, isLinuxProblemMetacharacter);
}

[[nodiscard]] bool ihdrHasLinuxProblemCharacter(std::span<const Byte> png_data) {
	if (png_data.size() < IHDR_CRC_END) {
		throw std::runtime_error("PNG Error: IHDR chunk is truncated after optimization.");
	}

	return pngRangeHasProblemCharacter(png_data, IHDR_WIDTH_START, IHDR_HEIGHT_END)
		|| pngRangeHasProblemCharacter(png_data, IHDR_CRC_START, IHDR_CRC_END);
}

[[nodiscard]] bool candidateIhdrIsLinuxSafe(unsigned width, unsigned height, Byte bit_depth, Byte color_type) {
	Byte ihdr[17] = {
		'I', 'H', 'D', 'R',
		static_cast<Byte>((width >> 24) & 0xFF), static_cast<Byte>((width >> 16) & 0xFF),
		static_cast<Byte>((width >> 8) & 0xFF),  static_cast<Byte>(width & 0xFF),
		static_cast<Byte>((height >> 24) & 0xFF), static_cast<Byte>((height >> 16) & 0xFF),
		static_cast<Byte>((height >> 8) & 0xFF),  static_cast<Byte>(height & 0xFF),
		bit_depth, color_type,
		0, 0, 0  // compression, filter, interlace - lodepng default is 0 for all.
	};

	const auto dimension_bytes = std::span<const Byte>(ihdr).subspan(4, 8);
	if (std::ranges::any_of(dimension_bytes, isLinuxProblemMetacharacter)) {
		return false;
	}

	const uint32_t crc = lodepng_crc32(ihdr, sizeof(ihdr));
	const Byte crc_bytes[4] = {
		static_cast<Byte>((crc >> 24) & 0xFF),
		static_cast<Byte>((crc >> 16) & 0xFF),
		static_cast<Byte>((crc >> 8) & 0xFF),
		static_cast<Byte>(crc & 0xFF),
	};
	return !std::ranges::any_of(crc_bytes, isLinuxProblemMetacharacter);
}

[[nodiscard]] std::optional<std::pair<unsigned, unsigned>> findLinuxSafeResizeTarget(const PngIhdr& current) {
	for (unsigned delta = 1; delta <= MAX_RESIZE_ITERATIONS; ++delta) {
		if (current.width < MIN_SAFE_DIMENSION + delta || current.height < MIN_SAFE_DIMENSION + delta) {
			break;
		}

		const unsigned width = static_cast<unsigned>(current.width) - delta;
		const unsigned height = static_cast<unsigned>(current.height) - delta;
		if (candidateIhdrIsLinuxSafe(width, height, current.bit_depth, current.color_type)) {
			return std::make_pair(width, height);
		}
	}

	return std::nullopt;
}

void ensureLinuxSafeIhdr(vBytes& image_file_vec) {
	if (!ihdrHasLinuxProblemCharacter(image_file_vec)) {
		return;
	}

	const PngIhdr current = readPngIhdr(image_file_vec);
	const auto target = findLinuxSafeResizeTarget(current);
	if (!target) {
		throw std::runtime_error(
			"Image Error: Could not eliminate problem characters from IHDR "
			"within the resize iteration limit.");
	}

	resizeImage(image_file_vec, target->first, target->second);

	// Sanity-check that the encoder produced the IHDR we predicted.
	if (ihdrHasLinuxProblemCharacter(image_file_vec)) {
		throw std::runtime_error(
			"Image Error: Post-resize IHDR still contains problem characters. "
			"Encoder produced an unexpected IHDR layout.");
	}
}

[[nodiscard]] std::size_t maxDimensionForColorType(Byte color_type) {
	if (color_type == INDEXED_PLTE) {
		return MAX_PLTE_DIMENSION;
	}
	if (color_type == TRUECOLOR_RGB || color_type == TRUECOLOR_RGBA) {
		return MAX_RGB_DIMENSION;
	}
	return 0;
}

void validateFinalImageCompatibility(const PngIhdr& ihdr) {
	const std::size_t max_dimension = maxDimensionForColorType(ihdr.color_type);
	const bool has_valid_dimensions = max_dimension != 0
		&& ihdr.width >= MIN_SAFE_DIMENSION && ihdr.width <= max_dimension
		&& ihdr.height >= MIN_SAFE_DIMENSION && ihdr.height <= max_dimension;

	if (has_valid_dimensions) {
		return;
	}

	if (max_dimension == 0) {
		std::print(stderr,
			"\nImage File Error: Color type of cover image is not supported.\n\n"
			"Supported types: PNG-32/24 (Truecolor) or PNG-8 (Indexed-Color).");
	} else {
		std::print(stderr,
			"\nImage File Error: Dimensions of cover image are not within the supported range.\n\n"
			"Supported ranges:\n"
			" - PNG-32/24 Truecolor: [68 x 68] to [900 x 900]\n"
			" - PNG-8 Indexed-Color: [68 x 68] to [4096 x 4096]\n");
	}

	throw std::runtime_error("Incompatible image. Aborting.");
}

} // anonymous namespace

// ============================================================================
// Public: Optimize image for polyglot embedding
// ============================================================================

void optimizeImage(vBytes& image_file_vec) {
	validateInputPngForDecode(readPngIhdr(image_file_vec));

	lodepng::State state;

	vBytes image;
	unsigned width = 0;
	unsigned height = 0;
	unsigned error = lodepng::decode(image, width, height, state, image_file_vec);
	throwLodepngError("LodePNG decode error", error, true);

	LodePNGColorStats stats;
	lodepng_color_stats_init(&stats);
	stats.allow_palette   = 1;
	stats.allow_greyscale = 0;
	error = lodepng_compute_color_stats(&stats, image.data(), width, height, &state.info_raw);
	throwLodepngError("LodePNG stats error", error);

	const Byte input_color_type = static_cast<Byte>(state.info_png.color.colortype);

	if (canConvertToPalette(input_color_type, stats)) {
		convertToPalette(image_file_vec, image, width, height, stats, state.info_raw.colortype);
	} else {
		stripAndCopyChunks(image_file_vec, input_color_type);
	}

	// Problem metacharacters can appear in IHDR width/height bytes or the IHDR CRC
	// and break the Linux extraction script. Compute safe dimensions analytically
	// over a 17-byte scratch buffer, then resize at most once — far cheaper than
	// repeatedly decoding and re-encoding the whole PNG.
	// Checked ranges: width+height at 0x10..0x17 and CRC at 0x1D..0x20.
	ensureLinuxSafeIhdr(image_file_vec);
	validateFinalImageCompatibility(readPngIhdr(image_file_vec));
}


================================================
FILE: src/image_processing_internal.h
================================================
#pragma once

#include "pdvzip.h"

namespace image_processing_internal {

constexpr std::size_t RGBA_COMPONENTS = 4;

inline void throwLodepngError(std::string_view context, unsigned error, bool include_error_text = false) {
	if (!error) return;
	throw std::runtime_error(include_error_text
		? std::format("{} {}: {}", context, error, lodepng_error_text(error))
		: std::format("{}: {}", context, error));
}

inline void copyPalette(const Byte* palette, std::size_t count, LodePNGColorMode& target) {
	if (count > 0 && palette == nullptr) {
		throw std::runtime_error("LodePNG palette setup error: source palette is null");
	}
	for (std::size_t i = 0; i < count; ++i) {
		const Byte* p = &palette[i * RGBA_COMPONENTS];
		throwLodepngError("LodePNG palette setup error", lodepng_palette_add(&target, p[0], p[1], p[2], p[3]));
	}
}

void resizeImage(vBytes& image_file_vec, unsigned new_width, unsigned new_height);

}  // namespace image_processing_internal


================================================
FILE: src/image_resize.cpp
================================================
#include "image_processing_internal.h"

#if defined(__SSE2__) || defined(_M_X64) || (defined(_M_IX86_FP) && _M_IX86_FP >= 2)
#include <immintrin.h>
#define PDVZIP_HAS_X86_SIMD 1
#else
#define PDVZIP_HAS_X86_SIMD 0
#endif

namespace {

struct ResizeAxisSample {
	unsigned lower{};
	unsigned upper{};
	unsigned nearest{};
	float lower_weight{};
	float upper_weight{};
};

[[nodiscard]] std::vector<ResizeAxisSample> buildResizeAxisSamples(unsigned source_size, unsigned target_size) {
	constexpr double SAMPLING_OFFSET = 0.5;

	std::vector<ResizeAxisSample> samples(target_size);
	const double ratio = static_cast<double>(source_size) / static_cast<double>(target_size);

	for (unsigned i = 0; i < target_size; ++i) {
		const double source_position = std::clamp(
			(i + SAMPLING_OFFSET) * ratio - SAMPLING_OFFSET,
			0.0,
			static_cast<double>(source_size - 1)
		);
		const unsigned lower = static_cast<unsigned>(source_position);
		const float upper_weight = static_cast<float>(source_position - static_cast<double>(lower));

		samples[i] = ResizeAxisSample{
			.lower = lower,
			.upper = std::min(lower + 1, source_size - 1),
			.nearest = static_cast<unsigned>(std::round(source_position)),
			.lower_weight = 1.0f - upper_weight,
			.upper_weight = upper_weight
		};
	}

	return samples;
}

template <unsigned Channels>
inline void interpolatePixelScalar(
	Byte* destination,
	const Byte* top_left,
	const Byte* top_right,
	const Byte* bottom_left,
	const Byte* bottom_right,
	float top_left_weight,
	float top_right_weight,
	float bottom_left_weight,
	float bottom_right_weight
) {
	for (unsigned channel = 0; channel < Channels; ++channel) {
		const float value =
			top_left_weight * static_cast<float>(top_left[channel]) +
			top_right_weight * static_cast<float>(top_right[channel]) +
			bottom_left_weight * static_cast<float>(bottom_left[channel]) +
			bottom_right_weight * static_cast<float>(bottom_right[channel]);
		destination[channel] = static_cast<Byte>(std::clamp(std::round(value), 0.0f, 255.0f));
	}
}

#if PDVZIP_HAS_X86_SIMD
template <unsigned Channels>
[[nodiscard]] inline __m128 loadPixelAsFloat(const Byte* source) {
	if constexpr (Channels == 4) {
		return _mm_setr_ps(
			static_cast<float>(source[0]),
			static_cast<float>(source[1]),
			static_cast<float>(source[2]),
			static_cast<float>(source[3]));
	} else {
		return _mm_setr_ps(
			static_cast<float>(source[0]),
			static_cast<float>(source[1]),
			static_cast<float>(source[2]),
			0.0f);
	}
}

template <unsigned Channels>
inline void storeRoundedPixel(Byte* destination, __m128 value) {
	const __m128 clamped = _mm_min_ps(_mm_max_ps(value, _mm_setzero_ps()), _mm_set1_ps(255.0f));
	const __m128 biased = _mm_add_ps(clamped, _mm_set1_ps(0.5f));
	const __m128i rounded = _mm_cvttps_epi32(biased);

	alignas(16) int components[4]{};
	_mm_storeu_si128(reinterpret_cast<__m128i*>(components), rounded);

	for (unsigned channel = 0; channel < Channels; ++channel) {
		destination[channel] = static_cast<Byte>(components[channel]);
	}
}

template <unsigned Channels>
inline void interpolatePixelSimd(
	Byte* destination,
	const Byte* top_left,
	const Byte* top_right,
	const Byte* bottom_left,
	const Byte* bottom_right,
	float top_left_weight,
	float top_right_weight,
	float bottom_left_weight,
	float bottom_right_weight
) {
	const __m128 sum = _mm_add_ps(
		_mm_add_ps(
			_mm_mul_ps(loadPixelAsFloat<Channels>(top_left), _mm_set1_ps(top_left_weight)),
			_mm_mul_ps(loadPixelAsFloat<Channels>(top_right), _mm_set1_ps(top_right_weight))),
		_mm_add_ps(
			_mm_mul_ps(loadPixelAsFloat<Channels>(bottom_left), _mm_set1_ps(bottom_left_weight)),
			_mm_mul_ps(loadPixelAsFloat<Channels>(bottom_right), _mm_set1_ps(bottom_right_weight))));

	storeRoundedPixel<Channels>(destination, sum);
}
#endif

template <unsigned Channels>
inline void interpolatePixel(
	Byte* destination,
	const Byte* top_left,
	const Byte* top_right,
	const Byte* bottom_left,
	const Byte* bottom_right,
	float top_left_weight,
	float top_right_weight,
	float bottom_left_weight,
	float bottom_right_weight
) {
#if PDVZIP_HAS_X86_SIMD
	interpolatePixelSimd<Channels>(
		destination,
		top_left,
		top_right,
		bottom_left,
		bottom_right,
		top_left_weight,
		top_right_weight,
		bottom_left_weight,
		bottom_right_weight);
#else
	interpolatePixelScalar<Channels>(
		destination,
		top_left,
		top_right,
		bottom_left,
		bottom_right,
		top_left_weight,
		top_right_weight,
		bottom_left_weight,
		bottom_right_weight);
#endif
}

void resizePaletteImage(
	vBytes& resized,
	const vBytes& pixels,
	unsigned width,
	unsigned new_width,
	unsigned new_height,
	const std::vector<ResizeAxisSample>& x_samples,
	const std::vector<ResizeAxisSample>& y_samples
) {
	for (unsigned y = 0; y < new_height; ++y) {
		const std::size_t source_row = static_cast<std::size_t>(y_samples[y].nearest) * width;
		Byte* const destination_row = resized.data() + static_cast<std::size_t>(y) * new_width;

		for (unsigned x = 0; x < new_width; ++x) {
			destination_row[x] = pixels[source_row + x_samples[x].nearest];
		}
	}
}

template <unsigned Channels>
void resizeTruecolorImage(
	vBytes& resized,
	const vBytes& pixels,
	unsigned width,
	unsigned new_width,
	unsigned new_height,
	const std::vector<ResizeAxisSample>& x_samples,
	const std::vector<ResizeAxisSample>& y_samples
) {
	const std::size_t source_row_stride = static_cast<std::size_t>(width) * Channels;
	const std::size_t destination_row_stride = static_cast<std::size_t>(new_width) * Channels;

	for (unsigned y = 0; y < new_height; ++y) {
		const ResizeAxisSample& y_sample = y_samples[y];
		const Byte* const top_row = pixels.data() + static_cast<std::size_t>(y_sample.lower) * source_row_stride;
		const Byte* const bottom_row = pixels.data() + static_cast<std::size_t>(y_sample.upper) * source_row_stride;
		Byte* const destination_row = resized.data() + static_cast<std::size_t>(y) * destination_row_stride;

		for (unsigned x = 0; x < new_width; ++x) {
			const ResizeAxisSample& x_sample = x_samples[x];
			const std::size_t left_offset = static_cast<std::size_t>(x_sample.lower) * Channels;
			const std::size_t right_offset = static_cast<std::size_t>(x_sample.upper) * Channels;

			const float top_left_weight = x_sample.lower_weight * y_sample.lower_weight;
			const float top_right_weight = x_sample.upper_weight * y_sample.lower_weight;
			const float bottom_left_weight = x_sample.lower_weight * y_sample.upper_weight;
			const float bottom_right_weight = x_sample.upper_weight * y_sample.upper_weight;

			interpolatePixel<Channels>(
				destination_row + static_cast<std::size_t>(x) * Channels,
				top_row + left_offset,
				top_row + right_offset,
				bottom_row + left_offset,
				bottom_row + right_offset,
				top_left_weight,
				top_right_weight,
				bottom_left_weight,
				bottom_right_weight);
		}
	}
}

void decodePreservingColorType(
	const vBytes& image_file_vec,
	vBytes& pixels,
	unsigned& width,
	unsigned& height,
	lodepng::State& decode_state) {

	decode_state.decoder.color_convert = 0;
	const unsigned error = lodepng::decode(pixels, width, height, decode_state, image_file_vec);
	image_processing_internal::throwLodepngError("LodePNG decode error", error);
}

void validateResizeTarget(unsigned new_width, unsigned new_height, unsigned width, unsigned height) {
	constexpr unsigned MIN_DIMENSION = 68;

	if (new_width < MIN_DIMENSION || new_height < MIN_DIMENSION) {
		throw std::runtime_error("Image Error: Resize target dimensions are below the minimum.");
	}
	if (new_width > width || new_height > height) {
		throw std::runtime_error("Image Error: Resize target must not exceed source dimensions.");
	}
}

void validateDecodedResizeFormat(bool is_palette, unsigned channels, unsigned bitdepth) {
	if (channels == 0) {
		throw std::runtime_error("Image Error: Decoded image reports 0 channels.");
	}
	if (!is_palette && bitdepth != 8) {
		throw std::runtime_error("Image Error: Only 8-bit truecolor/grayscale PNGs are supported.");
	}
	if (!is_palette && channels != 3 && channels != 4) {
		throw std::runtime_error("Image Error: Resize only supports RGB/RGBA truecolor PNGs.");
	}
}

void redecodeSubBytePalette(
	const vBytes& image_file_vec,
	const lodepng::State& decode_state,
	vBytes& pixels,
	unsigned& width,
	unsigned& height) {

	lodepng::State redecode_state;
	redecode_state.decoder.color_convert = 1;
	redecode_state.info_raw.colortype = LCT_PALETTE;
	redecode_state.info_raw.bitdepth = 8;

	// Copy the palette into info_raw so lodepng can convert.
	const auto& src_color = decode_state.info_png.color;
	image_processing_internal::copyPalette(src_color.palette, src_color.palettesize, redecode_state.info_raw);

	pixels.clear();
	const unsigned error = lodepng::decode(pixels, width, height, redecode_state, image_file_vec);
	image_processing_internal::throwLodepngError("LodePNG re-decode error", error);
}

[[nodiscard]] std::size_t bytesPerPixel(bool is_palette, unsigned channels) {
	return is_palette ? 1 : channels;
}

void validateSourceBufferSize(const vBytes& pixels, unsigned width, unsigned height, std::size_t bytes_per_pixel) {
	const std::size_t source_pixel_count = checkedMultiply(
		static_cast<std::size_t>(width),
		static_cast<std::size_t>(height),
		"Image Error: Source image dimensions overflow.");
	const std::size_t source_byte_count = checkedMultiply(
		source_pixel_count,
		bytes_per_pixel,
		"Image Error: Source image buffer size overflow.");
	if (pixels.size() < source_byte_count) {
		throw std::runtime_error("Image Error: Decoded image buffer is truncated.");
	}
}

[[nodiscard]] vBytes makeResizedPixelBuffer(unsigned new_width, unsigned new_height, std::size_t bytes_per_pixel) {
	const std::size_t resized_pixel_count = checkedMultiply(
		static_cast<std::size_t>(new_width),
		static_cast<std::size_t>(new_height),
		"Image Error: Resized image dimensions overflow.");
	return vBytes(checkedMultiply(
		resized_pixel_count,
		bytes_per_pixel,
		"Image Error: Resized image buffer size overflow."));
}

void resizePixels(
	vBytes& resized,
	const vBytes& pixels,
	unsigned width,
	unsigned new_width,
	unsigned new_height,
	bool is_palette,
	unsigned channels,
	const std::vector<ResizeAxisSample>& x_samples,
	const std::vector<ResizeAxisSample>& y_samples) {

	if (is_palette) {
		resizePaletteImage(resized, pixels, width, new_width, new_height, x_samples, y_samples);
	} else if (channels == 4) {
		resizeTruecolorImage<4>(resized, pixels, width, new_width, new_height, x_samples, y_samples);
	} else {
		resizeTruecolorImage<3>(resized, pixels, width, new_width, new_height, x_samples, y_samples);
	}
}

[[nodiscard]] lodepng::State makeEncodeState(
	const LodePNGColorMode& png_color,
	bool is_palette,
	unsigned decoded_bitdepth) {

	lodepng::State encode_state;

	// For sub-byte palettes we decoded to 8-bit, so encode as 8-bit palette.
	const unsigned encode_bitdepth = (is_palette && decoded_bitdepth < 8) ? 8 : png_color.bitdepth;

	encode_state.info_raw.colortype = png_color.colortype;
	encode_state.info_raw.bitdepth = encode_bitdepth;
	encode_state.info_png.color.colortype = png_color.colortype;
	encode_state.info_png.color.bitdepth = encode_bitdepth;
	encode_state.encoder.auto_convert = 0;

	if (is_palette) {
		image_processing_internal::copyPalette(png_color.palette, png_color.palettesize, encode_state.info_png.color);
		image_processing_internal::copyPalette(png_color.palette, png_color.palettesize, encode_state.info_raw);
	}

	return encode_state;
}

}  // namespace

namespace image_processing_internal {

void resizeImage(vBytes& image_file_vec, unsigned new_width, unsigned new_height) {
	lodepng::State decode_state;
	vBytes pixels;
	unsigned width = 0;
	unsigned height = 0;
	decodePreservingColorType(image_file_vec, pixels, width, height, decode_state);

	validateResizeTarget(new_width, new_height, width, height);
	if (new_width == width && new_height == height) {
		// Nothing to resample — keep the current encoding untouched.
		return;
	}

	const auto& raw = decode_state.info_raw;

	const unsigned channels = lodepng_get_channels(&raw);
	const unsigned bitdepth = raw.bitdepth;

	const bool is_palette = (raw.colortype == LCT_PALETTE);
	validateDecodedResizeFormat(is_palette, channels, bitdepth);

	// Sub-byte palette images need conversion to 8-bit palette first; bilinear
	// interpolation on packed palette indices would add disproportionate complexity.
	if (is_palette && bitdepth < 8) {
		redecodeSubBytePalette(image_file_vec, decode_state, pixels, width, height);
		// From here on, treat as 8-bit palette (1 byte per pixel index).
	}

	const std::vector<ResizeAxisSample> x_samples = buildResizeAxisSamples(width, new_width);
	const std::vector<ResizeAxisSample> y_samples = buildResizeAxisSamples(height, new_height);

	// Palette images use 1 byte per pixel index; truecolor uses `channels`.
	const std::size_t pixel_size = bytesPerPixel(is_palette, channels);
	validateSourceBufferSize(pixels, width, height, pixel_size);

	vBytes resized = makeResizedPixelBuffer(new_width, new_height, pixel_size);
	resizePixels(resized, pixels, width, new_width, new_height, is_palette, channels, x_samples, y_samples);

	// Re-encode with the same color type and palette.
	const auto& png_color = decode_state.info_png.color;
	lodepng::State encode_state = makeEncodeState(png_color, is_palette, bitdepth);

	vBytes output;
	unsigned error = lodepng::encode(output, resized, new_width, new_height, encode_state);
	throwLodepngError("LodePNG encode error", error);

	image_file_vec = std::move(output);
}

}  // namespace image_processing_internal


================================================
FILE: src/lodepng/LICENSE
================================================
Copyright (c) 2005-2018 Lode Vandevenne

This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.

Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:

    1. The origin of this software must not be misrepresented; you must not
    claim that you wrote the original software. If you use this software
    in a product, an acknowledgment in the product documentation would be
    appreciated but is not required.

    2. Altered source versions must be plainly marked as such, and must not be
    misrepresented as being the original software.

    3. This notice may not be removed or altered from any source
    distribution.
    


================================================
FILE: src/lodepng/lodepng.cpp
================================================
/*
LodePNG version 20260119

Copyright (c) 2005-2026 Lode Vandevenne

This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.

Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:

    1. The origin of this software must not be misrepresented; you must not
    claim that you wrote the original software. If you use this software
    in a product, an acknowledgment in the product documentation would be
    appreciated but is not required.

    2. Altered source versions must be plainly marked as such, and must not be
    misrepresented as being the original software.

    3. This notice may not be removed or altered from any source
    distribution.
*/

/*
The manual and changelog are in the header file "lodepng.h"
Rename this file to lodepng.cpp to use it for C++, or to lodepng.c to use it for C.
*/

#include "lodepng.h"

#ifdef LODEPNG_COMPILE_DISK
#include <limits.h> /* LONG_MAX */
#include <stdio.h> /* file handling */
#endif /* LODEPNG_COMPILE_DISK */

#ifdef LODEPNG_COMPILE_ALLOCATORS
#include <stdlib.h> /* allocations */
#endif /* LODEPNG_COMPILE_ALLOCATORS */

/* pdvzip: use SSE2 for a few encoder byte-comparison and byte-sum hot paths. */
#if defined(__SSE2__) || defined(_M_X64) || (defined(_M_IX86_FP) && _M_IX86_FP >= 2)
#include <emmintrin.h>
#define LODEPNG_PDVZIP_SSE2 1
#else
#define LODEPNG_PDVZIP_SSE2 0
#endif

#if defined(_MSC_VER) && (_MSC_VER >= 1310) /*Visual Studio: A few warning types are not desired here.*/
#pragma warning( disable : 4244 ) /*implicit conversions: not warned by gcc -Wall -Wextra and requires too much casts*/
#pragma warning( disable : 4996 ) /*VS does not like fopen, but fopen_s is not standard C so unusable here*/
#endif /*_MSC_VER */

const char* LODEPNG_VERSION_STRING = "20260119";

/*
This source file is divided into the following large parts. The code sections
with the "LODEPNG_COMPILE_" #defines divide this up further in an intermixed way.
-Tools for C and common code for PNG and Zlib
-C Code for Zlib (huffman, deflate, ...)
-C Code for PNG (file format chunks, adam7, PNG filters, color conversions, ...)
-The C++ wrapper around all of the above
*/

/* ////////////////////////////////////////////////////////////////////////// */
/* ////////////////////////////////////////////////////////////////////////// */
/* // Tools for C, and common code for PNG and Zlib.                       // */
/* ////////////////////////////////////////////////////////////////////////// */
/* ////////////////////////////////////////////////////////////////////////// */

/*The malloc, realloc and free functions defined here with "lodepng_" in front
of the name, so that you can easily change them to others related to your
platform if needed. Everything else in the code calls these. Pass
-DLODEPNG_NO_COMPILE_ALLOCATORS to the compiler, or comment out
#define LODEPNG_COMPILE_ALLOCATORS in the header, to disable the ones here and
define them in your own project's source files without needing to change
lodepng source code. Don't forget to remove "static" if you copypaste them
from here.*/

#ifdef LODEPNG_COMPILE_ALLOCATORS
static void* lodepng_malloc(size_t size) {
#ifdef LODEPNG_MAX_ALLOC
  if(size > LODEPNG_MAX_ALLOC) return 0;
#endif
  return malloc(size);
}

/* NOTE: when realloc returns NULL, it leaves the original memory untouched */
static void* lodepng_realloc(void* ptr, size_t new_size) {
#ifdef LODEPNG_MAX_ALLOC
  if(new_size > LODEPNG_MAX_ALLOC) return 0;
#endif
  return realloc(ptr, new_size);
}

static void lodepng_free(void* ptr) {
  free(ptr);
}
#else /*LODEPNG_COMPILE_ALLOCATORS*/
/* TODO: support giving additional void* payload to the custom allocators */
void* lodepng_malloc(size_t size);
void* lodepng_realloc(void* ptr, size_t new_size);
void lodepng_free(void* ptr);
#endif /*LODEPNG_COMPILE_ALLOCATORS*/

/* convince the compiler to inline a function, for use when this measurably improves performance */
/* inline is not available in C90, but use it when supported by the compiler */
#if (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || (defined(__cplusplus) && (__cplusplus >= 199711L))
#define LODEPNG_INLINE inline
#else
#define LODEPNG_INLINE /* not available */
#endif

/* restrict is not available in C90, but use it when supported by the compiler */
#if (defined(__GNUC__) && (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1))) ||\
    (defined(_MSC_VER) && (_MSC_VER >= 1400)) || \
    (defined(__WATCOMC__) && (__WATCOMC__ >= 1250) && !defined(__cplusplus))
#define LODEPNG_RESTRICT __restrict
#else
#define LODEPNG_RESTRICT /* not available */
#endif

/* Replacements for C library functions such as memcpy and strlen, to support platforms
where a full C library is not available. The compiler can recognize them and compile
to something as fast. */

static void lodepng_memcpy(void* LODEPNG_RESTRICT dst,
                           const void* LODEPNG_RESTRICT src, size_t size) {
  size_t i;
  for(i = 0; i < size; i++) ((char*)dst)[i] = ((const char*)src)[i];
}

static void lodepng_memset(void* LODEPNG_RESTRICT dst,
                           int value, size_t num) {
  size_t i;
  for(i = 0; i < num; i++) ((char*)dst)[i] = (char)value;
}

/* does not check memory out of bounds, do not use on untrusted data */
static size_t lodepng_strlen(const char* a) {
  const char* orig = a;
  /* avoid warning about unused function in case of disabled COMPILE... macros */
  (void)(&lodepng_strlen);
  while(*a) a++;
  return (size_t)(a - orig);
}

#define LODEPNG_MAX(a, b) (((a) > (b)) ? (a) : (b))
#define LODEPNG_MIN(a, b) (((a) < (b)) ? (a) : (b))

#if defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_DECODER)
/* Safely check if adding two integers will overflow (no undefined
behavior, compiler removing the code, etc...) and output result. */
static int lodepng_addofl(size_t a, size_t b, size_t* result) {
  *result = a + b; /* Unsigned addition is well defined and safe in C90 */
  return *result < a;
}
#endif /*defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_DECODER)*/

#ifdef LODEPNG_COMPILE_DECODER
/* Safely check if multiplying two integers will overflow (no undefined
behavior, compiler removing the code, etc...) and output result. */
static int lodepng_mulofl(size_t a, size_t b, size_t* result) {
  *result = a * b; /* Unsigned multiplication is well defined and safe in C90 */
  return (a != 0 && *result / a != b);
}

#ifdef LODEPNG_COMPILE_ZLIB
/* Safely check if a + b > c, even if overflow could happen. */
static int lodepng_gtofl(size_t a, size_t b, size_t c) {
  size_t d;
  if(lodepng_addofl(a, b, &d)) return 1;
  return d > c;
}
#endif /*LODEPNG_COMPILE_ZLIB*/
#endif /*LODEPNG_COMPILE_DECODER*/


/*
Often in case of an error a value is assigned to a variable and then it breaks
out of a loop (to go to the cleanup phase of a function). This macro does that.
It makes the error handling code shorter and more readable.

Example: if(!uivector_resize(&lz77_encoded, datasize)) ERROR_BREAK(83);
*/
#define CERROR_BREAK(errorvar, code){\
  errorvar = code;\
  break;\
}

/*version of CERROR_BREAK that assumes the common case where the error variable is named "error"*/
#define ERROR_BREAK(code) CERROR_BREAK(error, code)

/*Set error var to the error code, and return it.*/
#define CERROR_RETURN_ERROR(errorvar, code){\
  errorvar = code;\
  return code;\
}

/*Try the code, if it returns error, also return the error.*/
#define CERROR_TRY_RETURN(call){\
  unsigned error_ = call;\
  if(error_) return error_;\
}

/*Set error var to the error code, and return from the void function.*/
#define CERROR_RETURN(errorvar, code){\
  errorvar = code;\
  return;\
}

/*
About uivector, ucvector and string:
-All of them wrap dynamic arrays or text strings in a similar way.
-LodePNG was originally written in C++. The vectors replace the std::vectors that were used in the C++ version.
-The string tools are made to avoid problems with compilers that declare things like strncat as deprecated.
-They're not used in the interface, only internally in this file as static functions.
-As with many other structs in this file, the init and cleanup functions serve as ctor and dtor.
*/

#ifdef LODEPNG_COMPILE_ZLIB
#ifdef LODEPNG_COMPILE_ENCODER
/*dynamic vector of unsigned ints*/
typedef struct uivector {
  unsigned* data;
  size_t size; /*size in number of unsigned longs*/
  size_t allocsize; /*allocated size in bytes*/
} uivector;

static void uivector_cleanup(void* p) {
  ((uivector*)p)->size = ((uivector*)p)->allocsize = 0;
  lodepng_free(((uivector*)p)->data);
  ((uivector*)p)->data = NULL;
}

/*returns 1 if success, 0 if failure ==> nothing done*/
static unsigned uivector_resize(uivector* p, size_t size) {
  size_t allocsize = size * sizeof(unsigned);
  if(allocsize > p->allocsize) {
    size_t newsize = allocsize + (p->allocsize >> 1u);
    void* data = lodepng_realloc(p->data, newsize);
    if(data) {
      p->allocsize = newsize;
      p->data = (unsigned*)data;
    }
    else return 0; /*error: not enough memory*/
  }
  p->size = size;
  return 1; /*success*/
}

static void uivector_init(uivector* p) {
  p->data = NULL;
  p->size = p->allocsize = 0;
}

/*returns 1 if success, 0 if failure ==> nothing done*/
static unsigned uivector_push_back(uivector* p, unsigned c) {
  if(!uivector_resize(p, p->size + 1)) return 0;
  p->data[p->size - 1] = c;
  return 1;
}
#endif /*LODEPNG_COMPILE_ENCODER*/
#endif /*LODEPNG_COMPILE_ZLIB*/

/* /////////////////////////////////////////////////////////////////////////// */

/*dynamic vector of unsigned chars*/
typedef struct ucvector {
  unsigned char* data;
  size_t size; /*used size*/
  size_t allocsize; /*allocated size*/
} ucvector;

/*returns 1 if success, 0 if failure ==> nothing done*/
static unsigned ucvector_reserve(ucvector* p, size_t size) {
  if(size > p->allocsize) {
    size_t newsize = size + (p->allocsize >> 1u);
    void* data = lodepng_realloc(p->data, newsize);
    if(data) {
      p->allocsize = newsize;
      p->data = (unsigned char*)data;
    }
    else return 0; /*error: not enough memory*/
  }
  return 1; /*success*/
}

/*returns 1 if success, 0 if failure ==> nothing done*/
static unsigned ucvector_resize(ucvector* p, size_t size) {
  p->size = size;
  return ucvector_reserve(p, size);
}

static ucvector ucvector_init(unsigned char* buffer, size_t size) {
  ucvector v;
  v.data = buffer;
  v.allocsize = v.size = size;
  return v;
}

/* ////////////////////////////////////////////////////////////////////////// */

#ifdef LODEPNG_COMPILE_PNG
#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS

/*also appends null termination character*/
static char* alloc_string_sized(const char* in, size_t insize) {
  char* out = (char*)lodepng_malloc(insize + 1);
  if(out) {
    lodepng_memcpy(out, in, insize);
    out[insize] = 0;
  }
  return out;
}

/* dynamically allocates a new string with a copy of the null terminated input text */
static char* alloc_string(const char* in) {
  return alloc_string_sized(in, lodepng_strlen(in));
}
#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/
#endif /*LODEPNG_COMPILE_PNG*/

/* ////////////////////////////////////////////////////////////////////////// */

#if defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_PNG)
static unsigned lodepng_read32bitInt(const unsigned char* buffer) {
  return (((unsigned)buffer[0] << 24u) | ((unsigned)buffer[1] << 16u) |
         ((unsigned)buffer[2] << 8u) | (unsigned)buffer[3]);
}
#endif /*defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_PNG)*/

#if defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_ENCODER)
/*buffer must have at least 4 allocated bytes available*/
static void lodepng_set32bitInt(unsigned char* buffer, unsigned value) {
  buffer[0] = (unsigned char)((value >> 24) & 0xff);
  buffer[1] = (unsigned char)((value >> 16) & 0xff);
  buffer[2] = (unsigned char)((value >>  8) & 0xff);
  buffer[3] = (unsigned char)((value      ) & 0xff);
}
#endif /*defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_ENCODER)*/

/* ////////////////////////////////////////////////////////////////////////// */
/* / File IO                                                                / */
/* ////////////////////////////////////////////////////////////////////////// */

#ifdef LODEPNG_COMPILE_DISK

/* returns negative value on error. This should be pure C compatible, so no fstat. */
static long lodepng_filesize(FILE* file) {
  long size;
  if(fseek(file, 0, SEEK_END) != 0) return -1;
  size = ftell(file);
  /* It may give LONG_MAX as directory size, this is invalid for us. */
  if(size == LONG_MAX) return -1;
  if(fseek(file, 0, SEEK_SET) != 0) return -1;
  return size;
}

/* Allocates the output buffer to the file size and reads the file into it. Returns error code.*/
static unsigned lodepng_load_file_(unsigned char** out, size_t* outsize, FILE* file) {
  long size = lodepng_filesize(file);
  if(size < 0) return 78;
  *outsize = (size_t)size;
  *out = (unsigned char*)lodepng_malloc((size_t)size);
  if(!(*out) && size > 0) return 83; /*the above malloc failed*/
  if(fread(*out, 1, *outsize, file) != *outsize) return 78;
  return 0; /*ok*/
}

unsigned lodepng_load_file(unsigned char** out, size_t* outsize, const char* filename) {
  unsigned error;
  FILE* file = fopen(filename, "rb");
  if(!file) return 78;
  error = lodepng_load_file_(out, outsize, file);
  fclose(file);
  return error;
}

/*write given buffer to the file, overwriting the file, it doesn't append to it.*/
unsigned lodepng_save_file(const unsigned char* buffer, size_t buffersize, const char* filename) {
  FILE* file = fopen(filename, "wb" );
  if(!file) return 79;
  fwrite(buffer, 1, buffersize, file);
  fclose(file);
  return 0;
}

#endif /*LODEPNG_COMPILE_DISK*/

/* ////////////////////////////////////////////////////////////////////////// */
/* ////////////////////////////////////////////////////////////////////////// */
/* // End of common code and tools. Begin of Zlib related code.            // */
/* ////////////////////////////////////////////////////////////////////////// */
/* ////////////////////////////////////////////////////////////////////////// */

#ifdef LODEPNG_COMPILE_ZLIB
#ifdef LODEPNG_COMPILE_ENCODER

typedef struct {
  ucvector* data;
  unsigned char bp; /*ok to overflow, indicates bit pos inside byte*/
} LodePNGBitWriter;

static void LodePNGBitWriter_init(LodePNGBitWriter* writer, ucvector* data) {
  writer->data = data;
  writer->bp = 0;
}

/*TODO: this ignores potential out of memory errors*/
#define WRITEBIT(writer, bit){\
  /* append new byte */\
  if(((writer->bp) & 7u) == 0) {\
    if(!ucvector_resize(writer->data, writer->data->size + 1)) return;\
    writer->data->data[writer->data->size - 1] = 0;\
  }\
  (writer->data->data[writer->data->size - 1]) |= (bit << ((writer->bp) & 7u));\
  ++writer->bp;\
}

/* LSB of value is written first, and LSB of bytes is used first */
static void writeBits(LodePNGBitWriter* writer, unsigned value, size_t nbits) {
  if(nbits == 1) { /* compiler should statically compile this case if nbits == 1 */
    WRITEBIT(writer, value);
  } else {
    /* TODO: increase output size only once here rather than in each WRITEBIT */
    size_t i;
    for(i = 0; i != nbits; ++i) {
      WRITEBIT(writer, (unsigned char)((value >> i) & 1));
    }
  }
}

/* This one is to use for adding huffman symbol, the value bits are written MSB first */
static void writeBitsReversed(LodePNGBitWriter* writer, unsigned value, size_t nbits) {
  size_t i;
  for(i = 0; i != nbits; ++i) {
    /* TODO: increase output size only once here rather than in each WRITEBIT */
    WRITEBIT(writer, (unsigned char)((value >> (nbits - 1u - i)) & 1u));
  }
}
#endif /*LODEPNG_COMPILE_ENCODER*/

#ifdef LODEPNG_COMPILE_DECODER

typedef struct {
  const unsigned char* data;
  size_t size; /*size of data in bytes*/
  size_t bitsize; /*size of data in bits, end of valid bp values, should be 8*size*/
  size_t bp;
  unsigned buffer; /*buffer for reading bits. NOTE: 'unsigned' must support at least 32 bits*/
} LodePNGBitReader;

/* data size argument is in bytes. Returns error if size too large causing overflow */
static unsigned LodePNGBitReader_init(LodePNGBitReader* reader, const unsigned char* data, size_t size) {
  size_t temp;
  reader->data = data;
  reader->size = size;
  /* size in bits, return error if overflow (if size_t is 32 bit this supports up to 500MB)  */
  if(lodepng_mulofl(size, 8u, &reader->bitsize)) return 105;
  /*ensure incremented bp can be compared to bitsize without overflow even when it would be incremented 32 too much and
  trying to ensure 32 more bits*/
  if(lodepng_addofl(reader->bitsize, 64u, &temp)) return 105;
  reader->bp = 0;
  reader->buffer = 0;
  return 0; /*ok*/
}

/*
ensureBits functions:
Ensures the reader can at least read nbits bits in one or more readBits calls,
safely even if not enough bits are available.
The nbits parameter is unused but is given for documentation purposes, error
checking for amount of bits must be done beforehand.
*/

/*See ensureBits documentation above. This one ensures up to 9 bits */
static LODEPNG_INLINE void ensureBits9(LodePNGBitReader* reader, size_t nbits) {
  size_t start = reader->bp >> 3u;
  size_t size = reader->size;
  if(start + 1u < size) {
    reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u);
    reader->buffer >>= (reader->bp & 7u);
  } else {
    reader->buffer = 0;
    if(start + 0u < size) reader->buffer = reader->data[start + 0];
    reader->buffer >>= (reader->bp & 7u);
  }
  (void)nbits;
}

/*See ensureBits documentation above. This one ensures up to 17 bits */
static LODEPNG_INLINE void ensureBits17(LodePNGBitReader* reader, size_t nbits) {
  size_t start = reader->bp >> 3u;
  size_t size = reader->size;
  if(start + 2u < size) {
    reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u) |
                     ((unsigned)reader->data[start + 2] << 16u);
    reader->buffer >>= (reader->bp & 7u);
  } else {
    reader->buffer = 0;
    if(start + 0u < size) reader->buffer |= reader->data[start + 0];
    if(start + 1u < size) reader->buffer |= ((unsigned)reader->data[start + 1] << 8u);
    reader->buffer >>= (reader->bp & 7u);
  }
  (void)nbits;
}

/*See ensureBits documentation above. This one ensures up to 25 bits */
static LODEPNG_INLINE void ensureBits25(LodePNGBitReader* reader, size_t nbits) {
  size_t start = reader->bp >> 3u;
  size_t size = reader->size;
  if(start + 3u < size) {
    reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u) |
                     ((unsigned)reader->data[start + 2] << 16u) | ((unsigned)reader->data[start + 3] << 24u);
    reader->buffer >>= (reader->bp & 7u);
  } else {
    reader->buffer = 0;
    if(start + 0u < size) reader->buffer |= reader->data[start + 0];
    if(start + 1u < size) reader->buffer |= ((unsigned)reader->data[start + 1] << 8u);
    if(start + 2u < size) reader->buffer |= ((unsigned)reader->data[start + 2] << 16u);
    reader->buffer >>= (reader->bp & 7u);
  }
  (void)nbits;
}

/*See ensureBits documentation above. This one ensures up to 32 bits */
static LODEPNG_INLINE void ensureBits32(LodePNGBitReader* reader, size_t nbits) {
  size_t start = reader->bp >> 3u;
  size_t size = reader->size;
  if(start + 4u < size) {
    reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u) |
                     ((unsigned)reader->data[start + 2] << 16u) | ((unsigned)reader->data[start + 3] << 24u);
    reader->buffer >>= (reader->bp & 7u);
    reader->buffer |= (((unsigned)reader->data[start + 4] << 24u) << (8u - (reader->bp & 7u)));
  } else {
    reader->buffer = 0;
    if(start + 0u < size) reader->buffer |= reader->data[start + 0];
    if(start + 1u < size) reader->buffer |= ((unsigned)reader->data[start + 1] << 8u);
    if(start + 2u < size) reader->buffer |= ((unsigned)reader->data[start + 2] << 16u);
    if(start + 3u < size) reader->buffer |= ((unsigned)reader->data[start + 3] << 24u);
    reader->buffer >>= (reader->bp & 7u);
  }
  (void)nbits;
}

/* Get bits without advancing the bit pointer. Must have enough bits available with ensureBits. Max nbits is 31. */
static LODEPNG_INLINE unsigned peekBits(LodePNGBitReader* reader, size_t nbits) {
  /* The shift allows nbits to be only up to 31. */
  return reader->buffer & ((1u << nbits) - 1u);
}

/* Must have enough bits available with ensureBits */
static LODEPNG_INLINE void advanceBits(LodePNGBitReader* reader, size_t nbits) {
  reader->buffer >>= nbits;
  reader->bp += nbits;
}

/* Must have enough bits available with ensureBits */
static LODEPNG_INLINE unsigned readBits(LodePNGBitReader* reader, size_t nbits) {
  unsigned result = peekBits(reader, nbits);
  advanceBits(reader, nbits);
  return result;
}
#endif /*LODEPNG_COMPILE_DECODER*/

static unsigned reverseBits(unsigned bits, unsigned num) {
  /*TODO: implement faster lookup table based version when needed*/
  unsigned i, result = 0;
  for(i = 0; i < num; i++) result |= ((bits >> (num - i - 1u)) & 1u) << i;
  return result;
}

/* ////////////////////////////////////////////////////////////////////////// */
/* / Deflate - Huffman                                                      / */
/* ////////////////////////////////////////////////////////////////////////// */

#define FIRST_LENGTH_CODE_INDEX 257
#define LAST_LENGTH_CODE_INDEX 285
/*256 literals, the end code, some length codes, and 2 unused codes*/
#define NUM_DEFLATE_CODE_SYMBOLS 288
/*the distance codes have their own symbols, 30 used, 2 unused*/
#define NUM_DISTANCE_SYMBOLS 32
/*the code length codes. 0-15: code lengths, 16: copy previous 3-6 times, 17: 3-10 zeros, 18: 11-138 zeros*/
#define NUM_CODE_LENGTH_CODES 19

/*the base lengths represented by codes 257-285*/
static const unsigned LENGTHBASE[29]
  = {3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59,
     67, 83, 99, 115, 131, 163, 195, 227, 258};

/*the extra bits used by codes 257-285 (added to base length)*/
static const unsigned LENGTHEXTRA[29]
  = {0, 0, 0, 0, 0, 0, 0,  0,  1,  1,  1,  1,  2,  2,  2,  2,  3,  3,  3,  3,
      4,  4,  4,   4,   5,   5,   5,   5,   0};

/*the base backwards distances (the bits of distance codes appear after length codes and use their own huffman tree)*/
static const unsigned DISTANCEBASE[30]
  = {1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513,
     769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577};

/*the extra bits of backwards distances (added to base)*/
static const unsigned DISTANCEEXTRA[30]
  = {0, 0, 0, 0, 1, 1, 2,  2,  3,  3,  4,  4,  5,  5,   6,   6,   7,   7,   8,
       8,    9,    9,   10,   10,   11,   11,   12,    12,    13,    13};

/*the order in which "code length alphabet code lengths" are stored as specified by deflate, out of this the huffman
tree of the dynamic huffman tree lengths is generated*/
static const unsigned CLCL_ORDER[NUM_CODE_LENGTH_CODES]
  = {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15};

/* ////////////////////////////////////////////////////////////////////////// */

/*
Huffman tree struct, containing multiple representations of the tree
*/
typedef struct HuffmanTree {
  unsigned* codes; /*the huffman codes (bit patterns representing the symbols)*/
  unsigned* lengths; /*the lengths of the huffman codes*/
  unsigned maxbitlen; /*maximum number of bits a single code can get*/
  unsigned numcodes; /*number of symbols in the alphabet = number of codes*/
  /* for reading only */
  unsigned char* table_len; /*length of symbol from lookup table, or max length if secondary lookup needed*/
  unsigned short* table_value; /*value of symbol from lookup table, or pointer to secondary table if needed*/
} HuffmanTree;

static void HuffmanTree_init(HuffmanTree* tree) {
  tree->codes = 0;
  tree->lengths = 0;
  tree->table_len = 0;
  tree->table_value = 0;
}

static void HuffmanTree_cleanup(HuffmanTree* tree) {
  lodepng_free(tree->codes);
  lodepng_free(tree->lengths);
  lodepng_free(tree->table_len);
  lodepng_free(tree->table_value);
}

/* amount of bits for first huffman table lookup (aka root bits), see HuffmanTree_makeTable and huffmanDecodeSymbol.*/
/* values 8u and 9u work the fastest */
#define FIRSTBITS 9u

/* a symbol value too big to represent any valid symbol, to indicate reading disallowed huffman bits combination,
which is possible in case of only 0 or 1 present symbols. */
#define INVALIDSYMBOL 65535u

/* make table for huffman decoding */
static unsigned HuffmanTree_makeTable(HuffmanTree* tree) {
  static const unsigned headsize = 1u << FIRSTBITS; /*size of the first table*/
  static const unsigned mask = (1u << FIRSTBITS) /*headsize*/ - 1u;
  size_t i, numpresent, pointer, size; /*total table size*/
  unsigned* maxlens = (unsigned*)lodepng_malloc(headsize * sizeof(unsigned));
  if(!maxlens) return 83; /*alloc fail*/

  /* compute maxlens: max total bit length of symbols sharing prefix in the first table*/
  lodepng_memset(maxlens, 0, headsize * sizeof(*maxlens));
  for(i = 0; i < tree->numcodes; i++) {
    unsigned symbol = tree->codes[i];
    unsigned l = tree->lengths[i];
    unsigned index;
    if(l <= FIRSTBITS) continue; /*symbols that fit in first table don't increase secondary table size*/
    /*get the FIRSTBITS MSBs, the MSBs of the symbol are encoded first. See later comment about the reversing*/
    index = reverseBits(symbol >> (l - FIRSTBITS), FIRSTBITS);
    maxlens[index] = LODEPNG_MAX(maxlens[index], l);
  }
  /* compute total table size: size of first table plus all secondary tables for symbols longer than FIRSTBITS */
  size = headsize;
  for(i = 0; i < headsize; ++i) {
    unsigned l = maxlens[i];
    if(l > FIRSTBITS) size += (((size_t)1) << (l - FIRSTBITS));
  }
  tree->table_len = (unsigned char*)lodepng_malloc(size * sizeof(*tree->table_len));
  tree->table_value = (unsigned short*)lodepng_malloc(size * sizeof(*tree->table_value));
  if(!tree->table_len || !tree->table_value) {
    lodepng_free(maxlens);
    /* freeing tree->table values is done at a higher scope */
    return 83; /*alloc fail*/
  }
  /*initialize with an invalid length to indicate unused entries*/
  for(i = 0; i < size; ++i) tree->table_len[i] = 16;

  /*fill in the first table for long symbols: max prefix size and pointer to secondary tables*/
  pointer = headsize;
  for(i = 0; i < headsize; ++i) {
    unsigned l = maxlens[i];
    if(l <= FIRSTBITS) continue;
    tree->table_len[i] = l;
    tree->table_value[i] = (unsigned short)pointer;
    pointer += (((size_t)1) << (l - FIRSTBITS));
  }
  lodepng_free(maxlens);

  /*fill in the first table for short symbols, or secondary table for long symbols*/
  numpresent = 0;
  for(i = 0; i < tree->numcodes; ++i) {
    unsigned l = tree->lengths[i];
    unsigned symbol, reverse;
    if(l == 0) continue;
    symbol = tree->codes[i]; /*the huffman bit pattern. i itself is the value.*/
    /*reverse bits, because the huffman bits are given in MSB first order but the bit reader reads LSB first*/
    reverse = reverseBits(symbol, l);
    numpresent++;

    if(l <= FIRSTBITS) {
      /*short symbol, fully in first table, replicated num times if l < FIRSTBITS*/
      unsigned num = 1u << (FIRSTBITS - l);
      unsigned j;
      for(j = 0; j < num; ++j) {
        /*bit reader will read the l bits of symbol first, the remaining FIRSTBITS - l bits go to the MSB's*/
        unsigned index = reverse | (j << l);
        if(tree->table_len[index] != 16) return 55; /*invalid tree: long symbol shares prefix with short symbol*/
        tree->table_len[index] = l;
        tree->table_value[index] = (unsigned short)i;
      }
    } else {
      /*long symbol, shares prefix with other long symbols in first lookup table, needs second lookup*/
      /*the FIRSTBITS MSBs of the symbol are the first table index*/
      unsigned index = reverse & mask;
      unsigned maxlen = tree->table_len[index];
      /*log2 of secondary table length, should be >= l - FIRSTBITS*/
      unsigned tablelen = maxlen - FIRSTBITS;
      unsigned start = tree->table_value[index]; /*starting index in secondary table*/
      unsigned num = 1u << (tablelen - (l - FIRSTBITS)); /*amount of entries of this symbol in secondary table*/
      unsigned j;
      if(maxlen < l) return 55; /*invalid tree: long symbol shares prefix with short symbol*/
      for(j = 0; j < num; ++j) {
        unsigned reverse2 = reverse >> FIRSTBITS; /* l - FIRSTBITS bits */
        unsigned index2 = start + (reverse2 | (j << (l - FIRSTBITS)));
        tree->table_len[index2] = l;
        tree->table_value[index2] = (unsigned short)i;
      }
    }
  }

  if(numpresent < 2) {
    /* In case of exactly 1 symbol, in theory the huffman symbol needs 0 bits,
    but deflate uses 1 bit instead. In case of 0 symbols, no symbols can
    appear at all, but such huffman tree could still exist (e.g. if distance
    codes are never used). In both cases, not all symbols of the table will be
    filled in. Fill them in with an invalid symbol value so returning them from
    huffmanDecodeSymbol will cause error. */
    for(i = 0; i < size; ++i) {
      if(tree->table_len[i] == 16) {
        /* As length, use a value smaller than FIRSTBITS for the head table,
        and a value larger than FIRSTBITS for the secondary table, to ensure
        valid behavior for advanceBits when reading this symbol. */
        tree->table_len[i] = (i < headsize) ? 1 : (FIRSTBITS + 1);
        tree->table_value[i] = INVALIDSYMBOL;
      }
    }
  } else {
    /* A good huffman tree has N * 2 - 1 nodes, of which N - 1 are internal nodes.
    If that is not the case (due to too long length codes), the table will not
    have been fully used, and this is an error (not all bit combinations can be
    decoded): an oversubscribed huffman tree, indicated by error 55. */
    for(i = 0; i < size; ++i) {
      if(tree->table_len[i] == 16) return 55;
    }
  }

  return 0;
}

/*
Second step for the ...makeFromLengths and ...makeFromFrequencies functions.
numcodes, lengths and maxbitlen must already be filled in correctly. return
value is error.
*/
static unsigned HuffmanTree_makeFromLengths2(HuffmanTree* tree) {
  unsigned* blcount;
  unsigned* nextcode;
  unsigned error = 0;
  unsigned bits, n;

  tree->codes = (unsigned*)lodepng_malloc(tree->numcodes * sizeof(unsigned));
  blcount = (unsigned*)lodepng_malloc((tree->maxbitlen + 1) * sizeof(unsigned));
  nextcode = (unsigned*)lodepng_malloc((tree->maxbitlen + 1) * sizeof(unsigned));
  if(!tree->codes || !blcount || !nextcode) error = 83; /*alloc fail*/

  if(!error) {
    for(n = 0; n != tree->maxbitlen + 1; n++) blcount[n] = nextcode[n] = 0;
    /*step 1: count number of instances of each code length*/
    for(bits = 0; bits != tree->numcodes; ++bits) ++blcount[tree->lengths[bits]];
    /*step 2: generate the nextcode values*/
    for(bits = 1; bits <= tree->maxbitlen; ++bits) {
      nextcode[bits] = (nextcode[bits - 1] + blcount[bits - 1]) << 1u;
    }
    /*step 3: generate all the codes*/
    for(n = 0; n != tree->numcodes; ++n) {
      if(tree->lengths[n] != 0) {
        tree->codes[n] = nextcode[tree->lengths[n]]++;
        /*remove superfluous bits from the code*/
        tree->codes[n] &= ((1u << tree->lengths[n]) - 1u);
      }
    }
  }

  lodepng_free(blcount);
  lodepng_free(nextcode);

  if(!error) error = HuffmanTree_makeTable(tree);
  return error;
}

/*
given the code lengths (as stored in the PNG file), generate the tree as defined
by Deflate. maxbitlen is the maximum bits that a code in the tree can have.
return value is error.
*/
static unsigned HuffmanTree_makeFromLengths(HuffmanTree* tree, const unsigned* bitlen,
                                            size_t numcodes, unsigned maxbitlen) {
  unsigned i;
  tree->lengths = (unsigned*)lodepng_malloc(numcodes * sizeof(unsigned));
  if(!tree->lengths) return 83; /*alloc fail*/
  for(i = 0; i != numcodes; ++i) tree->lengths[i] = bitlen[i];
  tree->numcodes = (unsigned)numcodes; /*number of symbols*/
  tree->maxbitlen = maxbitlen;
  return HuffmanTree_makeFromLengths2(tree);
}

#ifdef LODEPNG_COMPILE_ENCODER

/*BPM: Boundary Package Merge, see "A Fast and Space-Economical Algorithm for Length-Limited Coding",
Jyrki Katajainen, Alistair Moffat, Andrew Turpin, 1995.*/

/*chain node for boundary package merge*/
typedef struct BPMNode {
  int weight; /*the sum of all weights in this chain*/
  unsigned index; /*index of this leaf node (called "count" in the paper)*/
  struct BPMNode* tail; /*the next nodes in this chain (null if last)*/
  int in_use;
} BPMNode;

/*lists of chains*/
typedef struct BPMLists {
  /*memory pool*/
  unsigned memsize;
  BPMNode* memory;
  unsigned numfree;
  unsigned nextfree;
  BPMNode** freelist;
  /*two heads of lookahead chains per list*/
  unsigned listsize;
  BPMNode** chains0;
  BPMNode** chains1;
} BPMLists;

/*creates a new chain node with the given parameters, from the memory in the lists */
static BPMNode* bpmnode_create(BPMLists* lists, int weight, unsigned index, BPMNode* tail) {
  unsigned i;
  BPMNode* result;

  /*memory full, so garbage collect*/
  if(lists->nextfree >= lists->numfree) {
    /*mark only those that are in use*/
    for(i = 0; i != lists->memsize; ++i) lists->memory[i].in_use = 0;
    for(i = 0; i != lists->listsize; ++i) {
      BPMNode* node;
      for(node = lists->chains0[i]; node != 0; node = node->tail) node->in_use = 1;
      for(node = lists->chains1[i]; node != 0; node = node->tail) node->in_use = 1;
    }
    /*collect those that are free*/
    lists->numfree = 0;
    for(i = 0; i != lists->memsize; ++i) {
      if(!lists->memory[i].in_use) lists->freelist[lists->numfree++] = &lists->memory[i];
    }
    lists->nextfree = 0;
  }

  result = lists->freelist[lists->nextfree++];
  result->weight = weight;
  result->index = index;
  result->tail = tail;
  return result;
}

/*sort the leaves with stable mergesort*/
static void bpmnode_sort(BPMNode* leaves, size_t num) {
  BPMNode* mem = (BPMNode*)lodepng_malloc(sizeof(*leaves) * num);
  size_t width, counter = 0;
  for(width = 1; width < num; width *= 2) {
    BPMNode* a = (counter & 1) ? mem : leaves;
    BPMNode* b = (counter & 1) ? leaves : mem;
    size_t p;
    for(p = 0; p < num; p += 2 * width) {
      size_t q = (p + width > num) ? num : (p + width);
      size_t r = (p + 2 * width > num) ? num : (p + 2 * width);
      size_t i = p, j = q, k;
      for(k = p; k < r; k++) {
        if(i < q && (j >= r || a[i].weight <= a[j].weight)) b[k] = a[i++];
        else b[k] = a[j++];
      }
    }
    counter++;
  }
  if(counter & 1) lodepng_memcpy(leaves, mem, sizeof(*leaves) * num);
  lodepng_free(mem);
}

/*Boundary Package Merge step, numpresent is the amount of leaves, and c is the current chain.*/
static void boundaryPM(BPMLists* lists, BPMNode* leaves, size_t numpresent, int c, int num) {
  unsigned lastindex = lists->chains1[c]->index;

  if(c == 0) {
    if(lastindex >= numpresent) return;
    lists->chains0[c] = lists->chains1[c];
    lists->chains1[c] = bpmnode_create(lists, leaves[lastindex].weight, lastindex + 1, 0);
  } else {
    /*sum of the weights of the head nodes of the previous lookahead chains.*/
    int sum = lists->chains0[c - 1]->weight + lists->chains1[c - 1]->weight;
    lists->chains0[c] = lists->chains1[c];
    if(lastindex < numpresent && sum > leaves[lastindex].weight) {
      lists->chains1[c] = bpmnode_create(lists, leaves[lastindex].weight, lastindex + 1, lists->chains1[c]->tail);
      return;
    }
    lists->chains1[c] = bpmnode_create(lists, sum, lastindex, lists->chains1[c - 1]);
    /*in the end we are only interested in the chain of the last list, so no
    need to recurse if we're at the last one (this gives measurable speedup)*/
    if(num + 1 < (int)(2 * numpresent - 2)) {
      boundaryPM(lists, leaves, numpresent, c - 1, num);
      boundaryPM(lists, leaves, numpresent, c - 1, num);
    }
  }
}

unsigned lodepng_huffman_code_lengths(unsigned* lengths, const unsigned* frequencies,
                                      size_t numcodes, unsigned maxbitlen) {
  unsigned error = 0;
  unsigned i;
  size_t numpresent = 0; /*number of symbols with non-zero frequency*/
  BPMNode* leaves; /*the symbols, only those with > 0 frequency*/

  if(numcodes == 0) return 80; /*error: a tree of 0 symbols is not supposed to be made*/
  if((1u << maxbitlen) < (unsigned)numcodes) return 80; /*error: represent all symbols*/

  leaves = (BPMNode*)lodepng_malloc(numcodes * sizeof(*leaves));
  if(!leaves) return 83; /*alloc fail*/

  for(i = 0; i != numcodes; ++i) {
    if(frequencies[i] > 0) {
      leaves[numpresent].weight = (int)frequencies[i];
      leaves[numpresent].index = i;
      ++numpresent;
    }
  }

  lodepng_memset(lengths, 0, numcodes * sizeof(*lengths));

  /*ensure at least two present symbols. There should be at least one symbol
  according to RFC 1951 section 3.2.7. Some decoders incorrectly require two. To
  make these work as well ensure there are at least two symbols. The
  Package-Merge code below also doesn't work correctly if there's only one
  symbol, it'd give it the theoretical 0 bits but in practice zlib wants 1 bit*/
  if(numpresent == 0) {
    lengths[0] = lengths[1] = 1; /*note that for RFC 1951 section 3.2.7, only lengths[0] = 1 is needed*/
  } else if(numpresent == 1) {
    lengths[leaves[0].index] = 1;
    lengths[leaves[0].index == 0 ? 1 : 0] = 1;
  } else {
    BPMLists lists;
    BPMNode* node;

    bpmnode_sort(leaves, numpresent);

    lists.listsize = maxbitlen;
    lists.memsize = 2 * maxbitlen * (maxbitlen + 1);
    lists.nextfree = 0;
    lists.numfree = lists.memsize;
    lists.memory = (BPMNode*)lodepng_malloc(lists.memsize * sizeof(*lists.memory));
    lists.freelist = (BPMNode**)lodepng_malloc(lists.memsize * sizeof(BPMNode*));
    lists.chains0 = (BPMNode**)lodepng_malloc(lists.listsize * sizeof(BPMNode*));
    lists.chains1 = (BPMNode**)lodepng_malloc(lists.listsize * sizeof(BPMNode*));
    if(!lists.memory || !lists.freelist || !lists.chains0 || !lists.chains1) error = 83; /*alloc fail*/

    if(!error) {
      for(i = 0; i != lists.memsize; ++i) lists.freelist[i] = &lists.memory[i];

      bpmnode_create(&lists, leaves[0].weight, 1, 0);
      bpmnode_create(&lists, leaves[1].weight, 2, 0);

      for(i = 0; i != lists.listsize; ++i) {
        lists.chains0[i] = &lists.memory[0];
        lists.chains1[i] = &lists.memory[1];
      }

      /*each boundaryPM call adds one chain to the last list, and we need 2 * numpresent - 2 chains.*/
      for(i = 2; i != 2 * numpresent - 2; ++i) boundaryPM(&lists, leaves, numpresent, (int)maxbitlen - 1, (int)i);

      for(node = lists.chains1[maxbitlen - 1]; node; node = node->tail) {
        for(i = 0; i != node->index; ++i) ++lengths[leaves[i].index];
      }
    }

    lodepng_free(lists.memory);
    lodepng_free(lists.freelist);
    lodepng_free(lists.chains0);
    lodepng_free(lists.chains1);
  }

  lodepng_free(leaves);
  return error;
}

/*Create the Huffman tree given the symbol frequencies*/
static unsigned HuffmanTree_makeFromFrequencies(HuffmanTree* tree, const unsigned* frequencies,
                                                size_t mincodes, size_t numcodes, unsigned maxbitlen) {
  unsigned error = 0;
  while(!frequencies[numcodes - 1] && numcodes > mincodes) --numcodes; /*trim zeroes*/
  tree->lengths = (unsigned*)lodepng_malloc(numcodes * sizeof(unsigned));
  if(!tree->lengths) return 83; /*alloc fail*/
  tree->maxbitlen = maxbitlen;
  tree->numcodes = (unsigned)numcodes; /*number of symbols*/

  error = lodepng_huffman_code_lengths(tree->lengths, frequencies, numcodes, maxbitlen);
  if(!error) error = HuffmanTree_makeFromLengths2(tree);
  return error;
}
#endif /*LODEPNG_COMPILE_ENCODER*/

/*get the literal and length code tree of a deflated block with fixed tree, as per the deflate specification*/
static unsigned generateFixedLitLenTree(HuffmanTree* tree) {
  unsigned i, error = 0;
  unsigned* bitlen = (unsigned*)lodepng_malloc(NUM_DEFLATE_CODE_SYMBOLS * sizeof(unsigned));
  if(!bitlen) return 83; /*alloc fail*/

  /*288 possible codes: 0-255=literals, 256=endcode, 257-285=lengthcodes, 286-287=unused*/
  for(i =   0; i <= 143; ++i) bitlen[i] = 8;
  for(i = 144; i <= 255; ++i) bitlen[i] = 9;
  for(i = 256; i <= 279; ++i) bitlen[i] = 7;
  for(i = 280; i <= 287; ++i) bitlen[i] = 8;

  error = HuffmanTree_makeFromLengths(tree, bitlen, NUM_DEFLATE_CODE_SYMBOLS, 15);

  lodepng_free(bitlen);
  return error;
}

/*get the distance code tree of a deflated block with fixed tree, as specified in the deflate specification*/
static unsigned generateFixedDistanceTree(HuffmanTree* tree) {
  unsigned i, error = 0;
  unsigned* bitlen = (unsigned*)lodepng_malloc(NUM_DISTANCE_SYMBOLS * sizeof(unsigned));
  if(!bitlen) return 83; /*alloc fail*/

  /*there are 32 distance codes, but 30-31 are unused*/
  for(i = 0; i != NUM_DISTANCE_SYMBOLS; ++i) bitlen[i] = 5;
  error = HuffmanTree_makeFromLengths(tree, bitlen, NUM_DISTANCE_SYMBOLS, 15);

  lodepng_free(bitlen);
  return error;
}

#ifdef LODEPNG_COMPILE_DECODER

/*
returns the code. The bit reader must already have been ensured at least 15 bits
*/
static unsigned huffmanDecodeSymbol(LodePNGBitReader* reader, const HuffmanTree* codetree) {
  unsigned short code = peekBits(reader, FIRSTBITS);
  unsigned short l = codetree->table_len[code];
  unsigned short value = codetree->table_value[code];
  if(l <= FIRSTBITS) {
    advanceBits(reader, l);
    return value;
  } else {
    advanceBits(reader, FIRSTBITS);
    value += peekBits(reader, l - FIRSTBITS);
    advanceBits(reader, codetree->table_len[value] - FIRSTBITS);
    return codetree->table_value[value];
  }
}
#endif /*LODEPNG_COMPILE_DECODER*/

#ifdef LODEPNG_COMPILE_DECODER

/* ////////////////////////////////////////////////////////////////////////// */
/* / Inflator (Decompressor)                                                / */
/* ////////////////////////////////////////////////////////////////////////// */

/*get the tree of a deflated block with fixed tree, as specified in the deflate specification
Returns error code.*/
static unsigned getTreeInflateFixed(HuffmanTree* tree_ll, HuffmanTree* tree_d) {
  unsigned error = generateFixedLitLenTree(tree_ll);
  if(error) return error;
  return generateFixedDistanceTree(tree_d);
}

/*get the tree of a deflated block with dynamic tree, the tree itself is also Huffman compressed with a known tree*/
static unsigned getTreeInflateDynamic(HuffmanTree* tree_ll, HuffmanTree* tree_d,
                                      LodePNGBitReader* reader) {
  /*make sure that length values that aren't filled in will be 0, or a wrong tree will be generated*/
  unsigned error = 0;
  unsigned n, HLIT, HDIST, HCLEN, i;

  /*see comments in deflateDynamic for explanation of the context and these variables, it is analogous*/
  unsigned* bitlen_ll = 0; /*lit,len code lengths*/
  unsigned* bitlen_d = 0; /*dist code lengths*/
  /*code length code lengths ("clcl"), the bit lengths of the huffman tree used to compress bitlen_ll and bitlen_d*/
  unsigned* bitlen_cl = 0;
  HuffmanTree tree_cl; /*the code tree for code length codes (the huffman tree for compressed huffman trees)*/

  if(reader->bitsize - reader->bp < 14) return 49; /*error: the bit pointer is or will go past the memory*/
  ensureBits17(reader, 14);

  /*number of literal/length codes + 257. Unlike the spec, the value 257 is added to it here already*/
  HLIT =  readBits(reader, 5) + 257;
  /*number of distance codes. Unlike the spec, the value 1 is added to it here already*/
  HDIST = readBits(reader, 5) + 1;
  /*number of code length codes. Unlike the spec, the value 4 is added to it here already*/
  HCLEN = readBits(reader, 4) + 4;

  bitlen_cl = (unsigned*)lodepng_malloc(NUM_CODE_LENGTH_CODES * sizeof(unsigned));
  if(!bitlen_cl) return 83 /*alloc fail*/;

  HuffmanTree_init(&tree_cl);

  while(!error) {
    /*read the code length codes out of 3 * (amount of code length codes) bits*/
    if(lodepng_gtofl(reader->bp, HCLEN * 3, reader->bitsize)) {
      ERROR_BREAK(50); /*error: the bit pointer is or will go past the memory*/
    }
    for(i = 0; i != HCLEN; ++i) {
      ensureBits9(reader, 3); /*out of bounds already checked above */
      bitlen_cl[CLCL_ORDER[i]] = readBits(reader, 3);
    }
    for(i = HCLEN; i != NUM_CODE_LENGTH_CODES; ++i) {
      bitlen_cl[CLCL_ORDER[i]] = 0;
    }

    error = HuffmanTree_makeFromLengths(&tree_cl, bitlen_cl, NUM_CODE_LENGTH_CODES, 7);
    if(error) break;

    /*now we can use this tree to read the lengths for the tree that this function will return*/
    bitlen_ll = (unsigned*)lodepng_malloc(NUM_DEFLATE_CODE_SYMBOLS * sizeof(unsigned));
    bitlen_d = (unsigned*)lodepng_malloc(NUM_DISTANCE_SYMBOLS * sizeof(unsigned));
    if(!bitlen_ll || !bitlen_d) ERROR_BREAK(83 /*alloc fail*/);
    lodepng_memset(bitlen_ll, 0, NUM_DEFLATE_CODE_SYMBOLS * sizeof(*bitlen_ll));
    lodepng_memset(bitlen_d, 0, NUM_DISTANCE_SYMBOLS * sizeof(*bitlen_d));

    /*i is the current symbol we're reading in the part that contains the code lengths of lit/len and dist codes*/
    i = 0;
    while(i < HLIT + HDIST) {
      unsigned code;
      ensureBits25(reader, 22); /* up to 15 bits for huffman code, up to 7 extra bits below*/
      code = huffmanDecodeSymbol(reader, &tree_cl);
      if(code <= 15) /*a length code*/ {
        if(i < HLIT) bitlen_ll[i] = code;
        else bitlen_d[i - HLIT] = code;
        ++i;
      } else if(code == 16) /*repeat previous*/ {
        unsigned replength = 3; /*read in the 2 bits that indicate repeat length (3-6)*/
        unsigned value; /*set value to the previous code*/

        if(i == 0) ERROR_BREAK(54); /*can't repeat previous if i is 0*/

        replength += readBits(reader, 2);

        if(i < HLIT + 1) value = bitlen_ll[i - 1];
        else value = bitlen_d[i - HLIT - 1];
        /*repeat this value in the next lengths*/
        for(n = 0; n < replength; ++n) {
          if(i >= HLIT + HDIST) ERROR_BREAK(13); /*error: i is larger than the amount of codes*/
          if(i < HLIT) bitlen_ll[i] = value;
          else bitlen_d[i - HLIT] = value;
          ++i;
        }
      } else if(code == 17) /*repeat "0" 3-10 times*/ {
        unsigned replength = 3; /*read in the bits that indicate repeat length*/
        replength += readBits(reader, 3);

        /*repeat this value in the next lengths*/
        for(n = 0; n < replength; ++n) {
          if(i >= HLIT + HDIST) ERROR_BREAK(14); /*error: i is larger than the amount of codes*/

          if(i < HLIT) bitlen_ll[i] = 0;
          else bitlen_d[i - HLIT] = 0;
          ++i;
        }
      } else if(code == 18) /*repeat "0" 11-138 times*/ {
        unsigned replength = 11; /*read in the bits that indicate repeat length*/
        replength += readBits(reader, 7);

        /*repeat this value in the next lengths*/
        for(n = 0; n < replength; ++n) {
          if(i >= HLIT + HDIST) ERROR_BREAK(15); /*error: i is larger than the amount of codes*/

          if(i < HLIT) bitlen_ll[i] = 0;
          else bitlen_d[i - HLIT] = 0;
          ++i;
        }
      } else /*if(code == INVALIDSYMBOL)*/ {
        ERROR_BREAK(16); /*error: tried to read disallowed huffman symbol*/
      }
      /*check if any of the ensureBits above went out of bounds*/
      if(reader->bp > reader->bitsize) {
        /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol
        (10=no endcode, 11=wrong jump outside of tree)*/
        /* TODO: revise error codes 10,11,50: the above comment is no longer valid */
        ERROR_BREAK(50); /*error, bit pointer jumps past memory*/
      }
    }
    if(error) break;

    if(bitlen_ll[256] == 0) ERROR_BREAK(64); /*the length of the end code 256 must be larger than 0*/

    /*now we've finally got HLIT and HDIST, so generate the code trees, and the function is done*/
    error = HuffmanTree_makeFromLengths(tree_ll, bitlen_ll, NUM_DEFLATE_CODE_SYMBOLS, 15);
    if(error) break;
    error = HuffmanTree_makeFromLengths(tree_d, bitlen_d, NUM_DISTANCE_SYMBOLS, 15);

    break; /*end of error-while*/
  }

  lodepng_free(bitlen_cl);
  lodepng_free(bitlen_ll);
  lodepng_free(bitlen_d);
  HuffmanTree_cleanup(&tree_cl);

  return error;
}

/*inflate a block with dynamic of fixed Huffman tree. btype must be 1 or 2.*/
static unsigned inflateHuffmanBlock(ucvector* out, LodePNGBitReader* reader,
                                    unsigned btype, size_t max_output_size) {
  unsigned error = 0;
  HuffmanTree tree_ll; /*the huffman tree for literal and length codes*/
  HuffmanTree tree_d; /*the huffman tree for distance codes*/
  const size_t reserved_size = 260; /* must be at least 258 for max length, and a few extra for adding a few extra literals */
  int done = 0;

  if(!ucvector_reserve(out, out->size + reserved_size)) return 83; /*alloc fail*/

  HuffmanTree_init(&tree_ll);
  HuffmanTree_init(&tree_d);

  if(btype == 1) error = getTreeInflateFixed(&tree_ll, &tree_d);
  else /*if(btype == 2)*/ error = getTreeInflateDynamic(&tree_ll, &tree_d, reader);


  while(!error && !done) /*decode all symbols until end reached, breaks at end code*/ {
    /*code_ll is literal, length or end code*/
    unsigned code_ll;
    /* ensure enough bits for 2 huffman code reads (15 bits each): if the first is a literal, a second literal is read at once. This
    appears to be slightly faster, than ensuring 20 bits here for 1 huffman symbol and the potential 5 extra bits for the length symbol.*/
    ensureBits32(reader, 30);
    code_ll = huffmanDecodeSymbol(reader, &tree_ll);
    if(code_ll <= 255) {
      /*slightly faster code path if multiple literals in a row*/
      out->data[out->size++] = (unsigned char)code_ll;
      code_ll = huffmanDecodeSymbol(reader, &tree_ll);
    }
    if(code_ll <= 255) /*literal symbol*/ {
      out->data[out->size++] = (unsigned char)code_ll;
    } else if(code_ll >= FIRST_LENGTH_CODE_INDEX && code_ll <= LAST_LENGTH_CODE_INDEX) /*length code*/ {
      unsigned code_d, distance;
      unsigned numextrabits_l, numextrabits_d; /*extra bits for length and distance*/
      size_t start, backward, length;

      /*part 1: get length base*/
      length = LENGTHBASE[code_ll - FIRST_LENGTH_CODE_INDEX];

      /*part 2: get extra bits and add the value of that to length*/
      numextrabits_l = LENGTHEXTRA[code_ll - FIRST_LENGTH_CODE_INDEX];
      if(numextrabits_l != 0) {
        /* bits already ensured above */
        ensureBits25(reader, 5);
        length += readBits(reader, numextrabits_l);
      }

      /*part 3: get distance code*/
      ensureBits32(reader, 28); /* up to 15 for the huffman symbol, up to 13 for the extra bits */
      code_d = huffmanDecodeSymbol(reader, &tree_d);
      if(code_d > 29) {
        if(code_d <= 31) {
          ERROR_BREAK(18); /*error: invalid distance code (30-31 are never used)*/
        } else /* if(code_d == INVALIDSYMBOL) */{
          ERROR_BREAK(16); /*error: tried to read disallowed huffman symbol*/
        }
      }
      distance = DISTANCEBASE[code_d];

      /*part 4: get extra bits from distance*/
      numextrabits_d = DISTANCEEXTRA[code_d];
      if(numextrabits_d != 0) {
        /* bits already ensured above */
        distance += readBits(reader, numextrabits_d);
      }

      /*part 5: fill in all the out[n] values based on the length and dist*/
      start = out->size;
      if(distance > start) ERROR_BREAK(52); /*too long backward distance*/
      backward = start - distance;

      out->size += length;
      if(distance < length) {
        size_t forward;
        lodepng_memcpy(out->data + start, out->data + backward, distance);
        start += distance;
        for(forward = distance; forward < length; ++forward) {
          out->data[start++] = out->data[backward++];
        }
      } else {
        lodepng_memcpy(out->data + start, out->data + backward, length);
      }
    } else if(code_ll == 256) {
      done = 1; /*end code, finish the loop*/
    } else /*if(code_ll == INVALIDSYMBOL)*/ {
      ERROR_BREAK(16); /*error: tried to read disallowed huffman symbol*/
    }
    if(out->allocsize - out->size < reserved_size) {
      if(!ucvector_reserve(out, out->size + reserved_size)) ERROR_BREAK(83); /*alloc fail*/
    }
    /*check if any of the ensureBits above went out of bounds*/
    if(reader->bp > reader->bitsize) {
      /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol
      (10=no endcode, 11=wrong jump outside of tree)*/
      /* TODO: revise error codes 10,11,50: the above comment is no longer valid */
      ERROR_BREAK(51); /*error, bit pointer jumps past memory*/
    }
    if(max_output_size && out->size > max_output_size) {
      ERROR_BREAK(109); /*error, larger than max size*/
    }
  }

  HuffmanTree_cleanup(&tree_ll);
  HuffmanTree_cleanup(&tree_d);

  return error;
}

static unsigned inflateNoCompression(ucvector* out, LodePNGBitReader* reader,
                                     const LodePNGDecompressSettings* settings) {
  size_t bytepos;
  size_t size = reader->size;
  unsigned LEN, NLEN, error = 0;

  /*go to first boundary of byte*/
  bytepos = (reader->bp + 7u) >> 3u;

  /*read LEN (2 bytes) and NLEN (2 bytes)*/
  if(bytepos + 4 >= size) return 52; /*error, bit pointer will jump past memory*/
  LEN = (unsigned)reader->data[bytepos] + ((unsigned)reader->data[bytepos + 1] << 8u); bytepos += 2;
  NLEN = (unsigned)reader->data[bytepos] + ((unsigned)reader->data[bytepos + 1] << 8u); bytepos += 2;

  /*check if 16-bit NLEN is really the one's complement of LEN*/
  if(!settings->ignore_nlen && LEN + NLEN != 65535) {
    return 21; /*error: NLEN is not one's complement of LEN*/
  }

  if(!ucvector_resize(out, out->size + LEN)) return 83; /*alloc fail*/

  /*read the literal data: LEN bytes are now stored in the out buffer*/
  if(bytepos + LEN > size) return 23; /*error: reading outside of in buffer*/

  /*out->data can be NULL (when LEN is zero), and arithmetic on NULL ptr is undefined*/
  if (LEN) {
    lodepng_memcpy(out->data + out->size - LEN, reader->data + bytepos, LEN);
    bytepos += LEN;
  }

  reader->bp = bytepos << 3u;

  return error;
}

static unsigned lodepng_inflatev(ucvector* out,
                                 const unsigned char* in, size_t insize,
                                 const LodePNGDecompressSettings* settings) {
  unsigned BFINAL = 0;
  LodePNGBitReader reader;
  unsigned error = LodePNGBitReader_init(&reader, in, insize);

  if(error) return error;

  while(!BFINAL) {
    unsigned BTYPE;
    if(reader.bitsize - reader.bp < 3) return 52; /*error, bit pointer will jump past memory*/
    ensureBits9(&reader, 3);
    BFINAL = readBits(&reader, 1);
    BTYPE = readBits(&reader, 2);

    if(BTYPE == 3) return 20; /*error: invalid BTYPE*/
    else if(BTYPE == 0) error = inflateNoCompression(out, &reader, settings); /*no compression*/
    else error = inflateHuffmanBlock(out, &reader, BTYPE, settings->max_output_size); /*compression, BTYPE 01 or 10*/
    if(!error && settings->max_output_size && out->size > settings->max_output_size) error = 109;
    if(error) break;
  }

  return error;
}

unsigned lodepng_inflate(unsigned char** out, size_t* outsize,
                         const unsigned char* in, size_t insize,
                         const LodePNGDecompressSettings* settings) {
  ucvector v = ucvector_init(*out, *outsize);
  unsigned error = lodepng_inflatev(&v, in, insize, settings);
  *out = v.data;
  *outsize = v.size;
  return error;
}

static unsigned inflatev(ucvector* out, const unsigned char* in, size_t insize,
                        const LodePNGDecompressSettings* settings) {
  if(settings->custom_inflate) {
    unsigned error = settings->custom_inflate(&out->data, &out->size, in, insize, settings);
    out->allocsize = out->size;
    if(error) {
      /*the custom inflate is allowed to have its own error codes, however, we translate it to code 110*/
      error = 110;
      /*if there's a max output size, and the custom zlib returned error, then indicate that error instead*/
      if(settings->max_output_size && out->size > settings->max_output_size) error = 109;
    }
    return error;
  } else {
    return lodepng_inflatev(out, in, insize, settings);
  }
}

#endif /*LODEPNG_COMPILE_DECODER*/

#ifdef LODEPNG_COMPILE_ENCODER

/* ////////////////////////////////////////////////////////////////////////// */
/* / Deflator (Compressor)                                                  / */
/* ////////////////////////////////////////////////////////////////////////// */

static const unsigned MAX_SUPPORTED_DEFLATE_LENGTH = 258;

/*search the index in the array, that has the largest value smaller than or equal to the given value,
given array must be sorted (if no value is smaller, it returns the size of the given array)*/
static size_t searchCodeIndex(const unsigned* array, size_t array_size, size_t value) {
  /*binary search (only small gain over linear). TODO: use CPU log2 instruction for getting symbols instead*/
  size_t left = 1;
  size_t right = array_size - 1;

  while(left <= right) {
    size_t mid = (left + right) >> 1;
    if(array[mid] >= value) right = mid - 1;
    else left = mid + 1;
  }
  if(left >= array_size || array[left] > value) left--;
  return left;
}

static void addLengthDistance(uivector* values, size_t length, size_t distance) {
  /*values in encoded vector are those used by deflate:
  0-255: literal bytes
  256: end
  257-285: length/distance pair (length code, followed by extra length bits, distance code, extra distance bits)
  286-287: invalid*/

  unsigned length_code = (unsigned)searchCodeIndex(LENGTHBASE, 29, length);
  unsigned extra_length = (unsigned)(length - LENGTHBASE[length_code]);
  unsigned dist_code = (unsigned)searchCodeIndex(DISTANCEBASE, 30, distance);
  unsigned extra_distance = (unsigned)(distance - DISTANCEBASE[dist_code]);

  size_t pos = values->size;
  /*TODO: return error when this fails (out of memory)*/
  unsigned ok = uivector_resize(values, values->size + 4);
  if(ok) {
    values->data[pos + 0] = length_code + FIRST_LENGTH_CODE_INDEX;
    values->data[pos + 1] = extra_length;
    values->data[pos + 2] = dist_code;
    values->data[pos + 3] = extra_distance;
  }
}

/*3 bytes of data get encoded into two bytes. The hash cannot use more than 3
bytes as input because 3 is the minimum match length for deflate*/
static const unsigned HASH_NUM_VALUES = 65536;
static const unsigned HASH_BIT_MASK = 65535; /*HASH_NUM_VALUES - 1, but C90 does not like that as initializer*/

typedef struct Hash {
  int* head; /*hash value to head circular pos - can be outdated if went around window*/
  /*circular pos to prev circular pos*/
  unsigned short* chain;
  int* val; /*circular pos to hash value*/

  /*TODO: do this not only for zeros but for any repeated byte. However for PNG
  it's always going to be the zeros that dominate, so not important for PNG*/
  int* headz; /*similar to head, but for chainz*/
  unsigned short* chainz; /*those with same amount of zeros*/
  unsigned short* zeros; /*length of zeros streak, used as a second hash chain*/
} Hash;

static unsigned hash_init(Hash* hash, unsigned windowsize) {
  unsigned i;
  hash->head = (int*)lodepng_malloc(sizeof(int) * HASH_NUM_VALUES);
  hash->val = (int*)lodepng_malloc(sizeof(int) * windowsize);
  hash->chain = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize);

  hash->zeros = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize);
  hash->headz = (int*)lodepng_malloc(sizeof(int) * (MAX_SUPPORTED_DEFLATE_LENGTH + 1));
  hash->chainz = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize);

  if(!hash->head || !hash->chain || !hash->val  || !hash->headz|| !hash->chainz || !hash->zeros) {
    return 83; /*alloc fail*/
  }

  /*initialize hash table*/
  for(i = 0; i != HASH_NUM_VALUES; ++i) hash->head[i] = -1;
  for(i = 0; i != windowsize; ++i) hash->val[i] = -1;
  for(i = 0; i != windowsize; ++i) hash->chain[i] = i; /*same value as index indicates uninitialized*/

  for(i = 0; i <= MAX_SUPPORTED_DEFLATE_LENGTH; ++i) hash->headz[i] = -1;
  for(i = 0; i != windowsize; ++i) hash->chainz[i] = i; /*same value as index indicates uninitialized*/

  return 0;
}

static void hash_cleanup(Hash* hash) {
  lodepng_free(hash->head);
  lodepng_free(hash->val);
  lodepng_free(hash->chain);

  lodepng_free(hash->zeros);
  lodepng_free(hash->headz);
  lodepng_free(hash->chainz);
}



static unsigned getHash(const unsigned char* data, size_t size, size_t pos) {
  unsigned result = 0;
  if(pos + 2 < size) {
    /*A simple shift and xor hash is used. Since the data of PNGs is dominated
    by zeroes due to the filters, a better hash does not have a significant
    effect on speed in traversing the chain, and causes more time spend on
    calculating the hash.*/
    result ^= ((unsigned)data[pos + 0] << 0u);
    result ^= ((unsigned)data[pos + 1] << 4u);
    result ^= ((unsigned)data[pos + 2] << 8u);
  } else {
    size_t amount, i;
    if(pos >= size) return 0;
    amount = size - pos;
    for(i = 0; i != amount; ++i) result ^= ((unsigned)data[pos + i] << (i * 8u));
  }
  return result & HASH_BIT_MASK;
}

#if LODEPNG_PDVZIP_SSE2
static unsigned countLowOneBits16(unsigned mask) {
  unsigned count = 0;
  while(mask & 1u) {
    ++count;
    mask >>= 1u;
  }
  return count;
}
#endif

static unsigned countZeros(const unsigned char* data, size_t size, size_t pos) {
  const unsigned char* start = data + pos;
  const unsigned char* end = start + MAX_SUPPORTED_DEFLATE_LENGTH;
  if(end > data + size) end = data + size;
  data = start;
#if LODEPNG_PDVZIP_SSE2
  {
    const __m128i zero = _mm_setzero_si128();
    while((size_t)(end - data) >= 16) {
      const __m128i bytes = _mm_loadu_si128((const __m128i*)data);
      const unsigned mask = (unsigned)_mm_movemask_epi8(_mm_cmpeq_epi8(bytes, zero));
      if(mask != 0xffffu) {
        return (unsigned)(data - start) + countLowOneBits16(mask);
      }
      data += 16;
    }
  }
#endif
  while(data != end && *data == 0) ++data;
  /*subtracting two addresses returned as 32-bit number (max value is MAX_SUPPORTED_DEFLATE_LENGTH)*/
  return (unsigned)(data - start);
}

static unsigned countMatchingBytes(const unsigned char* foreptr, const unsigned char* backptr,
                                   const unsigned char* lastptr) {
  const unsigned char* start = foreptr;
#if LODEPNG_PDVZIP_SSE2
  while((size_t)(lastptr - foreptr) >= 16) {
    const __m128i front = _mm_loadu_si128((const __m128i*)foreptr);
    const __m128i back = _mm_loadu_si128((const __m128i*)backptr);
    const unsigned mask = (unsigned)_mm_movemask_epi8(_mm_cmpeq_epi8(front, back));
    if(mask != 0xffffu) {
      return (unsigned)(foreptr - start) + countLowOneBits16(mask);
    }
    foreptr += 16;
    backptr += 16;
  }
#endif
  while(foreptr != lastptr && *backptr == *foreptr) {
    ++backptr;
    ++foreptr;
  }
  return (unsigned)(foreptr - start);
}


/*wpos = pos & (windowsize - 1)*/
static void updateHashChain(Hash* hash, size_t wpos, unsigned hashval, unsigned short numzeros) {
  hash->val[wpos] = (int)hashval;
  if(hash->head[hashval] != -1) hash->chain[wpos] = hash->head[hashval];
  hash->head[hashval] = (int)wpos;

  hash->zeros[wpos] = numzeros;
  if(hash->headz[numzeros] != -1) hash->chainz[wpos] = hash->headz[numzeros];
  hash->headz[numzeros] = (int)wpos;
}

/*
LZ77-encode the data. Return value is error code. The input are raw bytes, the output
is in the form of unsigned integers with codes representing for example literal bytes, or
length/distance pairs.
It uses a hash table technique to let it encode faster. When doing LZ77 encoding, a
sliding window (of windowsize) is used, and all past bytes in that window can be used as
the "dictionary". A brute force search through all possible distances would be slow, and
this hash technique is one out of several ways to speed this up.
*/
static unsigned encodeLZ77(uivector* out, Hash* hash,
                           const unsigned char* in, size_t inpos, size_t insize, unsigned windowsize,
                           unsigned minmatch, unsigned nicematch, unsigned lazymatching) {
  size_t pos;
  unsigned i, error = 0;
  /*for large window lengths, assume the user wants no compression loss. Otherwise, max hash chain length speedup.*/
  unsigned maxchainlength = windowsize >= 8192 ? windowsize : windowsize / 8u;
  unsigned maxlazymatch = windowsize >= 8192 ? MAX_SUPPORTED_DEFLATE_LENGTH : 64;

  unsigned usezeros = 1; /*not sure if setting it to false for windowsize < 8192 is better or worse*/
  unsigned numzeros = 0;

  unsigned offset; /*the offset represents the distance in LZ77 terminology*/
  unsigned length;
  unsigned lazy = 0;
  unsigned lazylength = 0, lazyoffset = 0;
  unsigned hashval;
  unsigned current_offset, current_length;
  unsigned prev_offset;
  const unsigned char *lastptr, *foreptr, *backptr;
  unsigned hashpos;

  if(windowsize == 0 || windowsize > 32768) return 60; /*error: windowsize smaller/larger than allowed*/
  if((windowsize & (windowsize - 1)) != 0) return 90; /*error: must be power of two*/

  if(nicematch > MAX_SUPPORTED_DEFLATE_LENGTH) nicematch = MAX_SUPPORTED_DEFLATE_LENGTH;

  for(pos = inpos; pos < insize; ++pos) {
    size_t wpos = pos & (windowsize - 1); /*position for in 'circular' hash buffers*/
    unsigned chainlength = 0;

    hashval = getHash(in, insize, pos);

    if(usezeros && hashval == 0) {
      if(numzeros == 0) numzeros = countZeros(in, insize, pos);
      else if(pos + numzeros > insize || in[pos + numzeros - 1] != 0) --numzeros;
    } else {
      numzeros = 0;
    }

    updateHashChain(hash, wpos, hashval, numzeros);

    /*the length and offset found for the current position*/
    length = 0;
    offset = 0;

    hashpos = hash->chain[wpos];

    lastptr = &in[insize < pos + MAX_SUPPORTED_DEFLATE_LENGTH ? insize : pos + MAX_SUPPORTED_DEFLATE_LENGTH];

    /*search for the longest string*/
    prev_offset = 0;
    for(;;) {
      if(chainlength++ >= maxchainlength) break;
      current_offset = (unsigned)(hashpos <= wpos ? wpos - hashpos : wpos - hashpos + windowsize);

      if(current_offset < prev_offset) break; /*stop when went completely around the circular buffer*/
      prev_offset = current_offset;
      if(current_offset > 0) {
        /*test the next characters*/
        foreptr = &in[pos];
        backptr = &in[pos - current_offset];

        /*common case in PNGs is lots of zeros. Quickly skip over them as a speedup*/
        if(numzeros >= 3) {
          unsigned skip = hash->zeros[hashpos];
          if(skip > numzeros) skip = numzeros;
          backptr += skip;
          foreptr += skip;
        }

        foreptr += countMatchingBytes(foreptr, backptr, lastptr);
        current_length = (unsigned)(foreptr - &in[pos]);

        if(current_length > length) {
          length = current_length; /*the longest length*/
          offset = current_offset; /*the offset that is related to this longest length*/
          /*jump out once a length of max length is found (speed gain). This also jumps
          out if length is MAX_SUPPORTED_DEFLATE_LENGTH*/
          if(current_length >= nicematch) break;
        }
      }

      if(hashpos == hash->chain[hashpos]) break;

      if(numzeros >= 3 && length > numzeros) {
        hashpos = hash->chainz[hashpos];
        if(hash->zeros[hashpos] != numzeros) break;
      } else {
        hashpos = hash->chain[hashpos];
        /*outdated hash value, happens if particular value was not encountered in whole last window*/
        if(hash->val[hashpos] != (int)hashval) break;
      }
    }

    if(lazymatching) {
      if(!lazy && length >= 3 && length <= maxlazymatch && length < MAX_SUPPORTED_DEFLATE_LENGTH) {
        lazy = 1;
        lazylength = length;
        lazyoffset = offset;
        continue; /*try the next byte*/
      }
      if(lazy) {
        lazy = 0;
        if(pos == 0) ERROR_BREAK(81);
        if(length > lazylength + 1) {
          /*push the previous character as literal*/
          if(!uivector_push_back(out, in[pos - 1])) ERROR_BREAK(83 /*alloc fail*/);
        } else {
          length = lazylength;
          offset = lazyoffset;
          hash->head[hashval] = -1; /*the same hashchain update will be done, this ensures no wrong alteration*/
          hash->headz[numzeros] = -1; /*idem*/
          --pos;
        }
      }
    }
    if(length >= 3 && offset > windowsize) ERROR_BREAK(86 /*too big (or overflown negative) offset*/);

    /*encode it as length/distance pair or literal value*/
    if(length < 3) /*only lengths of 3 or higher are supported as length/distance pair*/ {
      if(!uivector_push_back(out, in[pos])) ERROR_BREAK(83 /*alloc fail*/);
    } else if(length < minmatch || (length == 3 && offset > 4096)) {
      /*compensate for the fact that longer offsets have more extra bits, a
      length of only 3 may be not worth it then*/
      if(!uivector_push_back(out, in[pos])) ERROR_BREAK(83 /*alloc fail*/);
    } else {
      addLengthDistance(out, length, offset);
      for(i = 1; i < length; ++i) {
        ++pos;
        wpos = pos & (windowsize - 1);
        hashval = getHash(in, insize, pos);
        if(usezeros && hashval == 0) {
          if(numzeros == 0) numzeros = countZeros(in, insize, pos);
          else if(pos + numzeros > insize || in[pos + numzeros - 1] != 0) --numzeros;
        } else {
          numzeros = 0;
        }
        updateHashChain(hash, wpos, hashval, numzeros);
      }
    }
  } /*end of the loop through each character of input*/

  return error;
}

/* /////////////////////////////////////////////////////////////////////////// */

static unsigned deflateNoCompression(ucvector* out, const unsigned char* data, size_t datasize) {
  /*non compressed deflate block data: 1 bit BFINAL,2 bits BTYPE,(5 bits): it jumps to start of next byte,
  2 bytes LEN, 2 bytes NLEN, LEN bytes literal DATA*/

  size_t i, numdeflateblocks = (datasize + 65534u) / 65535u;
  size_t datapos = 0;
  for(i = 0; i != numdeflateblocks; ++i) {
    unsigned BFINAL, BTYPE, LEN, NLEN;
    unsigned char firstbyte;
    size_t pos = out->size;

    BFINAL = (i == numdeflateblocks - 1);
    BTYPE = 0;

    LEN = 65535;
    if(datasize - datapos < 65535u) LEN = (unsigned)datasize - (unsigned)datapos;
    NLEN = 65535 - LEN;

    if(!ucvector_resize(out, out->size + LEN + 5)) return 83; /*alloc fail*/

    firstbyte = (unsigned char)(BFINAL + ((BTYPE & 1u) << 1u) + ((BTYPE & 2u) << 1u));
    out->data[pos + 0] = firstbyte;
    out->data[pos + 1] = (unsigned char)(LEN & 255);
    out->data[pos + 2] = (unsigned char)(LEN >> 8u);
    out->data[pos + 3] = (unsigned char)(NLEN & 255);
    out->data[pos + 4] = (unsigned char)(NLEN >> 8u);
    lodepng_memcpy(out->data + pos + 5, data + datapos, LEN);
    datapos += LEN;
  }

  return 0;
}

/*
write the lz77-encoded data, which has lit, len and dist codes, to compressed stream using huffman trees.
tree_ll: the tree for lit and len codes.
tree_d: the tree for distance codes.
*/
static void writeLZ77data(LodePNGBitWriter* writer, const uivector* lz77_encoded,
                          const HuffmanTree* tree_ll, const HuffmanTree* tree_d) {
  size_t i = 0;
  for(i = 0; i != lz77_encoded->size; ++i) {
    unsigned val = lz77_encoded->data[i];
    writeBitsReversed(writer, tree_ll->codes[val], tree_ll->lengths[val]);
    if(val > 256) /*for a length code, 3 more things have to be added*/ {
      unsigned length_index = val - FIRST_LENGTH_CODE_INDEX;
      unsigned n_length_extra_bits = LENGTHEXTRA[length_index];
      unsigned length_extra_bits = lz77_encoded->data[++i];

      unsigned distance_code = lz77_encoded->data[++i];

      unsigned distance_index = distance_code;
      unsigned n_distance_extra_bits = DISTANCEEXTRA[distance_index];
      unsigned distance_extra_bits = lz77_encoded->data[++i];

      writeBits(writer, length_extra_bits, n_length_extra_bits);
      writeBitsReversed(writer, tree_d->codes[distance_code], tree_d->lengths[distance_code]);
      writeBits(writer, distance_extra_bits, n_distance_extra_bits);
    }
  }
}

/*Deflate for a block of type "dynamic", that is, with freely, optimally, created huffman trees*/
static unsigned deflateDynamic(LodePNGBitWriter* writer, Hash* hash,
                               const unsigned char* data, size_t datapos, size_t dataend,
                               const LodePNGCompressSettings* settings, unsigned final) {
  unsigned error = 0;

  /*
  A block is compressed as follows: The PNG data is lz77 encoded, resulting in
  literal bytes and length/distance pairs. This is then huffman compressed with
  two huffman trees. One huffman tree is used for the lit and len values ("ll"),
  another huffman tree is used for the dist values ("d"). These two trees are
  stored using their code lengths, and to compress even more these code lengths
  are also run-length encoded and huffman compressed. This gives a huffman tree
  of code lengths "cl". The code lengths used to describe this third tree are
  the code length code lengths ("clcl").
  */

  /*The lz77 encoded data, represented with integers since there will also be length and distance codes in it*/
  uivector lz77_encoded;
  HuffmanTree tree_ll; /*tree for lit,len values*/
  HuffmanTree tree_d; /*tree for distance codes*/
  HuffmanTree tree_cl; /*tree for encoding the code lengths representing tree_ll and tree_d*/
  unsigned* frequencies_ll = 0; /*frequency of lit,len codes*/
  unsigned* frequencies_d = 0; /*frequency of dist codes*/
  unsigned* frequencies_cl = 0; /*frequency of code length codes*/
  unsigned* bitlen_lld = 0; /*lit,len,dist code lengths (int bits), literally (without repeat codes).*/
  unsigned* bitlen_lld_e = 0; /*bitlen_lld encoded with repeat codes (this is a rudimentary run length compression)*/
  size_t datasize = dataend - datapos;

  /*
  If we could call "bitlen_cl" the the code length code lengths ("clcl"), that is the bit lengths of codes to represent
  tree_cl in CLCL_ORDER, then due to the huffman compression of huffman tree representations ("two levels"), there are
  some analogies:
  bitlen_lld is to tree_cl what data is to tree_ll and tree_d.
  bitlen_lld_e is to bitlen_lld what lz77_encoded is to data.
  bitlen_cl is to bitlen_lld_e what bitlen_lld is to lz77_encoded.
  */

  unsigned BFINAL = final;
  size_t i;
  size_t numcodes_ll, numcodes_d, numcodes_lld, numcodes_lld_e, numcodes_cl;
  unsigned HLIT, HDIST, HCLEN;

  uivector_init(&lz77_encoded);
  HuffmanTree_init(&tree_ll);
  HuffmanTree_init(&tree_d);
  HuffmanTree_init(&tree_cl);
  /* could fit on stack, but >1KB is on the larger side so allocate instead */
  frequencies_ll = (unsigned*)lodepng_malloc(286 * sizeof(*frequencies_ll));
  frequencies_d = (unsigned*)lodepng_malloc(30 * sizeof(*frequencies_d));
  frequencies_cl = (unsigned*)lodepng_malloc(NUM_CODE_LENGTH_CODES * sizeof(*frequencies_cl));

  if(!frequencies_ll || !frequencies_d || !frequencies_cl) error = 83; /*alloc fail*/

  /*This while loop never loops due to a break at the end, it is here to
  allow breaking out of it to the cleanup phase on error conditions.*/
  while(!error) {
    lodepng_memset(frequencies_ll, 0, 286 * sizeof(*frequencies_ll));
    lodepng_memset(frequencies_d, 0, 30 * sizeof(*frequencies_d));
    lodepng_memset(frequencies_cl, 0, NUM_CODE_LENGTH_CODES * sizeof(*frequencies_cl));

    if(settings->use_lz77) {
      error = encodeLZ77(&lz77_encoded, hash, data, datapos, dataend, settings->windowsize,
                         settings->minmatch, settings->nicematch, settings->lazymatching);
      if(error) break;
    } else {
      if(!uivector_resize(&lz77_encoded, datasize)) ERROR_BREAK(83 /*alloc fail*/);
      for(i = datapos; i < dataend; ++i) lz77_encoded.data[i - datapos] = data[i]; /*no LZ77, but still will be Huffman compressed*/
    }

    /*Count the frequencies of lit, len and dist codes*/
    for(i = 0; i != lz77_encoded.size; ++i) {
      unsigned symbol = lz77_encoded.data[i];
      ++frequencies_ll[symbol];
      if(symbol > 256) {
        unsigned dist = lz77_encoded.data[i + 2];
        ++frequencies_d[dist];
        i += 3;
      }
    }
    frequencies_ll[256] = 1; /*there will be exactly 1 end code, at the end of the block*/

    /*Make both huffman trees, one for the lit and len codes, one for the dist codes*/
    error = HuffmanTree_makeFromFrequencies(&tree_ll, frequencies_ll, 257, 286, 15);
    if(error) break;
    /*2, not 1, is chosen for mincodes: some buggy PNG decoders require at least 2 symbols in the dist tree*/
    error = HuffmanTree_makeFromFrequencies(&tree_d, frequencies_d, 2, 30, 15);
    if(error) break;

    numcodes_ll = LODEPNG_MIN(tree_ll.numcodes, 286);
    numcodes_d = LODEPNG_MIN(tree_d.numcodes, 30);
    /*store the code lengths of both generated trees in bitlen_lld*/
    numcodes_lld = numcodes_ll + numcodes_d;
    bitlen_lld = (unsigned*)lodepng_malloc(numcodes_lld * sizeof(*bitlen_lld));
    /*numcodes_lld_e never needs more size than bitlen_lld*/
    bitlen_lld_e = (unsigned*)lodepng_malloc(numcodes_lld * sizeof(*bitlen_lld_e));
    if(!bitlen_lld || !bitlen_lld_e) ERROR_BREAK(83); /*alloc fail*/
    numcodes_lld_e = 0;

    for(i = 0; i != numcodes_ll; ++i) bitlen_lld[i] = tree_ll.lengths[i];
    for(i = 0; i != numcodes_d; ++i) bitlen_lld[numcodes_ll + i] = tree_d.lengths[i];

    /*run-length compress bitlen_ldd into bitlen_lld_e by using repeat codes 16 (copy length 3-6 times),
    17 (3-10 zeroes), 18 (11-138 zeroes)*/
    for(i = 0; i != numcodes_lld; ++i) {
      unsigned j = 0; /*amount of repetitions*/
      while(i + j + 1 < numcodes_lld && bitlen_lld[i + j + 1] == bitlen_lld[i]) ++j;

      if(bitlen_lld[i] == 0 && j >= 2) /*repeat code for zeroes*/ {
        ++j; /*include the first zero*/
        if(j <= 10) /*repeat code 17 supports max 10 zeroes*/ {
          bitlen_lld_e[numcodes_lld_e++] = 17;
          bitlen_lld_e[numcodes_lld_e++] = j - 3;
        } else /*repeat code 18 supports max 138 zeroes*/ {
          if(j > 138) j = 138;
          bitlen_lld_e[numcodes_lld_e++] = 18;
          bitlen_lld_e[numcodes_lld_e++] = j - 11;
        }
        i += (j - 1);
      } else if(j >= 3) /*repeat code for value other than zero*/ {
        size_t k;
        unsigned num = j / 6u, rest = j % 6u;
        bitlen_lld_e[numcodes_lld_e++] = bitlen_lld[i];
        for(k = 0; k < num; ++k) {
          bitlen_lld_e[numcodes_lld_e++] = 16;
          bitlen_lld_e[numcodes_lld_e++] = 6 - 3;
        }
        if(rest >= 3) {
          bitlen_lld_e[numcodes_lld_e++] = 16;
          bitlen_lld_e[numcodes_lld_e++] = rest - 3;
        }
        else j -= rest;
        i += j;
      } else /*too short to benefit from repeat code*/ {
        bitlen_lld_e[numcodes_lld_e++] = bitlen_lld[i];
      }
    }

    /*generate tree_cl, the huffmantree of huffmantrees*/
    for(i = 0; i != numcodes_lld_e; ++i) {
      ++frequencies_cl[bitlen_lld_e[i]];
      /*after a repeat code come the bits that specify the number of repetitions,
      those don't need to be in the frequencies_cl calculation*/
      if(bitlen_lld_e[i] >= 16) ++i;
    }

    error = HuffmanTree_makeFromFrequencies(&tree_cl, frequencies_cl,
                                            NUM_CODE_LENGTH_CODES, NUM_CODE_LENGTH_CODES, 7);
    if(error) break;

    /*compute amount of code-length-code-lengths to output*/
    numcodes_cl = NUM_CODE_LENGTH_CODES;
    /*trim zeros at the end (using CLCL_ORDER), but minimum size must be 4 (see HCLEN below)*/
    while(numcodes_cl > 4u && tree_cl.lengths[CLCL_ORDER[numcodes_cl - 1u]] == 0) {
      numcodes_cl--;
    }

    /*
    Write everything into the output

    After the BFINAL and BTYPE, the dynamic block consists out of the following:
    - 5 bits HLIT, 5 bits HDIST, 4 bits HCLEN
    - (HCLEN+4)*3 bits code lengths of code length alphabet
    - HLIT + 257 code lengths of lit/length alphabet (encoded using the code length
      alphabet, + possible repetition codes 16, 17, 18)
    - HDIST + 1 code lengths of distance alphabet (encoded using the code length
      alphabet, + possible repetition codes 16, 17, 18)
    - compressed data
    - 256 (end code)
    */

    /*Write block type*/
    writeBits(writer, BFINAL, 1);
    writeBits(writer, 0, 1); /*first bit of BTYPE "dynamic"*/
    writeBits(writer, 1, 1); /*second bit of BTYPE "dynamic"*/

    /*write the HLIT, HDIST and HCLEN values*/
    /*all three sizes take trimmed ending zeroes into account, done either by HuffmanTree_makeFromFrequencies
    or in the loop for numcodes_cl above, which saves space. */
    HLIT = (unsigned)(numcodes_ll - 257);
    HDIST = (unsigned)(numcodes_d - 1);
    HCLEN = (unsigned)(numcodes_cl - 4);
    writeBits(writer, HLIT, 5);
    writeBits(writer, HDIST, 5);
    writeBits(writer, HCLEN, 4);

    /*write the code lengths of the code length alphabet ("bitlen_cl")*/
    for(i = 0; i != numcodes_cl; ++i) writeBits(writer, tree_cl.lengths[CLCL_ORDER[i]], 3);

    /*write the lengths of the lit/len AND the dist alphabet*/
    for(i = 0; i != numcodes_lld_e; ++i) {
      writeBitsReversed(writer, tree_cl.codes[bitlen_lld_e[i]], tree_cl.lengths[bitlen_lld_e[i]]);
      /*extra bits of repeat codes*/
      if(bitlen_lld_e[i] == 16) writeBits(writer, bitlen_lld_e[++i], 2);
      else if(bitlen_lld_e[i] == 17) writeBits(writer, bitlen_lld_e[++i], 3);
      else if(bitlen_lld_e[i] == 18) writeBits(writer, bitlen_lld_e[++i], 7);
    }

    /*write the compressed data symbols*/
    writeLZ77data(writer, &lz77_encoded, &tree_ll, &tree_d);
    /*error: the length of the end code 256 must be larger than 0*/
    if(tree_ll.lengths[256] == 0) ERROR_BREAK(64);

    /*write the end code*/
    writeBitsReversed(writer, tree_ll.codes[256], tree_ll.lengths[256]);

    break; /*end of error-while*/
  }

  /*cleanup*/
  uivector_cleanup(&lz77_encoded);
  HuffmanTree_cleanup(&tree_ll);
  HuffmanTree_cleanup(&tree_d);
  HuffmanTree_cleanup(&tree_cl);
  lodepng_free(frequencies_ll);
  lodepng_free(frequencies_d);
  lodepng_free(frequencies_cl);
  lodepng_free(bitlen_lld);
  lodepng_free(bitlen_lld_e);

  return error;
}

static unsigned deflateFixed(LodePNGBitWriter* writer, Hash* hash,
                             const unsigned char* data,
                             size_t datapos, size_t dataend,
                             const LodePNGCompressSettings* settings, unsigned final) {
  HuffmanTree tree_ll; /*tree for literal values and length codes*/
  HuffmanTree tree_d; /*tree for distance codes*/

  unsigned BFINAL = final;
  unsigned error = 0;
  size_t i;

  HuffmanTree_init(&tree_ll);
  HuffmanTree_init(&tree_d);

  error = generateFixedLitLenTree(&tree_ll);
  if(!error) error = generateFixedDistanceTree(&tree_d);

  if(!error) {
    writeBits(writer, BFINAL, 1);
    writeBits(writer, 1, 1); /*first bit of BTYPE*/
    writeBits(writer, 0, 1); /*second bit of BTYPE*/

    if(settings->use_lz77) /*LZ77 encoded*/ {
      uivector lz77_encoded;
      uivector_init(&lz77_encoded);
      error = encodeLZ77(&lz77_encoded, hash, data, datapos, dataend, settings->windowsize,
                         settings->minmatch, settings->nicematch, settings->lazymatching);
      if(!error) writeLZ77data(writer, &lz77_encoded, &tree_ll, &tree_d);
      uivector_cleanup(&lz77_encoded);
    } else /*no LZ77, but still will be Huffman compressed*/ {
      for(i = datapos; i < dataend; ++i) {
        writeBitsReversed(writer, tree_ll.codes[data[i]], tree_ll.lengths[data[i]]);
      }
    }
    /*add END code*/
    if(!error) writeBitsReversed(writer,tree_ll.codes[256], tree_ll.lengths[256]);
  }

  /*cleanup*/
  HuffmanTree_cleanup(&tree_ll);
  HuffmanTree_cleanup(&tree_d);

  return error;
}

static unsigned lodepng_deflatev(ucvector* out, const unsigned char* in, size_t insize,
                                 const LodePNGCompressSettings* settings) {
  unsigned error = 0;
  size_t i, blocksize, numdeflateblocks;
  Hash hash;
  LodePNGBitWriter writer;

  LodePNGBitWriter_init(&writer, out);

  if(settings->btype > 2) return 61;
  else if(settings->btype == 0) return deflateNoCompression(out, in, insize);
  else if(settings->btype == 1) blocksize = insize;
  else /*if(settings->btype == 2)*/ {
    /*on PNGs, deflate blocks of 65-262k seem to give most dense encoding*/
    blocksize = insize / 8u + 8;
    if(blocksize < 65536) blocksize = 65536;
    if(blocksize > 262144) blocksize = 262144;
  }

  numdeflateblocks = (insize + blocksize - 1) / blocksize;
  if(numdeflateblocks == 0) numdeflateblocks = 1;

  error = hash_init(&hash, settings->windowsize);

  if(!error) {
    for(i = 0; i != numdeflateblocks && !error; ++i) {
      unsigned final = (i == numdeflateblocks - 1);
      size_t start = i * blocksize;
      size_t end = start + blocksize;
      if(end > insize) end = insize;

      if(settings->btype == 1) error = deflateFixed(&writer, &hash, in, start, end, settings, final);
      else if(settings->btype == 2) error = deflateDynamic(&writer, &hash, in, start, end, settings, final);
    }
  }

  hash_cleanup(&hash);

  return error;
}

unsigned lodepng_deflate(unsigned char** out, size_t* outsize,
                         const unsigned char* in, size_t insize,
                         const LodePNGCompressSettings* settings) {
  ucvector v = ucvector_init(*out, *outsize);
  unsigned error = lodepng_deflatev(&v, in, insize, settings);
  *out = v.data;
  *outsize = v.size;
  return error;
}

static unsigned deflate(unsigned char** out, size_t* outsize,
                        const unsigned char* in, size_t insize,
                        const LodePNGCompressSettings* settings) {
  if(settings->custom_deflate) {
    unsigned error = settings->custom_deflate(out, outsize, in, insize, settings);
    /*the custom deflate is allowed to have its own error codes, however, we translate it to code 111*/
    return error ? 111 : 0;
  } else {
    return lodepng_deflate(out, outsize, in, insize, settings);
  }
}

#endif /*LODEPNG_COMPILE_DECODER*/

/* ////////////////////////////////////////////////////////////////////////// */
/* / Adler32                                                                / */
/* ////////////////////////////////////////////////////////////////////////// */

static unsigned update_adler32(unsigned adler, const unsigned char* data, unsigned len) {
  unsigned s1 = adler & 0xffffu;
  unsigned s2 = (adler >> 16u) & 0xffffu;

  while(len != 0u) {
    unsigned i;
    /*at least 5552 sums can be done before the sums overflow, saving a lot of module divisions*/
    unsigned amount = len > 5552u ? 5552u : len;
    len -= amount;
    for(i = 0; i != amount; ++i) {
      s1 += (*data++);
      s2 += s1;
    }
    s1 %= 65521u;
    s2 %= 65521u;
  }

  return (s2 << 16u) | s1;
}

/*Return the adler32 of the bytes data[0..len-1]*/
static unsigned adler32(const unsigned char* data, unsigned len) {
  return update_adler32(1u, data, len);
}

/* ////////////////////////////////////////////////////////////////////////// */
/* / Zlib                                                                   / */
/* ////////////////////////////////////////////////////////////////////////// */

#ifdef LODEPNG_COMPILE_DECODER

static unsigned lodepng_zlib_decompressv(ucvector* out,
                                         const unsigned char* in, size_t insize,
                                         const LodePNGDecompressSettings* settings) {
  unsigned error = 0;
  unsigned CM, CINFO, FDICT;

  if(insize < 2) return 53; /*error, size of zlib data too small*/
  /*read information from zlib header*/
  if((in[0] * 256 + in[1]) % 31 != 0) {
    /*error: 256 * in[0] + in[1] must be a multiple of 31, the FCHECK value is supposed to be made that way*/
    return 24;
  }

  CM = in[0] & 15;
  CINFO = (in[0] >> 4) & 15;
  /*FCHECK = in[1] & 31;*/ /*FCHECK is already tested above*/
  FDICT = (in[1] >> 5) & 1;
  /*FLEVEL = (in[1] >> 6) & 3;*/ /*FLEVEL is not used here*/

  if(CM != 8 || CINFO > 7) {
    /*error: only compression method 8: inflate with sliding window of 32k is supported by the PNG spec*/
    return 25;
  }
  if(FDICT != 0) {
    /*error: the specification of PNG says about the zlib stream:
      "The additional flags shall not specify a preset dictionary."*/
    return 26;
  }

  error = inflatev(out, in + 2, insize - 2, settings);
  if(error) return error;

  if(!settings->ignore_adler32) {
    unsigned ADLER32 = lodepng_read32bitInt(&in[insize - 4]);
    unsigned checksum = adler32(out->data, (unsigned)(out->size));
    if(checksum != ADLER32) return 58; /*error, adler checksum not correct, data must be corrupted*/
  }

  return 0; /*no error*/
}


unsigned lodepng_zlib_decompress(unsigned char** out, size_t* outsize, const unsigned char* in,
                                 size_t insize, const LodePNGDecompressSettings* settings) {
  ucvector v = ucvector_init(*out, *outsize);
  unsigned error = lodepng_zlib_decompressv(&v, in, insize, settings);
  *out = v.data;
  *outsize = v.size;
  return error;
}

/*expected_size is expected output size, to avoid intermediate allocations. Set to 0 if not known. */
static unsigned zlib_decompress(unsigned char** out, size_t* outsize, size_t expected_size,
                                const unsigned char* in, size_t insize, const LodePNGDecompressSettings* settings) {
  unsigned error;
  if(settings->custom_zlib) {
    error = settings->custom_zlib(out, outsize, in, insize, settings);
    if(error) {
      /*the custom zlib is allowed to have its own error codes, however, we translate it to code 110*/
      error = 110;
      /*if there's a max output size, and the custom zlib returned error, then indicate that error instead*/
      if(settings->max_output_size && *outsize > settings->max_output_size) error = 109;
    }
  } else {
    ucvector v = ucvector_init(*out, *outsize);
    if(expected_size) {
      /*reserve the memory to avoid intermediate reallocations*/
      ucvector_resize(&v, *outsize + expected_size);
      v.size = *outsize;
    }
    error = lodepng_zlib_decompressv(&v, in, insize, settings);
    *out = v.data;
    *outsize = v.size;
  }
  return error;
}

#endif /*LODEPNG_COMPILE_DECODER*/

#ifdef LODEPNG_COMPILE_ENCODER

unsigned lodepng_zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in,
                               size_t insize, const LodePNGCompressSettings* settings) {
  size_t i;
  unsigned error;
  unsigned char* deflatedata = 0;
  size_t deflatesize = 0;

  error = deflate(&deflatedata, &deflatesize, in, insize, settings);

  *out = NULL;
  *outsize = 0;
  if(!error) {
    *outsize = deflatesize + 6;
    *out = (unsigned char*)lodepng_malloc(*outsize);
    if(!*out) error = 83; /*alloc fail*/
  }

  if(!error) {
    unsigned ADLER32 = adler32(in, (unsigned)insize);
    /*zlib data: 1 byte CMF (CM+CINFO), 1 byte FLG, deflate data, 4 byte ADLER32 checksum of the Decompressed data*/
    unsigned CMF = 120; /*0b01111000: CM 8, CINFO 7. With CINFO 7, any window size up to 32768 can be used.*/
    unsigned FLEVEL = 0;
    unsigned FDICT = 0;
    unsigned CMFFLG = 256 * CMF + FDICT * 32 + FLEVEL * 64;
    unsigned FCHECK = 31 - CMFFLG % 31;
    CMFFLG += FCHECK;

    (*out)[0] = (unsigned char)(CMFFLG >> 8);
    (*out)[1] = (unsigned char)(CMFFLG & 255);
    for(i = 0; i != deflatesize; ++i) (*out)[i + 2] = deflatedata[i];
    lodepng_set32bitInt(&(*out)[*outsize - 4], ADLER32);
  }

  lodepng_free(deflatedata);
  return error;
}

/* compress using the default or custom zlib function */
static unsigned zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in,
                              size_t insize, const LodePNGCompressSettings* settings) {
  if(settings->custom_zlib) {
    unsigned error = settings->custom_zlib(out, outsize, in, insize, settings);
    /*the custom zlib is allowed to have its own error codes, however, we translate it to code 111*/
    return error ? 111 : 0;
  } else {
    return lodepng_zlib_compress(out, outsize, in, insize, settings);
  }
}

#endif /*LODEPNG_COMPILE_ENCODER*/

#else /*no LODEPNG_COMPILE_ZLIB*/

#ifdef LODEPNG_COMPILE_DECODER
static unsigned zlib_decompress(unsigned char** out, size_t* outsize, size_t expected_size,
                                const unsigned char* in, size_t insize, const LodePNGDecompressSettings* settings) {
  if(!settings->custom_zlib) return 87; /*no custom zlib function provided */
  (void)expected_size;
  return settings->custom_zlib(out, outsize, in, insize, settings);
}
#endif /*LODEPNG_COMPILE_DECODER*/
#ifdef LODEPNG_COMPILE_ENCODER
static unsigned zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in,
                              size_t insize, const LodePNGCompressSettings* settings) {
  if(!settings->custom_zlib) return 87; /*no custom zlib function provided */
  return settings->custom_zlib(out, outsize, in, insize, settings);
}
#endif /*LODEPNG_COMPILE_ENCODER*/

#endif /*LODEPNG_COMPILE_ZLIB*/

/* ////////////////////////////////////////////////////////////////////////// */

#ifdef LODEPNG_COMPILE_ENCODER

/*this is a good tradeoff between speed and compression ratio*/
#define DEFAULT_WINDOWSIZE 2048

void lodepng_compress_settings_init(LodePNGCompressSettings* settings) {
  /*compress with dynamic huffman tree (not in the mathematical sense, just not the predefined one)*/
  settings->btype = 2;
  settings->use_lz77 = 1;
  settings->windowsize = DEFAULT_WINDOWSIZE;
  settings->minmatch = 3;
  settings->nicematch = 128;
  settings->lazymatching = 1;

  settings->custom_zlib = 0;
  settings->custom_deflate = 0;
  settings->custom_context = 0;
}

const LodePNGCompressSettings lodepng_default_compress_settings = {2, 1, DEFAULT_WINDOWSIZE, 3, 128, 1, 0, 0, 0};


#endif /*LODEPNG_COMPILE_ENCODER*/

#ifdef LODEPNG_COMPILE_DECODER

void lodepng_decompress_settings_init(LodePNGDecompressSettings* settings) {
  settings->ignore_adler32 = 0;
  settings->ignore_nlen = 0;
  settings->max_output_size = 0;

  settings->custom_zlib = 0;
  settings->custom_inflate = 0;
  settings->custom_context = 0;
}

const LodePNGDecompressSettings lodepng_default_decompress_settings = {0, 0, 0, 0, 0, 0};

#endif /*LODEPNG_COMPILE_DECODER*/

/* ////////////////////////////////////////////////////////////////////////// */
/* ////////////////////////////////////////////////////////////////////////// */
/* // End of Zlib related code. Begin of PNG related code.                 // */
/* ////////////////////////////////////////////////////////////////////////// */
/* ////////////////////////////////////////////////////////////////////////// */

#ifdef LODEPNG_COMPILE_PNG

/* ////////////////////////////////////////////////////////////////////////// */
/* / CRC32                                                                  / */
/* ////////////////////////////////////////////////////////////////////////// */


#ifdef LODEPNG_COMPILE_CRC

static const unsigned lodepng_crc32_table0[256] = {
  0x00000000u, 0x77073096u, 0xee0e612cu, 0x990951bau, 0x076dc419u, 0x706af48fu, 0xe963a535u, 0x9e6495a3u,
  0x0edb8832u, 0x79dcb8a4u, 0xe0d5e91eu, 0x97d2d988u, 0x09b64c2bu, 0x7eb17cbdu, 0xe7b82d07u, 0x90bf1d91u,
  0x1db71064u, 0x6ab020f2u, 0xf3b97148u, 0x84be41deu, 0x1adad47du, 0x6ddde4ebu, 0xf4d4b551u, 0x83d385c7u,
  0x136c9856u, 0x646ba8c0u, 0xfd62f97au, 0x8a65c9ecu, 0x14015c4fu, 0x63066cd9u, 0xfa0f3d63u, 0x8d080df5u,
  0x3b6e20c8u, 0x4c69105eu, 0xd56041e4u, 0xa2677172u, 0x3c03e4d1u, 0x4b04d447u, 0xd20d85fdu, 0xa50ab56bu,
  0x35b5a8fau, 0x42b2986cu, 0xdbbbc9d6u, 0xacbcf940u, 0x32d86ce3u, 0x45df5c75u, 0xdcd60dcfu, 0xabd13d59u,
  0x26d930acu, 0x51de003au, 0xc8d75180u, 0xbfd06116u, 0x21b4f4b5u, 0x56b3c423u, 0xcfba9599u, 0xb8bda50fu,
  0x2802b89eu, 0x5f058808u, 0xc60cd9b2u, 0xb10be924u, 0x2f6f7c87u, 0x58684c11u, 0xc1611dabu, 0xb6662d3du,
  0x76dc4190u, 0x01db7106u, 0x98d220bcu, 0xefd5102au, 0x71b18589u, 0x06b6b51fu, 0x9fbfe4a5u, 0xe8b8d433u,
  0x7807c9a2u, 0x0f00f934u, 0x9609a88eu, 0xe10e9818u, 0x7f6a0dbbu, 0x086d3d2du, 0x91646c97u, 0xe6635c01u,
  0x6b6b51f4u, 0x1c6c6162u, 0x856530d8u, 0xf262004eu, 0x6c0695edu, 0x1b01a57bu, 0x8208f4c1u, 0xf50fc457u,
  0x65b0d9c6u, 0x12b7e950u, 0x8bbeb8eau, 0xfcb9887cu, 0x62dd1ddfu, 0x15da2d49u, 0x8cd37cf3u, 0xfbd44c65u,
  0x4db26158u, 0x3ab551ceu, 0xa3bc0074u, 0xd4bb30e2u, 0x4adfa541u, 0x3dd895d7u, 0xa4d1c46du, 0xd3d6f4fbu,
  0x4369e96au, 0x346ed9fcu, 0xad678846u, 0xda60b8d0u, 0x44042d73u, 0x33031de5u, 0xaa0a4c5fu, 0xdd0d7cc9u,
  0x5005713cu, 0x270241aau, 0xbe0b1010u, 0xc90c2086u, 0x5768b525u, 0x206f85b3u, 0xb966d409u, 0xce61e49fu,
  0x5edef90eu, 0x29d9c998u, 0xb0d09822u, 0xc7d7a8b4u, 0x59b33d17u, 0x2eb40d81u, 0xb7bd5c3bu, 0xc0ba6cadu,
  0xedb88320u, 0x9abfb3b6u, 0x03b6e20cu, 0x74b1d29au, 0xead54739u, 0x9dd277afu, 0x04db2615u, 0x73dc1683u,
  0xe3630b12u, 0x94643b84u, 0x0d6d6a3eu, 0x7a6a5aa8u, 0xe40ecf0bu, 0x9309ff9du, 0x0a00ae27u, 0x7d079eb1u,
  0xf00f9344u, 0x8708a3d2u, 0x1e01f268u, 0x6906c2feu, 0xf762575du, 0x806567cbu, 0x196c3671u, 0x6e6b06e7u,
  0xfed41b76u, 0x89d32be0u, 0x10da7a5au, 0x67dd4accu, 0xf9b9df6fu, 0x8ebeeff9u, 0x17b7be43u, 0x60b08ed5u,
  0xd6d6a3e8u, 0xa1d1937eu, 0x38d8c2c4u, 0x4fdff252u, 0xd1bb67f1u, 0xa6bc5767u, 0x3fb506ddu, 0x48b2364bu,
  0xd80d2bdau, 0xaf0a1b4cu, 0x36034af6u, 0x41047a60u, 0xdf60efc3u, 0xa867df55u, 0x316e8eefu, 0x4669be79u,
  0xcb61b38cu, 0xbc66831au, 0x256fd2a0u, 0x5268e236u, 0xcc0c7795u, 0xbb0b4703u, 0x220216b9u, 0x5505262fu,
  0xc5ba3bbeu, 0xb2bd0b28u, 0x2bb45a92u, 0x5cb36a04u, 0xc2d7ffa7u, 0xb5d0cf31u, 0x2cd99e8bu, 0x5bdeae1du,
  0x9b64c2b0u, 0xec63f226u, 0x756aa39cu, 0x026d930au, 0x9c0906a9u, 0xeb0e363fu, 0x72076785u, 0x05005713u,
  0x95bf4a82u, 0xe2b87a14u, 0x7bb12baeu, 0x0cb61b38u, 0x92d28e9bu, 0xe5d5be0du, 0x7cdcefb7u, 0x0bdbdf21u,
  0x86d3d2d4u, 0xf1d4e242u, 0x68ddb3f8u, 0x1fda836eu, 0x81be16cdu, 0xf6b9265bu, 0x6fb077e1u, 0x18b74777u,
  0x88085ae6u, 0xff0f6a70u, 0x66063bcau, 0x11010b5cu, 0x8f659effu, 0xf862ae69u, 0x616bffd3u, 0x166ccf45u,
  0xa00ae278u, 0xd70dd2eeu, 0x4e048354u, 0x3903b3c2u, 0xa7672661u, 0xd06016f7u, 0x4969474du, 0x3e6e77dbu,
  0xaed16a4au, 0xd9d65adcu, 0x40df0b66u, 0x37d83bf0u, 0xa9bcae53u, 0xdebb9ec5u, 0x47b2cf7fu, 0x30b5ffe9u,
  0xbdbdf21cu, 0xcabac28au, 0x53b39330u, 0x24b4a3a6u, 0xbad03605u, 0xcdd70693u, 0x54de5729u, 0x23d967bfu,
  0xb3667a2eu, 0xc4614ab8u, 0x5d681b02u, 0x2a6f2b94u, 0xb40bbe37u, 0xc30c8ea1u, 0x5a05df1bu, 0x2d02ef8du
};

static const unsigned lodepng_crc32_table1[256] = {
  0x00000000u, 0x191b3141u, 0x32366282u, 0x2b2d53c3u, 0x646cc504u, 0x7d77f445u, 0x565aa786u, 0x4f4196c7u,
  0xc8d98a08u, 0xd1c2bb49u, 0xfaefe88au, 0xe3f4d9cbu, 0xacb54f0cu, 0xb5ae7e4du, 0x9e832d8eu, 0x87981ccfu,
  0x4ac21251u, 0x53d92310u, 0x78f470d3u, 0x61ef4192u, 0x2eaed755u, 0x37b5e614u, 0x1c98b5d7u, 0x05838496u,
  0x821b9859u, 0x9b00a918u, 0xb02dfadbu, 0xa936cb9au, 0xe6775d5du, 0xff6c6c1cu, 0xd4413fdfu, 0xcd5a0e9eu,
  0x958424a2u, 0x8c9f15e3u, 0xa7b24620u, 0xbea97761u, 0xf1e8e1a6u, 0xe8f3d0e7u, 0xc3de8324u, 0xdac5b265u,
  0x5d5daeaau, 0x44469febu, 0x6f6bcc28u, 0x7670fd69u, 0x39316baeu, 0x202a5aefu, 0x0b07092cu, 0x121c386du,
  0xdf4636f3u, 0xc65d07b2u, 0xed705471u, 0xf46b6530u, 0xbb2af3f7u, 0xa231c2b6u, 0x891c9175u, 0x9007a034u,
  0x179fbcfbu, 0x0e848dbau, 0x25a9de79u, 0x3cb2ef38u, 0x73f379ffu, 0x6ae848beu, 0x41c51b7du, 0x58de2a3cu,
  0xf0794f05u, 0xe9627e44u, 0xc24f2d87u, 0xdb541cc6u, 0x94158a01u, 0x8d0ebb40u, 0xa623e883u, 0xbf38d9c2u,
  0x38a0c50du, 0x21bbf44cu, 0x0a96a78fu, 0x138d96ceu, 0x5ccc0009u, 0x45d73148u, 0x6efa628bu, 0x77e153cau,
  0xbabb5d54u, 0xa3a06c15u, 0x888d3fd6u, 0x91960e97u, 0xded79850u, 0xc7cca911u, 0xece1fad2u, 0xf5facb93u,
  0x7262d75cu, 0x6b79e61du, 0x4054b5deu, 0x594f849fu, 0x160e1258u, 0x0f152319u, 0x243870dau, 0x3d23419bu,
  0x65fd6ba7u, 0x7ce65ae6u, 0x57cb0925u, 0x4ed03864u, 0x0191aea3u, 0x188a9fe2u, 0x33a7cc21u, 0x2abcfd60u,
  0xad24e1afu, 0xb43fd0eeu, 0x9f12832du, 0x8609b26cu, 0xc94824abu, 0xd05315eau, 0xfb7e4629u, 0xe2657768u,
  0x2f3f79f6u, 0x362448b7u, 0x1d091b74u, 0x04122a35u, 0x4b53bcf2u, 0x52488db3u, 0x7965de70u, 0x607eef31u,
  0xe7e6f3feu, 0xfefdc2bfu, 0xd5d0917cu, 0xcccba03du, 0x838a36fau, 0x9a9107bbu, 0xb1bc5478u, 0xa8a76539u,
  0x3b83984bu, 0x2298a90au, 0x09b5fac9u, 0x10aecb88u, 0x5fef5d4fu, 0x46f46c0eu, 0x6dd93fcdu, 0x74c20e8cu,
  0xf35a1243u, 0xea412302u, 0xc16c70c1u, 0xd8774180u, 0x9736d747u, 0x8e2de606u, 0xa500b5c5u, 0xbc1b8484u,
  0x71418a1au, 0x685abb5bu, 0x4377e898u, 0x5a6cd9d9u, 0x152d4f1eu, 0x0c367e5fu, 0x271b2d9cu, 0x3e001cddu,
  0xb9980012u, 0xa0833153u, 0x8bae6290u, 0x92b553d1u, 0xddf4c516u, 0xc4eff457u, 0xefc2a794u, 0xf6d996d5u,
  0xae07bce9u, 0xb71c8da8u, 0x9c31de6bu, 0x852aef2au, 0xca6b79edu, 0xd37048acu, 0xf85d1b6fu, 0xe1462a2eu,
  0x66de36e1u, 0x7fc507a0u, 0x54e85463u, 0x4df36522u, 0x02b2f3e5u, 0x1ba9c2a4u, 0x30849167u, 0x299fa026u,
  0xe4c5aeb8u, 0xfdde9ff9u, 0xd6f3cc3au, 0xcfe8fd7bu, 0x80a96bbcu, 0x99b25afdu, 0xb29f093eu, 0xab84387fu,
  0x2c1c24b0u, 0x350715f1u, 0x1e2a4632u, 0x07317773u, 0x4870e1b4u, 0x516bd0f5u, 0x
Download .txt
gitextract_u94dvs2j/

├── LICENSE
├── README.md
└── src/
    ├── archive_analysis.cpp
    ├── binary_utils.cpp
    ├── compile_pdvzip.sh
    ├── display_info.cpp
    ├── file_io.cpp
    ├── image_processing.cpp
    ├── image_processing_internal.h
    ├── image_resize.cpp
    ├── lodepng/
    │   ├── LICENSE
    │   ├── lodepng.cpp
    │   └── lodepng.h
    ├── main.cpp
    ├── pdvzip.h
    ├── polyglot_assembly.cpp
    ├── program_args.cpp
    ├── script_builder.cpp
    ├── script_builder_internal.h
    ├── script_text_builder.cpp
    └── user_input.cpp
Download .txt
SYMBOL INDEX (450 symbols across 17 files)

FILE: src/archive_analysis.cpp
  function T (line 6) | [[nodiscard]] T readZipField(std::span<const Byte> data, std::size_t off...
  function containsControlCharacters (line 20) | [[nodiscard]] bool containsControlCharacters(std::string_view value) {
  function toLowerAscii (line 26) | [[nodiscard]] char toLowerAscii(char ch) {
  function readZipStringView (line 33) | [[nodiscard]] std::string_view readZipStringView(std::span<const Byte> d...
  function hasWindowsReservedSegmentName (line 42) | [[nodiscard]] bool hasWindowsReservedSegmentName(std::string_view segmen...
  function hasWindowsInvalidPathCharacter (line 64) | [[nodiscard]] bool hasWindowsInvalidPathCharacter(std::string_view segme...
  function makePortableEntryKey (line 70) | [[nodiscard]] std::string makePortableEntryKey(std::string_view path) {
  type LocalEntrySpan (line 84) | struct LocalEntrySpan {
  type CentralDirectoryBounds (line 108) | struct CentralDirectoryBounds {
  type CentralEntryMetadata (line 114) | struct CentralEntryMetadata {
  type ArchiveEntryTracking (line 128) | struct ArchiveEntryTracking {
    method reserve (line 135) | void reserve(std::size_t total_records) {
  function validateEntryName (line 143) | void validateEntryName(std::string_view entry_name, std::string_view con...
  function isUnixLikeZipHost (line 155) | [[nodiscard]] bool isUnixLikeZipHost(uint16_t version_made_by) {
  function validateEntryAttributes (line 161) | void validateEntryAttributes(uint16_t version_made_by, uint16_t flags, u...
  function validateEntrySizeMetadata (line 205) | void validateEntrySizeMetadata(uint32_t compressed_size, uint32_t uncomp...
  function validateEntryPathCollision (line 230) | void validateEntryPathCollision(std::string_view entry_name, std::unorde...
  function descriptor32Matches (line 276) | [[nodiscard]] bool descriptor32Matches(std::span<const Byte> archive_dat...
  function readDataDescriptorLength (line 283) | [[nodiscard]] std::size_t readDataDescriptorLength(std::span<const Byte>...
  function validateLocalEntryPayload (line 311) | void validateLocalEntryPayload(std::span<const Byte> archive_data, std::...
  function validateLocalEntrySpans (line 376) | void validateLocalEntrySpans(std::vector<LocalEntrySpan>& local_spans) {
  function centralRecordSize (line 388) | [[nodiscard]] std::size_t centralRecordSize(std::size_t name_length, std...
  function CentralDirectoryBounds (line 402) | [[nodiscard]] CentralDirectoryBounds readCentralDirectoryBounds(std::spa...
  function CentralEntryMetadata (line 444) | [[nodiscard]] CentralEntryMetadata readCentralEntryMetadata(std::span<co...
  function validateCentralRecordSpan (line 494) | void validateCentralRecordSpan(std::size_t cursor, std::size_t record_si...
  function validateCentralEntryMetadata (line 504) | void validateCentralEntryMetadata(const CentralEntryMetadata& entry, Arc...
  function validateLocalEntryForCentralEntry (line 536) | void validateLocalEntryForCentralEntry(std::span<const Byte> archive_dat...
  function parseFirstZipFilename (line 600) | [[nodiscard]] std::string parseFirstZipFilename(std::span<const Byte> ar...
  function isUnsafeEntryPath (line 649) | [[nodiscard]] bool isUnsafeEntryPath(std::string_view path) {
  function findEndOfCentralDirectory (line 686) | [[nodiscard]] std::size_t findEndOfCentralDirectory(std::span<const Byte...
  function ArchiveMetadata (line 702) | ArchiveMetadata analyzeArchive(std::span<const Byte> archive_data, bool ...
  function validateArchiveEntryPaths (line 749) | void validateArchiveEntryPaths(std::span<const Byte> archive_data) {

FILE: src/binary_utils.cpp
  type binary_utils_detail (line 3) | namespace binary_utils_detail {
    function throwOutOfRange (line 5) | [[noreturn]] void throwOutOfRange(std::string_view fn_name) {
  function isSupportedFieldLength (line 13) | [[nodiscard]] bool isSupportedFieldLength(std::size_t length) {
  function validateFieldBounds (line 17) | void validateFieldBounds(std::size_t data_size, std::size_t offset, std:...
  function maxFieldValue (line 26) | [[nodiscard]] std::size_t maxFieldValue(std::size_t length) {
  function fieldByteShift (line 32) | [[nodiscard]] std::size_t fieldByteShift(std::size_t length, std::size_t...
  function writeValueAt (line 40) | void writeValueAt(
  function readValueAt (line 58) | std::size_t readValueAt(
  function checkedAdd (line 73) | std::size_t checkedAdd(std::size_t lhs, std::size_t rhs, std::string_vie...
  function checkedMultiply (line 80) | std::size_t checkedMultiply(std::size_t lhs, std::size_t rhs, std::strin...
  function findZipEocdLocator (line 87) | std::optional<ZipEocdLocator> findZipEocdLocator(

FILE: src/display_info.cpp
  function displayInfo (line 3) | void displayInfo() {

FILE: src/file_io.cpp
  function equalsIgnoreAsciiCase (line 5) | [[nodiscard]] bool equalsIgnoreAsciiCase(std::string_view lhs, std::stri...
  function hasValidFilename (line 13) | bool hasValidFilename(const fs::path& p) {
  function hasFileExtension (line 20) | bool hasFileExtension(const fs::path& p, std::initializer_list<std::stri...
  type ScopedFd (line 29) | struct ScopedFd {
    method ScopedFd (line 32) | explicit ScopedFd(int file_descriptor) noexcept : fd(file_descriptor) {}
    method ScopedFd (line 39) | ScopedFd(const ScopedFd&) = delete;
    method ScopedFd (line 40) | ScopedFd& operator=(const ScopedFd&) = delete;
    method ScopedFd (line 42) | ScopedFd(ScopedFd&& other) noexcept : fd(other.fd) {
    method ScopedFd (line 45) | ScopedFd& operator=(ScopedFd&& other) noexcept {
    method get (line 56) | [[nodiscard]] int get() const noexcept { return fd; }
  function ScopedFd (line 59) | [[nodiscard]] ScopedFd openFileForReadOrThrow(const fs::path& path) {
    method ScopedFd (line 32) | explicit ScopedFd(int file_descriptor) noexcept : fd(file_descriptor) {}
    method ScopedFd (line 39) | ScopedFd(const ScopedFd&) = delete;
    method ScopedFd (line 40) | ScopedFd& operator=(const ScopedFd&) = delete;
    method ScopedFd (line 42) | ScopedFd(ScopedFd&& other) noexcept : fd(other.fd) {
    method ScopedFd (line 45) | ScopedFd& operator=(ScopedFd&& other) noexcept {
    method get (line 56) | [[nodiscard]] int get() const noexcept { return fd; }
  function fdFileSizeChecked (line 73) | [[nodiscard]] std::size_t fdFileSizeChecked(int fd, const fs::path& path) {
  function validateCoverImageConstraints (line 100) | void validateCoverImageConstraints(const fs::path& path, std::size_t fil...
  function validateArchiveConstraints (line 118) | void validateArchiveConstraints(const fs::path& path, std::size_t file_s...
  function validateTypeSpecificConstraints (line 134) | void validateTypeSpecificConstraints(const fs::path& path, std::size_t f...
  function vBytes (line 142) | [[nodiscard]] vBytes makeReadBuffer(std::size_t file_size, bool wrap_arc...
  function readFileContents (line 160) | void readFileContents(int fd, const fs::path& path, vBytes& vec, std::si...
  function validateWrappedArchiveSignature (line 184) | void validateWrappedArchiveSignature(const vBytes& vec, bool wrap_archiv...
  function vBytes (line 192) | vBytes readFile(const fs::path& path, FileTypeCheck check_type) {
  function writePolyglotFile (line 212) | void writePolyglotFile(const vBytes& image_vec, bool is_zip_file) {

FILE: src/image_processing.cpp
  function pngChunkType (line 11) | [[nodiscard]] constexpr std::uint32_t pngChunkType(char a, char b, char ...
  function packRgbaKey (line 18) | [[nodiscard]] constexpr std::uint32_t packRgbaKey(Byte red, Byte green, ...
  type PngIhdr (line 23) | struct PngIhdr {
  class PaletteIndexTable (line 43) | class PaletteIndexTable {
    method hash (line 51) | [[nodiscard]] static constexpr std::size_t hash(std::uint32_t key) {
    method insertIfAbsent (line 61) | void insertIfAbsent(std::uint32_t key, Byte value) {
    method find (line 78) | [[nodiscard]] bool find(std::uint32_t key, Byte& value) const {
  function PngIhdr (line 94) | [[nodiscard]] PngIhdr readPngIhdr(std::span<const Byte> png_data) {
  function validateInputPngForDecode (line 131) | void validateInputPngForDecode(const PngIhdr& ihdr) {
  function convertToPalette (line 163) | void convertToPalette(
  function stripAndCopyChunks (line 261) | void stripAndCopyChunks(vBytes& image_file_vec, Byte color_type) {
  function canConvertToPalette (line 333) | [[nodiscard]] bool canConvertToPalette(Byte input_color_type, const Lode...
  function pngRangeHasProblemCharacter (line 338) | [[nodiscard]] bool pngRangeHasProblemCharacter(std::span<const Byte> png...
  function ihdrHasLinuxProblemCharacter (line 343) | [[nodiscard]] bool ihdrHasLinuxProblemCharacter(std::span<const Byte> pn...
  function candidateIhdrIsLinuxSafe (line 352) | [[nodiscard]] bool candidateIhdrIsLinuxSafe(unsigned width, unsigned hei...
  function findLinuxSafeResizeTarget (line 378) | [[nodiscard]] std::optional<std::pair<unsigned, unsigned>> findLinuxSafe...
  function ensureLinuxSafeIhdr (line 394) | void ensureLinuxSafeIhdr(vBytes& image_file_vec) {
  function maxDimensionForColorType (line 417) | [[nodiscard]] std::size_t maxDimensionForColorType(Byte color_type) {
  function validateFinalImageCompatibility (line 427) | void validateFinalImageCompatibility(const PngIhdr& ihdr) {
  function optimizeImage (line 458) | void optimizeImage(vBytes& image_file_vec) {

FILE: src/image_processing_internal.h
  function namespace (line 5) | namespace image_processing_internal {

FILE: src/image_resize.cpp
  type ResizeAxisSample (line 12) | struct ResizeAxisSample {
  function buildResizeAxisSamples (line 20) | [[nodiscard]] std::vector<ResizeAxisSample> buildResizeAxisSamples(unsig...
  function interpolatePixelScalar (line 48) | inline void interpolatePixelScalar(
  function __m128 (line 71) | [[nodiscard]] inline __m128 loadPixelAsFloat(const Byte* source) {
  function storeRoundedPixel (line 88) | inline void storeRoundedPixel(Byte* destination, __m128 value) {
  function interpolatePixelSimd (line 102) | inline void interpolatePixelSimd(
  function interpolatePixel (line 126) | inline void interpolatePixel(
  function resizePaletteImage (line 162) | void resizePaletteImage(
  function resizeTruecolorImage (line 182) | void resizeTruecolorImage(
  function decodePreservingColorType (line 224) | void decodePreservingColorType(
  function validateResizeTarget (line 236) | void validateResizeTarget(unsigned new_width, unsigned new_height, unsig...
  function validateDecodedResizeFormat (line 247) | void validateDecodedResizeFormat(bool is_palette, unsigned channels, uns...
  function redecodeSubBytePalette (line 259) | void redecodeSubBytePalette(
  function bytesPerPixel (line 280) | [[nodiscard]] std::size_t bytesPerPixel(bool is_palette, unsigned channe...
  function validateSourceBufferSize (line 284) | void validateSourceBufferSize(const vBytes& pixels, unsigned width, unsi...
  function vBytes (line 298) | [[nodiscard]] vBytes makeResizedPixelBuffer(unsigned new_width, unsigned...
  function resizePixels (line 309) | void resizePixels(
  function makeEncodeState (line 329) | [[nodiscard]] lodepng::State makeEncodeState(
  type image_processing_internal (line 355) | namespace image_processing_internal {
    function resizeImage (line 357) | void resizeImage(vBytes& image_file_vec, unsigned new_width, unsigned ...

FILE: src/lodepng/lodepng.cpp
  function lodepng_free (line 97) | static void lodepng_free(void* ptr) {
  function lodepng_memcpy (line 128) | static void lodepng_memcpy(void* LODEPNG_RESTRICT dst,
  function lodepng_memset (line 134) | static void lodepng_memset(void* LODEPNG_RESTRICT dst,
  function lodepng_strlen (line 141) | static size_t lodepng_strlen(const char* a) {
  function lodepng_addofl (line 155) | static int lodepng_addofl(size_t a, size_t b, size_t* result) {
  function lodepng_mulofl (line 164) | static int lodepng_mulofl(size_t a, size_t b, size_t* result) {
  function lodepng_gtofl (line 171) | static int lodepng_gtofl(size_t a, size_t b, size_t c) {
  type uivector (line 225) | struct uivector {
  function uivector_cleanup (line 231) | static void uivector_cleanup(void* p) {
  function uivector_resize (line 238) | static unsigned uivector_resize(uivector* p, size_t size) {
  function uivector_init (line 253) | static void uivector_init(uivector* p) {
  function uivector_push_back (line 259) | static unsigned uivector_push_back(uivector* p, unsigned c) {
  type ucvector (line 270) | struct ucvector {
  function ucvector_reserve (line 277) | static unsigned ucvector_reserve(ucvector* p, size_t size) {
  function ucvector_resize (line 291) | static unsigned ucvector_resize(ucvector* p, size_t size) {
  function ucvector (line 296) | static ucvector ucvector_init(unsigned char* buffer, size_t size) {
  function lodepng_read32bitInt (line 328) | static unsigned lodepng_read32bitInt(const unsigned char* buffer) {
  function lodepng_set32bitInt (line 336) | static void lodepng_set32bitInt(unsigned char* buffer, unsigned value) {
  function lodepng_filesize (line 351) | static long lodepng_filesize(FILE* file) {
  function lodepng_load_file_ (line 362) | static unsigned lodepng_load_file_(unsigned char** out, size_t* outsize,...
  function lodepng_load_file (line 372) | unsigned lodepng_load_file(unsigned char** out, size_t* outsize, const c...
  function lodepng_save_file (line 382) | unsigned lodepng_save_file(const unsigned char* buffer, size_t buffersiz...
  function LodePNGBitWriter_init (line 406) | static void LodePNGBitWriter_init(LodePNGBitWriter* writer, ucvector* da...
  function writeBits (line 423) | static void writeBits(LodePNGBitWriter* writer, unsigned value, size_t n...
  function writeBitsReversed (line 436) | static void writeBitsReversed(LodePNGBitWriter* writer, unsigned value, ...
  function LodePNGBitReader_init (line 456) | static unsigned LodePNGBitReader_init(LodePNGBitReader* reader, const un...
  function LODEPNG_INLINE (line 479) | static LODEPNG_INLINE void ensureBits9(LodePNGBitReader* reader, size_t ...
  function LODEPNG_INLINE (line 494) | static LODEPNG_INLINE void ensureBits17(LodePNGBitReader* reader, size_t...
  function LODEPNG_INLINE (line 511) | static LODEPNG_INLINE void ensureBits25(LodePNGBitReader* reader, size_t...
  function LODEPNG_INLINE (line 529) | static LODEPNG_INLINE void ensureBits32(LodePNGBitReader* reader, size_t...
  function peekBits (line 549) | static LODEPNG_INLINE unsigned peekBits(LodePNGBitReader* reader, size_t...
  function LODEPNG_INLINE (line 555) | static LODEPNG_INLINE void advanceBits(LodePNGBitReader* reader, size_t ...
  function readBits (line 561) | static LODEPNG_INLINE unsigned readBits(LodePNGBitReader* reader, size_t...
  function reverseBits (line 568) | static unsigned reverseBits(unsigned bits, unsigned num) {
  type HuffmanTree (line 618) | struct HuffmanTree {
  function HuffmanTree_init (line 628) | static void HuffmanTree_init(HuffmanTree* tree) {
  function HuffmanTree_cleanup (line 635) | static void HuffmanTree_cleanup(HuffmanTree* tree) {
  function HuffmanTree_makeTable (line 651) | static unsigned HuffmanTree_makeTable(HuffmanTree* tree) {
  function HuffmanTree_makeFromLengths2 (line 772) | static unsigned HuffmanTree_makeFromLengths2(HuffmanTree* tree) {
  function HuffmanTree_makeFromLengths (line 813) | static unsigned HuffmanTree_makeFromLengths(HuffmanTree* tree, const uns...
  type BPMNode (line 830) | struct BPMNode {
    type BPMNode (line 833) | struct BPMNode
  type BPMLists (line 838) | struct BPMLists {
  function BPMNode (line 852) | static BPMNode* bpmnode_create(BPMLists* lists, int weight, unsigned ind...
    type BPMNode (line 833) | struct BPMNode
  function bpmnode_sort (line 881) | static void bpmnode_sort(BPMNode* leaves, size_t num) {
  function boundaryPM (line 904) | static void boundaryPM(BPMLists* lists, BPMNode* leaves, size_t numprese...
  function lodepng_huffman_code_lengths (line 929) | unsigned lodepng_huffman_code_lengths(unsigned* lengths, const unsigned*...
  function HuffmanTree_makeFromFrequencies (line 1008) | static unsigned HuffmanTree_makeFromFrequencies(HuffmanTree* tree, const...
  function generateFixedLitLenTree (line 1024) | static unsigned generateFixedLitLenTree(HuffmanTree* tree) {
  function generateFixedDistanceTree (line 1042) | static unsigned generateFixedDistanceTree(HuffmanTree* tree) {
  function huffmanDecodeSymbol (line 1060) | static unsigned huffmanDecodeSymbol(LodePNGBitReader* reader, const Huff...
  function getTreeInflateFixed (line 1084) | static unsigned getTreeInflateFixed(HuffmanTree* tree_ll, HuffmanTree* t...
  function getTreeInflateDynamic (line 1091) | static unsigned getTreeInflateDynamic(HuffmanTree* tree_ll, HuffmanTree*...
  function inflateHuffmanBlock (line 1225) | static unsigned inflateHuffmanBlock(ucvector* out, LodePNGBitReader* rea...
  function inflateNoCompression (line 1333) | static unsigned inflateNoCompression(ucvector* out, LodePNGBitReader* re...
  function lodepng_inflatev (line 1368) | static unsigned lodepng_inflatev(ucvector* out,
  function lodepng_inflate (line 1394) | unsigned lodepng_inflate(unsigned char** out, size_t* outsize,
  function inflatev (line 1404) | static unsigned inflatev(ucvector* out, const unsigned char* in, size_t ...
  function searchCodeIndex (line 1433) | static size_t searchCodeIndex(const unsigned* array, size_t array_size, ...
  function addLengthDistance (line 1447) | static void addLengthDistance(uivector* values, size_t length, size_t di...
  type Hash (line 1475) | struct Hash {
  function hash_init (line 1488) | static unsigned hash_init(Hash* hash, unsigned windowsize) {
  function hash_cleanup (line 1513) | static void hash_cleanup(Hash* hash) {
  function getHash (line 1525) | static unsigned getHash(const unsigned char* data, size_t size, size_t p...
  function countLowOneBits16 (line 1545) | static unsigned countLowOneBits16(unsigned mask) {
  function countZeros (line 1555) | static unsigned countZeros(const unsigned char* data, size_t size, size_...
  function countMatchingBytes (line 1578) | static unsigned countMatchingBytes(const unsigned char* foreptr, const u...
  function updateHashChain (line 1602) | static void updateHashChain(Hash* hash, size_t wpos, unsigned hashval, u...
  function encodeLZ77 (line 1621) | static unsigned encodeLZ77(uivector* out, Hash* hash,
  function deflateNoCompression (line 1769) | static unsigned deflateNoCompression(ucvector* out, const unsigned char*...
  function writeLZ77data (line 1807) | static void writeLZ77data(LodePNGBitWriter* writer, const uivector* lz77...
  function deflateDynamic (line 1832) | static unsigned deflateDynamic(LodePNGBitWriter* writer, Hash* hash,
  function deflateFixed (line 2054) | static unsigned deflateFixed(LodePNGBitWriter* writer, Hash* hash,
  function lodepng_deflatev (line 2099) | static unsigned lodepng_deflatev(ucvector* out, const unsigned char* in,...
  function lodepng_deflate (line 2140) | unsigned lodepng_deflate(unsigned char** out, size_t* outsize,
  function deflate (line 2150) | static unsigned deflate(unsigned char** out, size_t* outsize,
  function update_adler32 (line 2168) | static unsigned update_adler32(unsigned adler, const unsigned char* data...
  function adler32 (line 2189) | static unsigned adler32(const unsigned char* data, unsigned len) {
  function lodepng_zlib_decompressv (line 2199) | static unsigned lodepng_zlib_decompressv(ucvector* out,
  function lodepng_zlib_decompress (line 2241) | unsigned lodepng_zlib_decompress(unsigned char** out, size_t* outsize, c...
  function zlib_decompress (line 2251) | static unsigned zlib_decompress(unsigned char** out, size_t* outsize, si...
  function lodepng_zlib_compress (line 2280) | unsigned lodepng_zlib_compress(unsigned char** out, size_t* outsize, con...
  function zlib_compress (line 2318) | static unsigned zlib_compress(unsigned char** out, size_t* outsize, cons...
  function zlib_decompress (line 2334) | static unsigned zlib_decompress(unsigned char** out, size_t* outsize, si...
  function zlib_compress (line 2342) | static unsigned zlib_compress(unsigned char** out, size_t* outsize, cons...
  function lodepng_compress_settings_init (line 2358) | void lodepng_compress_settings_init(LodePNGCompressSettings* settings) {
  function lodepng_decompress_settings_init (line 2379) | void lodepng_decompress_settings_init(LodePNGDecompressSettings* setting...
  function lodepng_crc32 (line 2689) | unsigned lodepng_crc32(const unsigned char* data, size_t length) {
  function readBitFromReversedStream (line 2739) | static unsigned char readBitFromReversedStream(size_t* bitpointer, const...
  function readBitsFromReversedStream (line 2746) | static unsigned readBitsFromReversedStream(size_t* bitpointer, const uns...
  function setBitOfReversedStream (line 2756) | static void setBitOfReversedStream(size_t* bitpointer, unsigned char* bi...
  function lodepng_chunk_length (line 2767) | unsigned lodepng_chunk_length(const unsigned char* chunk) {
  function lodepng_chunk_type (line 2771) | void lodepng_chunk_type(char type[5], const unsigned char* chunk) {
  function lodepng_chunk_type_equals (line 2777) | unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, cons...
  function lodepng_chunk_type_name_valid (line 2783) | static unsigned char lodepng_chunk_type_name_valid(const unsigned char* ...
  function lodepng_chunk_ancillary (line 2794) | unsigned char lodepng_chunk_ancillary(const unsigned char* chunk) {
  function lodepng_chunk_private (line 2798) | unsigned char lodepng_chunk_private(const unsigned char* chunk) {
  function lodepng_chunk_reserved (line 2804) | static unsigned char lodepng_chunk_reserved(const unsigned char* chunk) {
  function lodepng_chunk_safetocopy (line 2808) | unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk) {
  function lodepng_chunk_check_crc (line 2820) | unsigned lodepng_chunk_check_crc(const unsigned char* chunk) {
  function lodepng_chunk_generate_crc (line 2829) | void lodepng_chunk_generate_crc(unsigned char* chunk) {
  function lodepng_chunk_append (line 2881) | unsigned lodepng_chunk_append(unsigned char** out, size_t* outsize, cons...
  function lodepng_chunk_init (line 2911) | static unsigned lodepng_chunk_init(unsigned char** chunk,
  function lodepng_chunk_createv (line 2930) | static unsigned lodepng_chunk_createv(ucvector* out,
  function lodepng_chunk_create (line 2944) | unsigned lodepng_chunk_create(unsigned char** out, size_t* outsize,
  function checkColorValidity (line 2959) | static unsigned checkColorValidity(LodePNGColorType colortype, unsigned ...
  function getNumColorChannels (line 2972) | static unsigned getNumColorChannels(LodePNGColorType colortype) {
  function lodepng_get_bpp_lct (line 2984) | static unsigned lodepng_get_bpp_lct(LodePNGColorType colortype, unsigned...
  function lodepng_color_mode_init (line 2991) | void lodepng_color_mode_init(LodePNGColorMode* info) {
  function lodepng_color_mode_alloc_palette (line 3001) | static void lodepng_color_mode_alloc_palette(LodePNGColorMode* info) {
  function lodepng_color_mode_cleanup (line 3018) | void lodepng_color_mode_cleanup(LodePNGColorMode* info) {
  function lodepng_color_mode_copy (line 3022) | unsigned lodepng_color_mode_copy(LodePNGColorMode* dest, const LodePNGCo...
  function LodePNGColorMode (line 3033) | LodePNGColorMode lodepng_color_mode_make(LodePNGColorType colortype, uns...
  function lodepng_color_mode_equal (line 3041) | static int lodepng_color_mode_equal(const LodePNGColorMode* a, const Lod...
  function lodepng_palette_clear (line 3058) | void lodepng_palette_clear(LodePNGColorMode* info) {
  function lodepng_palette_add (line 3064) | unsigned lodepng_palette_add(LodePNGColorMode* info,
  function lodepng_get_bpp (line 3082) | unsigned lodepng_get_bpp(const LodePNGColorMode* info) {
  function lodepng_get_channels (line 3086) | unsigned lodepng_get_channels(const LodePNGColorMode* info) {
  function lodepng_is_greyscale_type (line 3090) | unsigned lodepng_is_greyscale_type(const LodePNGColorMode* info) {
  function lodepng_is_alpha_type (line 3094) | unsigned lodepng_is_alpha_type(const LodePNGColorMode* info) {
  function lodepng_is_palette_type (line 3098) | unsigned lodepng_is_palette_type(const LodePNGColorMode* info) {
  function lodepng_has_palette_alpha (line 3102) | unsigned lodepng_has_palette_alpha(const LodePNGColorMode* info) {
  function lodepng_can_have_alpha (line 3110) | unsigned lodepng_can_have_alpha(const LodePNGColorMode* info) {
  function lodepng_get_raw_size_lct (line 3116) | static size_t lodepng_get_raw_size_lct(unsigned w, unsigned h, LodePNGCo...
  function lodepng_get_raw_size (line 3122) | size_t lodepng_get_raw_size(unsigned w, unsigned h, const LodePNGColorMo...
  function lodepng_get_raw_size_idat (line 3132) | static size_t lodepng_get_raw_size_idat(unsigned w, unsigned h, unsigned...
  function lodepng_pixel_overflow (line 3148) | static int lodepng_pixel_overflow(unsigned w, unsigned h,
  function LodePNGUnknownChunks_init (line 3171) | static void LodePNGUnknownChunks_init(LodePNGInfo* info) {
  function LodePNGUnknownChunks_cleanup (line 3177) | static void LodePNGUnknownChunks_cleanup(LodePNGInfo* info) {
  function LodePNGUnknownChunks_copy (line 3182) | static unsigned LodePNGUnknownChunks_copy(LodePNGInfo* dest, const LodeP...
  function LodePNGText_init (line 3202) | static void LodePNGText_init(LodePNGInfo* info) {
  function LodePNGText_cleanup (line 3208) | static void LodePNGText_cleanup(LodePNGInfo* info) {
  function LodePNGText_copy (line 3218) | static unsigned LodePNGText_copy(LodePNGInfo* dest, const LodePNGInfo* s...
  function lodepng_add_text_sized (line 3229) | static unsigned lodepng_add_text_sized(LodePNGInfo* info, const char* ke...
  function lodepng_add_text (line 3246) | unsigned lodepng_add_text(LodePNGInfo* info, const char* key, const char...
  function lodepng_clear_text (line 3250) | void lodepng_clear_text(LodePNGInfo* info) {
  function LodePNGIText_init (line 3258) | static void LodePNGIText_init(LodePNGInfo* info) {
  function LodePNGIText_cleanup (line 3266) | static void LodePNGIText_cleanup(LodePNGInfo* info) {
  function LodePNGIText_copy (line 3280) | static unsigned LodePNGIText_copy(LodePNGInfo* dest, const LodePNGInfo* ...
  function lodepng_clear_itext (line 3294) | void lodepng_clear_itext(LodePNGInfo* info) {
  function lodepng_add_itext_sized (line 3300) | static unsigned lodepng_add_itext_sized(LodePNGInfo* info, const char* k...
  function lodepng_add_itext (line 3324) | unsigned lodepng_add_itext(LodePNGInfo* info, const char* key, const cha...
  function lodepng_set_icc (line 3329) | unsigned lodepng_set_icc(LodePNGInfo* info, const char* name, const unsi...
  function lodepng_init_icc (line 3350) | static void lodepng_init_icc(LodePNGInfo* info) {
  function lodepng_clear_icc (line 3357) | void lodepng_clear_icc(LodePNGInfo* info) {
  function lodepng_set_exif (line 3363) | unsigned lodepng_set_exif(LodePNGInfo* info, const unsigned char* exif, ...
  function lodepng_init_exif (line 3376) | static void lodepng_init_exif(LodePNGInfo* info) {
  function lodepng_clear_exif (line 3382) | void lodepng_clear_exif(LodePNGInfo* info) {
  function lodepng_info_init (line 3388) | void lodepng_info_init(LodePNGInfo* info) {
  function lodepng_info_cleanup (line 3435) | void lodepng_info_cleanup(LodePNGInfo* info) {
  function lodepng_info_copy (line 3448) | unsigned lodepng_info_copy(LodePNGInfo* dest, const LodePNGInfo* source) {
  function addColorBits (line 3481) | static void addColorBits(unsigned char* out, size_t index, unsigned bits...
  type ColorTree (line 3491) | struct ColorTree
  type ColorTree (line 3499) | struct ColorTree {
  function color_tree_init (line 3504) | static void color_tree_init(ColorTree* tree) {
  function color_tree_cleanup (line 3509) | static void color_tree_cleanup(ColorTree* tree) {
  function color_tree_get (line 3520) | static int color_tree_get(ColorTree* tree, unsigned char r, unsigned cha...
  function color_tree_has (line 3531) | static int color_tree_has(ColorTree* tree, unsigned char r, unsigned cha...
  function color_tree_add (line 3539) | static unsigned color_tree_add(ColorTree* tree,
  function rgba8ToPixel (line 3556) | static unsigned rgba8ToPixel(unsigned char* out, size_t i,
  function rgba16ToPixel (line 3610) | static void rgba16ToPixel(unsigned char* out, size_t i,
  function getPixelColorRGBA8 (line 3643) | static void getPixelColorRGBA8(unsigned char* r, unsigned char* g,
  function getPixelColorsRGBA8 (line 3717) | static void getPixelColorsRGBA8(unsigned char* LODEPNG_RESTRICT buffer, ...
  function getPixelColorsRGB8 (line 3813) | static void getPixelColorsRGB8(unsigned char* LODEPNG_RESTRICT buffer, s...
  function getPixelColorRGBA16 (line 3887) | static void getPixelColorRGBA16(unsigned short* r, unsigned short* g, un...
  function lodepng_convert (line 3913) | unsigned lodepng_convert(unsigned char* out, const unsigned char* in,
  function lodepng_convert_rgb (line 3994) | unsigned lodepng_convert_rgb(
  function lodepng_color_stats_init (line 4046) | void lodepng_color_stats_init(LodePNGColorStats* stats) {
  function getValueRequiredBits (line 4073) | static unsigned getValueRequiredBits(unsigned char value) {
  function lodepng_compute_color_stats (line 4081) | unsigned lodepng_compute_color_stats(LodePNGColorStats* stats,
  function lodepng_color_stats_add (line 4277) | static unsigned lodepng_color_stats_add(LodePNGColorStats* stats,
  function auto_choose_color (line 4300) | static unsigned auto_choose_color(LodePNGColorMode* mode_out,
  function paethPredictor (line 4368) | static unsigned char paethPredictor(unsigned char a, unsigned char b, un...
  function Adam7_getpassvalues (line 4402) | static void Adam7_getpassvalues(unsigned passw[7], unsigned passh[7], si...
  function lodepng_inspect (line 4434) | unsigned lodepng_inspect(unsigned* w, unsigned* h, LodePNGState* state,
  function unfilterScanline (line 4498) | static unsigned unfilterScanline(unsigned char* recon, const unsigned ch...
  function unfilter (line 4689) | static unsigned unfilter(unsigned char* out, const unsigned char* in, un...
  function Adam7_deinterlace (line 4730) | static void Adam7_deinterlace(unsigned char* out, const unsigned char* i...
  function removePaddingBits (line 4770) | static void removePaddingBits(unsigned char* out, const unsigned char* in,
  function postProcessScanlines (line 4797) | static unsigned postProcessScanlines(unsigned char* out, unsigned char* in,
  function readChunk_PLTE (line 4840) | static unsigned readChunk_PLTE(LodePNGColorMode* color, const unsigned c...
  function readChunk_tRNS (line 4860) | static unsigned readChunk_tRNS(LodePNGColorMode* color, const unsigned c...
  function readChunk_bKGD (line 4890) | static unsigned readChunk_bKGD(LodePNGInfo* info, const unsigned char* d...
  function readChunk_tEXt (line 4922) | static unsigned readChunk_tEXt(LodePNGInfo* info, const unsigned char* d...
  function readChunk_zTXt (line 4962) | static unsigned readChunk_zTXt(LodePNGInfo* info, const LodePNGDecoderSe...
  function readChunk_iTXt (line 5009) | static unsigned readChunk_iTXt(LodePNGInfo* info, const LodePNGDecoderSe...
  function readChunk_tIME (line 5095) | static unsigned readChunk_tIME(LodePNGInfo* info, const unsigned char* d...
  function readChunk_pHYs (line 5109) | static unsigned readChunk_pHYs(LodePNGInfo* info, const unsigned char* d...
  function readChunk_gAMA (line 5120) | static unsigned readChunk_gAMA(LodePNGInfo* info, const unsigned char* d...
  function readChunk_cHRM (line 5129) | static unsigned readChunk_cHRM(LodePNGInfo* info, const unsigned char* d...
  function readChunk_sRGB (line 5145) | static unsigned readChunk_sRGB(LodePNGInfo* info, const unsigned char* d...
  function readChunk_iCCP (line 5154) | static unsigned readChunk_iCCP(LodePNGInfo* info, const LodePNGDecoderSe...
  function readChunk_cICP (line 5195) | static unsigned readChunk_cICP(LodePNGInfo* info, const unsigned char* d...
  function readChunk_mDCV (line 5209) | static unsigned readChunk_mDCV(LodePNGInfo* info, const unsigned char* d...
  function readChunk_cLLI (line 5227) | static unsigned readChunk_cLLI(LodePNGInfo* info, const unsigned char* d...
  function readChunk_eXIf (line 5237) | static unsigned readChunk_eXIf(LodePNGInfo* info, const unsigned char* d...
  function readChunk_sBIT (line 5242) | static unsigned readChunk_sBIT(LodePNGInfo* info, const unsigned char* d...
  function lodepng_inspect_chunk (line 5283) | unsigned lodepng_inspect_chunk(LodePNGState* state, size_t pos,
  function decodeGeneric (line 5346) | static void decodeGeneric(unsigned char** out, unsigned* w, unsigned* h,
  function lodepng_decode (line 5563) | unsigned lodepng_decode(unsigned char** out, unsigned* w, unsigned* h,
  function lodepng_decode_memory (line 5600) | unsigned lodepng_decode_memory(unsigned char** out, unsigned* w, unsigne...
  function lodepng_decode32 (line 5617) | unsigned lodepng_decode32(unsigned char** out, unsigned* w, unsigned* h,...
  function lodepng_decode24 (line 5621) | unsigned lodepng_decode24(unsigned char** out, unsigned* w, unsigned* h,...
  function lodepng_decode_file (line 5626) | unsigned lodepng_decode_file(unsigned char** out, unsigned* w, unsigned*...
  function lodepng_decode32_file (line 5640) | unsigned lodepng_decode32_file(unsigned char** out, unsigned* w, unsigne...
  function lodepng_decode24_file (line 5644) | unsigned lodepng_decode24_file(unsigned char** out, unsigned* w, unsigne...
  function lodepng_decoder_settings_init (line 5649) | void lodepng_decoder_settings_init(LodePNGDecoderSettings* settings) {
  function lodepng_state_init (line 5667) | void lodepng_state_init(LodePNGState* state) {
  function lodepng_state_cleanup (line 5679) | void lodepng_state_cleanup(LodePNGState* state) {
  function lodepng_state_copy (line 5684) | unsigned lodepng_state_copy(LodePNGState* dest, const LodePNGState* sour...
  function writeSignature (line 5704) | static unsigned writeSignature(ucvector* out) {
  function addChunk_IHDR (line 5713) | static unsigned addChunk_IHDR(ucvector* out, unsigned w, unsigned h,
  function addChunk_PLTE (line 5732) | static unsigned addChunk_PLTE(ucvector* out, const LodePNGColorMode* inf...
  function addChunk_tRNS (line 5753) | static unsigned addChunk_tRNS(ucvector* out, const LodePNGColorMode* inf...
  function addChunk_IDAT (line 5790) | static unsigned addChunk_IDAT(ucvector* out, const unsigned char* data, ...
  function addChunk_IEND (line 5813) | static unsigned addChunk_IEND(ucvector* out) {
  function addChunk_tEXt (line 5819) | static unsigned addChunk_tEXt(ucvector* out, const char* keyword, const ...
  function addChunk_zTXt (line 5832) | static unsigned addChunk_zTXt(ucvector* out, const char* keyword, const ...
  function addChunk_iTXt (line 5860) | static unsigned addChunk_iTXt(ucvector* out, unsigned compress, const ch...
  function addChunk_bKGD (line 5904) | static unsigned addChunk_bKGD(ucvector* out, const LodePNGInfo* info) {
  function addChunk_tIME (line 5926) | static unsigned addChunk_tIME(ucvector* out, const LodePNGTime* time) {
  function addChunk_pHYs (line 5940) | static unsigned addChunk_pHYs(ucvector* out, const LodePNGInfo* info) {
  function addChunk_gAMA (line 5950) | static unsigned addChunk_gAMA(ucvector* out, const LodePNGInfo* info) {
  function addChunk_cHRM (line 5958) | static unsigned addChunk_cHRM(ucvector* out, const LodePNGInfo* info) {
  function addChunk_sRGB (line 5973) | static unsigned addChunk_sRGB(ucvector* out, const LodePNGInfo* info) {
  function addChunk_iCCP (line 5978) | static unsigned addChunk_iCCP(ucvector* out, const LodePNGInfo* info, co...
  function addChunk_cICP (line 6004) | static unsigned addChunk_cICP(ucvector* out, const LodePNGInfo* info) {
  function addChunk_mDCV (line 6022) | static unsigned addChunk_mDCV(ucvector* out, const LodePNGInfo* info) {
  function addChunk_cLLI (line 6056) | static unsigned addChunk_cLLI(ucvector* out, const LodePNGInfo* info) {
  function addChunk_eXIf (line 6065) | static unsigned addChunk_eXIf(ucvector* out, const LodePNGInfo* info) {
  function addChunk_sBIT (line 6069) | static unsigned addChunk_sBIT(ucvector* out, const LodePNGInfo* info) {
  function filterScanline (line 6107) | static void filterScanline(unsigned char* out, const unsigned char* scan...
  function sumUnsignedBytes (line 6151) | static size_t sumUnsignedBytes(const unsigned char* data, size_t length) {
  function sumFilteredAbsBytes (line 6171) | static size_t sumFilteredAbsBytes(const unsigned char* data, size_t leng...
  function ilog2 (line 6198) | static size_t ilog2(size_t i) {
  function ilog2i (line 6209) | static size_t ilog2i(size_t i) {
  function filter (line 6218) | static unsigned filter(unsigned char* out, const unsigned char* in, unsi...
  function addPaddingBits (line 6400) | static void addPaddingBits(unsigned char* out, const unsigned char* in,
  function Adam7_interlace (line 6430) | static void Adam7_interlace(unsigned char* out, const unsigned char* in,...
  function preProcessScanlines (line 6471) | static unsigned preProcessScanlines(unsigned char** out, size_t* outsize...
  function addUnknownChunks (line 6545) | static unsigned addUnknownChunks(ucvector* out, unsigned char* data, siz...
  function isGrayICCProfile (line 6555) | static unsigned isGrayICCProfile(const unsigned char* profile, unsigned ...
  function isRGBICCProfile (line 6569) | static unsigned isRGBICCProfile(const unsigned char* profile, unsigned s...
  function lodepng_encode (line 6576) | unsigned lodepng_encode(unsigned char** out, size_t* outsize,
  function lodepng_encode_memory (line 6929) | unsigned lodepng_encode_memory(unsigned char** out, size_t* outsize, con...
  function lodepng_encode32 (line 6943) | unsigned lodepng_encode32(unsigned char** out, size_t* outsize, const un...
  function lodepng_encode24 (line 6947) | unsigned lodepng_encode24(unsigned char** out, size_t* outsize, const un...
  function lodepng_encode_file (line 6952) | unsigned lodepng_encode_file(const char* filename, const unsigned char* ...
  function lodepng_encode32_file (line 6962) | unsigned lodepng_encode32_file(const char* filename, const unsigned char...
  function lodepng_encode24_file (line 6966) | unsigned lodepng_encode24_file(const char* filename, const unsigned char...
  function lodepng_encoder_settings_init (line 6971) | void lodepng_encoder_settings_init(LodePNGEncoderSettings* settings) {
  type lodepng (line 7136) | namespace lodepng {
    function load_file_ (line 7140) | static unsigned load_file_(std::vector<unsigned char>& buffer, FILE* f...
    function load_file (line 7149) | unsigned load_file(std::vector<unsigned char>& buffer, const std::stri...
    function save_file (line 7159) | unsigned save_file(const std::vector<unsigned char>& buffer, const std...
    function decompress (line 7166) | unsigned decompress(std::vector<unsigned char>& out, const unsigned ch...
    function decompress (line 7178) | unsigned decompress(std::vector<unsigned char>& out, const std::vector...
    function compress (line 7185) | unsigned compress(std::vector<unsigned char>& out, const unsigned char...
    function compress (line 7197) | unsigned compress(std::vector<unsigned char>& out, const std::vector<u...
    function State (line 7220) | State& State::operator=(const State& other) {
    function decode (line 7227) | unsigned decode(std::vector<unsigned char>& out, unsigned& w, unsigned...
    function decode (line 7242) | unsigned decode(std::vector<unsigned char>& out, unsigned& w, unsigned...
    function decode (line 7247) | unsigned decode(std::vector<unsigned char>& out, unsigned& w, unsigned...
    function decode (line 7260) | unsigned decode(std::vector<unsigned char>& out, unsigned& w, unsigned...
    function decode (line 7267) | unsigned decode(std::vector<unsigned char>& out, unsigned& w, unsigned...
    function encode (line 7280) | unsigned encode(std::vector<unsigned char>& out, const unsigned char* ...
    function encode (line 7292) | unsigned encode(std::vector<unsigned char>& out,
    function encode (line 7299) | unsigned encode(std::vector<unsigned char>& out,
    function encode (line 7312) | unsigned encode(std::vector<unsigned char>& out,
    function encode (line 7320) | unsigned encode(const std::string& filename,
    function encode (line 7329) | unsigned encode(const std::string& filename,

FILE: src/lodepng/lodepng.h
  type LodePNGColorType (line 121) | typedef enum LodePNGColorType {
  function namespace (line 251) | namespace lodepng {
  type LodePNGDecompressSettings (line 313) | typedef struct LodePNGDecompressSettings LodePNGDecompressSettings;
  type LodePNGDecompressSettings (line 314) | struct LodePNGDecompressSettings {
  type LodePNGCompressSettings (line 350) | typedef struct LodePNGCompressSettings LodePNGCompressSettings;
  type LodePNGCompressSettings (line 351) | struct LodePNGCompressSettings /*deflate = compress*/ {
  type LodePNGColorMode (line 384) | typedef struct LodePNGColorMode {
  type LodePNGTime (line 466) | typedef struct LodePNGTime {
  type LodePNGInfo (line 477) | typedef struct LodePNGInfo {
  type LodePNGDecoderSettings (line 816) | typedef struct LodePNGDecoderSettings {
  type LodePNGFilterStrategy (line 854) | typedef enum LodePNGFilterStrategy {
  type LodePNGColorStats (line 880) | typedef struct LodePNGColorStats {
  type LodePNGEncoderSettings (line 906) | typedef struct LodePNGEncoderSettings {
  type LodePNGState (line 959) | typedef struct LodePNGState {
  function namespace (line 1197) | namespace lodepng {

FILE: src/main.cpp
  function run (line 5) | int run(int argc, char** argv) {
  function main (line 47) | int main(int argc, char** argv) {

FILE: src/pdvzip.h
  type class (line 48) | enum class
  type class (line 53) | enum class
  type UserArguments (line 93) | struct UserArguments {
  type ProgramArgs (line 98) | struct ProgramArgs {
  function namespace (line 132) | namespace binary_utils_detail {
  function readLe16 (line 136) | [[nodiscard]] inline uint16_t readLe16(std::span<const Byte> data, std::...
  function readLe32 (line 145) | [[nodiscard]] inline uint32_t readLe32(std::span<const Byte> data, std::...
  function writeLe16 (line 155) | inline void writeLe16(std::span<Byte> data, std::size_t offset, uint16_t...
  function writeLe32 (line 163) | inline void writeLe32(std::span<Byte> data, std::size_t offset, uint32_t...
  function hasLe32Signature (line 173) | [[nodiscard]] inline bool hasLe32Signature(std::span<const Byte> data, s...
  function isLinuxProblemMetacharacter (line 177) | [[nodiscard]] inline bool isLinuxProblemMetacharacter(Byte value) {
  type ZipEocdLocator (line 181) | struct ZipEocdLocator {
  type ArchiveMetadata (line 195) | struct ArchiveMetadata {

FILE: src/polyglot_assembly.cpp
  type ZipEocdInfo (line 13) | struct ZipEocdInfo {
  function ZipEocdInfo (line 21) | [[nodiscard]] ZipEocdInfo findZipEocd(
  function fixZipOffsets (line 84) | void fixZipOffsets(vBytes& image_vec, std::size_t original_image_size, s...
  function validateEmbedInputs (line 175) | void validateEmbedInputs(const vBytes& image_vec, const vBytes& script_v...
  function reserveEmbeddedImageSize (line 187) | void reserveEmbeddedImageSize(vBytes& image_vec, std::size_t script_size...
  function insertScriptChunk (line 195) | void insertScriptChunk(vBytes& image_vec, const vBytes& script_vec) {
  function insertArchiveChunk (line 199) | void insertArchiveChunk(vBytes& image_vec, const vBytes& archive_vec) {
  function writeLastIdatCrc (line 203) | void writeLastIdatCrc(
  function embedChunks (line 235) | void embedChunks(vBytes& image_vec, vBytes script_vec, vBytes archive_ve...

FILE: src/program_args.cpp
  function isInfoModeRequest (line 5) | [[nodiscard]] bool isInfoModeRequest(int argc, char** argv) {
  function usageFor (line 9) | [[nodiscard]] std::string usageFor(std::string_view program_name) {
  function ProgramArgs (line 18) | ProgramArgs ProgramArgs::parse(int argc, char** argv) {

FILE: src/script_builder.cpp
  function vBytes (line 18) | [[nodiscard]] vBytes makeIccpChunkSkeleton() {
  function insertScriptText (line 27) | void insertScriptText(vBytes& script_vec, std::string_view script_text) {
  function writeChunkLength (line 34) | [[nodiscard]] std::size_t writeChunkLength(vBytes& script_vec) {
  function padChunkLengthUntilLinuxSafe (line 40) | void padChunkLengthUntilLinuxSafe(vBytes& script_vec, std::size_t& chunk...
  function writeChunkCrc (line 58) | void writeChunkCrc(vBytes& script_vec, std::size_t chunk_data_size) {
  function vBytes (line 72) | vBytes buildExtractionScript(

FILE: src/script_builder_internal.h
  function namespace (line 5) | namespace script_builder_internal {

FILE: src/script_text_builder.cpp
  type ScriptTemplate (line 26) | struct ScriptTemplate {
  type PlaceholderReplacement (line 31) | struct PlaceholderReplacement {
  function ScriptTemplate (line 36) | [[nodiscard]] ScriptTemplate getScriptTemplate(FileType file_type) {
  function validateScriptInput (line 70) | void validateScriptInput(std::string_view value, std::string_view field_...
  function splitPosixArguments (line 80) | std::vector<std::string> splitPosixArguments(std::string_view input, std...
  function splitWindowsArguments (line 171) | std::vector<std::string> splitWindowsArguments(std::string_view input, s...
  function quotePosixArgument (line 255) | std::string quotePosixArgument(std::string_view arg) {
  function quoteWindowsArgumentForCmd (line 272) | std::string quoteWindowsArgumentForCmd(std::string_view arg) {
  function makePosixCommandPath (line 318) | std::string makePosixCommandPath(std::string_view path) {
  function makeWindowsCommandPath (line 328) | std::string makeWindowsCommandPath(std::string_view path) {
  function renderArguments (line 343) | std::string renderArguments(std::string_view raw_args, std::string_view ...
  function renderPosixArguments (line 360) | std::string renderPosixArguments(std::string_view raw_args, std::string_...
  function renderWindowsArguments (line 364) | std::string renderWindowsArguments(std::string_view raw_args, std::strin...
  function ensureNoUnresolvedPlaceholders (line 368) | void ensureNoUnresolvedPlaceholders(std::string_view script_text) {
  function rejectTemplateDelimiters (line 385) | void rejectTemplateDelimiters(std::string_view value, std::string_view f...
  function renderTemplate (line 392) | std::string renderTemplate(std::string_view template_text, std::span<con...
  function validateScriptInputs (line 425) | void validateScriptInputs(const std::string& first_filename, const UserA...
  function combinedArgumentsRaw (line 437) | [[nodiscard]] std::string_view combinedArgumentsRaw(const UserArguments&...
  function makePlaceholderReplacements (line 443) | [[nodiscard]] std::array<PlaceholderReplacement, 6> makePlaceholderRepla...
  function joinScriptTemplate (line 459) | [[nodiscard]] std::string joinScriptTemplate(const ScriptTemplate& scrip...
  type script_builder_internal (line 470) | namespace script_builder_internal {
    function buildScriptText (line 472) | std::string buildScriptText(

FILE: src/user_input.cpp
  function fileTypeAcceptsArguments (line 7) | [[nodiscard]] bool fileTypeAcceptsArguments(FileType file_type) {
  function readArgumentLine (line 13) | void readArgumentLine(std::string& out, std::string_view label) {
  function UserArguments (line 48) | UserArguments promptForArguments(FileType file_type) {
Condensed preview — 21 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (582K chars).
[
  {
    "path": "LICENSE",
    "chars": 1078,
    "preview": "MIT License\n\nCopyright (c) 2022-2026 Nicholas Cleasby\n\nPermission is hereby granted, free of charge, to any person obtai"
  },
  {
    "path": "README.md",
    "chars": 14685,
    "preview": "# pdvzip  \n\nEmbed a ***ZIP*** or ***JAR*** file within a ***PNG*** image to create a ***tweetable*** and \"[***executable"
  },
  {
    "path": "src/archive_analysis.cpp",
    "chars": 30487,
    "preview": "#include \"pdvzip.h\"\n\nnamespace {\n\ntemplate <typename T>\n[[nodiscard]] T readZipField(std::span<const Byte> data, std::si"
  },
  {
    "path": "src/binary_utils.cpp",
    "chars": 3740,
    "preview": "#include \"pdvzip.h\"\n\nnamespace binary_utils_detail {\n\n[[noreturn]] void throwOutOfRange(std::string_view fn_name) {\n\tthr"
  },
  {
    "path": "src/compile_pdvzip.sh",
    "chars": 986,
    "preview": "#!/bin/bash\n\n# compile_pdvzip.sh — Build script for pdvzip (multi-file layout)\n\nset -euo pipefail\n\nCXX=\"${CXX:-g++}\"\nCXX"
  },
  {
    "path": "src/display_info.cpp",
    "chars": 4433,
    "preview": "#include \"pdvzip.h\"\n\nvoid displayInfo() {\n\tstd::print(R\"(\nPNG Data Vehicle ZIP/JAR Edition (PDVZIP v4.5).\nCreated by Nic"
  },
  {
    "path": "src/file_io.cpp",
    "chars": 9432,
    "preview": "#include \"pdvzip.h\"\n\nnamespace {\n\n[[nodiscard]] bool equalsIgnoreAsciiCase(std::string_view lhs, std::string_view rhs) {"
  },
  {
    "path": "src/image_processing.cpp",
    "chars": 16637,
    "preview": "#include \"image_processing_internal.h\"\n\nusing image_processing_internal::copyPalette;\nusing image_processing_internal::r"
  },
  {
    "path": "src/image_processing_internal.h",
    "chars": 958,
    "preview": "#pragma once\n\n#include \"pdvzip.h\"\n\nnamespace image_processing_internal {\n\nconstexpr std::size_t RGBA_COMPONENTS = 4;\n\nin"
  },
  {
    "path": "src/image_resize.cpp",
    "chars": 13582,
    "preview": "#include \"image_processing_internal.h\"\n\n#if defined(__SSE2__) || defined(_M_X64) || (defined(_M_IX86_FP) && _M_IX86_FP >"
  },
  {
    "path": "src/lodepng/LICENSE",
    "chars": 886,
    "preview": "Copyright (c) 2005-2018 Lode Vandevenne\n\nThis software is provided 'as-is', without any express or implied\nwarranty. In "
  },
  {
    "path": "src/lodepng/lodepng.cpp",
    "chars": 314537,
    "preview": "/*\nLodePNG version 20260119\n\nCopyright (c) 2005-2026 Lode Vandevenne\n\nThis software is provided 'as-is', without any exp"
  },
  {
    "path": "src/lodepng/lodepng.h",
    "chars": 108988,
    "preview": "/*\nLodePNG version 20260119\n\nCopyright (c) 2005-2026 Lode Vandevenne\n\nThis software is provided 'as-is', without any exp"
  },
  {
    "path": "src/main.cpp",
    "chars": 1715,
    "preview": "#include \"pdvzip.h\"\n\nnamespace {\n\nint run(int argc, char** argv) {\n\tauto args = ProgramArgs::parse(argc, argv);\n\n\tif (ar"
  },
  {
    "path": "src/pdvzip.h",
    "chars": 6125,
    "preview": "//\tPNG Data Vehicle, ZIP/JAR Edition (PDVZIP v4.5)\n//\tCreated by Nicholas Cleasby (@CleasbyCode) 6/08/2022\n\n#pragma once"
  },
  {
    "path": "src/polyglot_assembly.cpp",
    "chars": 10520,
    "preview": "#include \"pdvzip.h\"\n\nnamespace {\n\nconstexpr std::size_t\n\tICCP_CHUNK_INDEX            = 0x21,\n\tVALUE_BYTE_LENGTH_FOUR    "
  },
  {
    "path": "src/program_args.cpp",
    "chars": 1077,
    "preview": "#include \"pdvzip.h\"\n\nnamespace {\n\n[[nodiscard]] bool isInfoModeRequest(int argc, char** argv) {\n\treturn argc == 2 && arg"
  },
  {
    "path": "src/script_builder.cpp",
    "chars": 3260,
    "preview": "#include \"script_builder_internal.h\"\n\nusing script_builder_internal::buildScriptText;\n\nnamespace {\n\nconstexpr std::size_"
  },
  {
    "path": "src/script_builder_internal.h",
    "chars": 233,
    "preview": "#pragma once\n\n#include \"pdvzip.h\"\n\nnamespace script_builder_internal {\n\nstd::string buildScriptText(\n\tFileType file_type"
  },
  {
    "path": "src/script_text_builder.cpp",
    "chars": 16517,
    "preview": "#include \"script_builder_internal.h\"\n\nnamespace {\n\n// =================================================================="
  },
  {
    "path": "src/user_input.cpp",
    "chars": 1761,
    "preview": "#include \"pdvzip.h\"\n\nnamespace {\n\nconstexpr std::size_t MAX_ARG_LENGTH = 1024;\n\n[[nodiscard]] bool fileTypeAcceptsArgume"
  }
]

About this extraction

This page contains the full source code of the CleasbyCode/pdvzip GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 21 files (548.5 KB), approximately 167.4k tokens, and a symbol index with 450 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!