This service tests the validity of an RSS 2.0 feed, checking to see that it follows the rules of the RSS specification. For advice from the RSS Advisory Board on how to implement RSS and handle issues such as enclosures and HTML encoding, read the RSS Best Practices Profile. This checker is also a validator of Atom and RSS 1.0 feeds.
Use this tester regularly to ensure that your RSS feed continues to work well in the wide audience of RSS readers, podcast clients and other software that supports the format.
This is a valid RSS feed.
This feed is valid, but interoperability with the widest range of feed readers could be improved by implementing the following recommendations.
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?><rss version="2.0"> <channel> <title>doclic's blog!</title> <link>http://doclic.eu/blog/index-en.htm</link> <description>ramblings about computers</description> <language>en</language> <managingEditor>doclic@tutanota.com (doclic)</managingEditor> <webMaster>doclic@tutanota.com (doclic)</webMaster> <pubDate>Thu, 17 Sep 2026 00:00:00 GMT</pubDate> <generator>doclic's garbage code</generator> <docs>https://www.rssboard.org/rss-2-0</docs> <ttl>1440</ttl> <image> <url>http://doclic.eu/badges/ME.gif</url> <title>doclic's blog!</title> <link>http://doclic.eu/blog/index-en.htm</link> <width>88</width> <height>31</height> </image> <skipHours> <hour>1</hour> <hour>2</hour> <hour>3</hour> <hour>4</hour> <hour>5</hour> <hour>6</hour> <hour>7</hour> <hour>8</hour> </skipHours> <item> <title>Use .ZIP files for assets</title> <link>http://doclic.eu/blog/use-zip-files-for-assets.htm</link> <author>doclic@tutanota.com (doclic)</author> <guid isPermaLink="true">http://doclic.eu/blog/use-zip-files-for-assets.htm</guid> <pubDate>Thu, 17 Sep 2026 00:00:00 GMT</pubDate> <source url="http://doclic.eu/rss-en.xml"> doclic's blog! </source> <description><p>I like to make small games for home computers and sharing them around. Everygame loads assets such as images or sound effects. These assets are often storedin a directory next to the game's executable file, which isn't ideal becausethis method requires the programmer to share their game in an archive formatsuch as a<a href="https://en.wikipedia.org/wiki/Tar_%28computing%29?useskin=monobook"><code>.TAR</code></a>or<a href="https://en.wikipedia.org/wiki/ZIP_%28file_format%29?useskin=monobook"><code>.ZIP</code></a>file that contains everything the game needs. The game can no longer run on itsown.</p><p>Because of this, I looked for better ways to store game assets and found SheriefFarouk's blog post<a href="https://sherief.fyi/post/atomic-application/">"The Atomic Application"</a>where the author talks about the<a href="https://icculus.org/physfs/">PhysicsFS</a>library. This library can read an archive stored in the game's source code andload assets from there.<br> It's a well-made tool however it's way too complex forwhat I want to do. It supports 13 archive formats including bespoke ones likethe <code>.ISO</code> format used on CD-ROMs. Storing the archive in the source code alsomeans that the whole archive is stored in RAM for the full runtime of our game.Not ideal.</p><p>After a bit more searching, I found this blog post by Dominic Szablewski:<a href="https://phoboslab.org/log/2024/09/qop">"A Simple Archive Format for Self-Contained Executables"</a>.It starts with a hack where the assets are embedded in the game's program byconcatenating the program with an asset archive using a command such as<a href="https://manpages.debian.org/trixie/coreutils/cat.1"><code>cat(1)</code></a>.This means that the assets aren't loaded by the OS when the program is ran,however we now have to do that manually with our code. This requires knowing thepath to our program binary which is inherently platform-specific. The author ofthe post gives us<a href="https://github.com/phoboslab/qop/blob/master/example.c#L17-L35">a function</a>that works nicely for Linux, Apple Mac OS X and Microsoft Windows.</p><p>The post goes on describing a new format, the<a href="https://github.com/phoboslab/qop"><code>.QOP</code></a>format which is very simple and has a few quirks that make it really nice to usefor this scenario: file paths are hashed and the archive "header" (tailer?) isat the end of the file, unlike many other formats that store it at the start ofthe file. The hashing of file paths makes it easy to find files (see<a href="https://en.wikipedia.org/wiki/Hash_table?useskin=monobook">hashmaps</a>).The header being stored at the end makes it easy to locate without knowing thesize of the archive. It's a great format! The tools around it aren't very welldeveloped however and there's no formal specification to follow whenimplementing it, meaning that different implementations may have slightdifferences, making the creation of mods and extraction of assets harder.</p><h2>A better format</h2><p>I searched for the perfect file format for this use-case. Ideally one that doesall the following:</p><ul> <li> simple format that's easy to read,</li> <li> popular format with good tools,</li> <li> free specification (or similar) describing the format in details,</li> <li> ASCII (or compatible) encoding of file paths,</li> <li> no decompression required,</li> <li> header at the end of the file and</li> <li> cryptographic hashing for file paths.</li></ul><p>After looking at the<a href="https://en.wikipedia.org/wiki/Tar_%28computing%29?useskin=monobook"><code>.TAR</code></a>,<a href="https://en.wikipedia.org/wiki/Cpio?useskin=monobook"><code>.CPIO</code></a>,<a href="https://manpages.debian.org/trixie/binutils-common/ar.1"><code>.AR</code></a>,<a href="https://en.wikipedia.org/wiki/ZIP_%28file_format%29?useskin=monobook"><code>.ZIP</code></a>,<a href="https://en.wikipedia.org/wiki/RAR_%28file_format%29?useskin=monobook"><code>.RAR</code></a>,<a href="https://en.wikipedia.org/wiki/StuffIt?useskin=monobook"><code>.SIT</code></a>,<a href="https://en.wikipedia.org/wiki/ARC_%28file_format%29?useskin=monobook"><code>.ARC</code></a>,<a href="https://7-zip.org/7z.html"><code>.7Z</code></a>,<a href="https://en.wikipedia.org/wiki/Cabinet_%28file_format%29?useskin=monobook"><code>.CAB</code></a>,<a href="https://en.wikipedia.org/wiki/Windows_Imaging_Format?useskin=monobook"><code>.WIM</code></a>and<a href="https://en.wikipedia.org/wiki/ISO_9660?useskin=monobook">ISO 9660 (used by CD-ROM)</a>,I've found that the only viable format is<a href="https://en.wikipedia.org/wiki/ZIP_%28file_format%29?useskin=monobook"><code>.ZIP</code></a>.The only thing that <code>.ZIP</code> is missing is the hashing of file paths. The <code>.ZIP</code>format supports archives stored only multiple diskettes so the creator of theformat,<a href="https://en.wikipedia.org/wiki/Phil_Katz?useskin=monobook">Phil Katz</a>,decided to store the file index and archive header at the end of the archive sothat adding files to the archive would only require inserting the last disc.This means we can concatenate our program and a <code>.ZIP</code> archive and the resultwill readable by most tools. Some tools complain about the positions stored inthe file index being offset. Example on Linux with <a href="https://manpages.debian.org/trixie/unzip/unzip.1"><code>unzip(1)</code></a>:</p><pre>$ unzip uberprogrammeArchive: uberprogrammewarning [uberprogramme]: 15968 extra bytes at beginning or within zipfile (attempting to process anyway) extracting: exemple.png$</pre><p>This isn't hard to fix, we'll see how later. The format also supportscompression of individual files and storing information such as UNIX file modebits. We can write a decoder that doesn't support these features, returning anerror if it encounters them.</p><h2>Implementation</h2><p>The <code>.ZIP</code> format is specified in a file called <code>APPNOTE.TXT</code> published by<a href="https://en.wikipedia.org/wiki/PKWare?useskin=monobook">PKWARE</a>.There are many versions of this spec. I used<a href="https://pkware.cachefly.net/webdocs/APPNOTE/APPNOTE-1.0.txt">version 1.0</a>because it's the first version and therefore the most compatible.<br> We start by reading the End of Central Directory (EOCD) record stored at the endof the archive. It looks like this:</p><pre>/* every number is read little endian */struct eocd { unsigned char signature[4]; /* always "PK\x05\x06" in ASCII */ uint16_t disc_number; /* always 0 for us */ uint16_t disc_number_of_index; /* always 0 for us */ uint16_t file_count_on_disc; uint16_t file_count_total; uint32_t index_size; uint32_t index_pos; uint16_t comment_len; /* always 0 for us */};</pre><p>The signature starts with the initials of Phil Katz, the creator of the format.<code>file_count_on_disc</code> and <code>file_count_total</code> need to have the same value becausewe don't support multiple discs.<br> We can now find the file index. <code>index_pos</code> possibly contains incorrectinformation perhaps because some idiot decided to put an entire game's codebefore the archive contents. We can instead calculate the position of the indexfrom the end of the file using <code>-(sizeof(struct eocd) - index_size)</code>because (usually) the index is right before the EOCD record. We can then use theoffset between <code>index_pos</code> and the actual index position to know by how much thefile is offset.<br> For programs like <code>unzip(1)</code> to stop complaining about the archive being offset,we can update all the positions stored in the archive after concatenating.</p><p>We know where the index is, so we can read it now. Each entry in the index is aCentral Directory File Header (CDFH) record, which looks like this:</p><pre>/* every number is read little endian */struct cdfh { unsigned char signature[4]; /* always "PK\x01\x02" in ASCII */ uint16_t version_created_by; /* can be ignored */ uint16_t version_to_extract; /* low byte <= 10 */ uint16_t flags; /* always 0 for us */ uint16_t compression; /* always 0 for us */ uint16_t last_edit_time; /* can be ignored */ uint16_t last_edit_date; /* can be ignored */ uint32_t crc32; /* can be ignored */ uint32_t size_before_compression; uint32_t size_after_compression; uint16_t path_len; uint16_t extra_len; uint16_t comment_len; uint16_t disc_num; uint16_t internal_attributes; uint32_t external_attributes; uint32_t local_header_pos; /* relative to archive start */ /* this isn't valid C but it works for the sake of the example */ unsigned char path[path_len];};</pre><p>Note that the signature is different from the EOCD.<br> <code>version_to_extract</code> is kinda weird, the low byte is the minimum format versionto implement (up to 10 for version 1.0). The high byte is an OS identifier, 0represents Microsoft DOS for example. We can ignore the high byte because it'sonly useful to decode information we don't care about. Even if the archive wascreated on Microsoft DOS, Microsoft Windows or OS/2 which all use \ as a filepath separator, the archive will use / as the file path separator.<br> <code>size_before_compression</code> and <code>size_after_compression</code> have the same value sincewe don't do compression. They store the file size in bytes.<br> The <code>path</code> is encoded with<a href="https://en.wikipedia.org/wiki/Code_page_437?useskin=monobook">IBM code page 437</a>.This is the same ASCII-compatible encoding that the IBM PC and clones use. Newerversions of the format also support UTF-8 paths.</p><p>We're almost done, but we also need to deal with local file headers. This headeris stored right before each file's content and doesn't contain anything reallyuseful:</p><pre>/* every number is read little endian */struct lfh { unsigned char signature[4]; /* always "PK\x03\x04" in ASCII */ uint16_t version_to_extract; uint16_t flags; uint16_t compression; uint16_t last_edit_time; uint16_t last_edit_date; uint32_t crc32; uint32_t size_before_compression; uint32_t size_after_compression; uint16_t path_len; uint16_t extra_len; unsigned char path[path_len]; unsigned char extra[extra_len];};</pre><p>We can either read this header to ensure it contains the data we assume it doesor decide to skip it entirely, which may cause issues if it contains incorrectdata since this header has a variable length. The file contents are stored rightafter this header.</p><h2>Performance</h2><p>Even if <code>.ZIP</code> is pretty simple, I wanted to compare its performance with<code>.QOP</code>. I compared 3 different algorithms for reading the archive headers and1 file from the archive:</p><ul> <li> qop.h,</li> <li> a minimal <code>.ZIP</code> implementation that skips the local file header and</li> <li> a <code>.ZIP</code> implementation that validates the local file header.</li></ul><p>These algorithms are so basic that on my test machine (AMD Ryzen 5 2600 withArch Linux last updated on 13 September 2026), I needed tens of thousands offiles for the difference to be significant enough. Using code compiled with<code>-O3</code> on archives with 50,000 files, I got the following results:</p><ul> <li> 8.8ms ± 3.0ms for qop.h</li> <li> 7.7ms ± 2.7ms for the minimal <code>.ZIP</code></li> <li> 57.3ms ± 4.3ms for the <code>.ZIP</code> that validates local file headers</li></ul><p>It's interesting to see that minimal <code>.ZIP</code> is actually faster than <code>.QOP</code>, butthe difference is not very big. In real world scenarios, the real bottleneckwould be the hard disc's reading speed anyway. I could also read the local fileheaders when reading each file instead of when opening the archive, but I don'tknow how to meaningfully compare this method with the other algorithms.</p><p>Using <code>.ZIP</code> doesn't seem to have a major impact on performance. However, a<code>.ZIP</code> file without compression is generally larger than a <code>.QOP</code> because theheaders are bigger and file paths are stored twice for each file. In my tests,the <code>.QOP</code> archive is 4,488,902 bytes and the <code>.ZIP</code> is almost twice the sizewith 7,477,802 bytes.</p><p>My testing code is available using<a href="http://doclic.eu/ftp/zip_qop_bench.tar.gz">HTTP</a>and using<a href="ftp://ftp@doclic.eu/pub/zip_qop_bench.tar.gz">FTP</a>.</p><h2>Closing thoughts</h2><p>The <code>.ZIP</code> format is not a strictly superior or inferior format when compared to<code>.QOP</code>. Each format has its trade-offs, <code>.ZIP</code> having more flexibility, bettertools and better support by third-party programs, but <code>.QOP</code> is simpler andsmaller. There is no single best format for every use-case, you should chooseaccording to what fits your needs best.</p> </description> </item> </channel></rss> If you would like to create a banner that links to this page (i.e. this validation result), do the following:
Download the "valid RSS" banner.
Upload the image to your own server. (This step is important. Please do not link directly to the image on this server.)
Add this HTML to your page (change the image src attribute if necessary):
If you would like to create a text link instead, here is the URL you can use:
http://www.rssboard.org/rss-validator/check.cgi?url=http%3A//doclic.eu/blog/rss-en.xml