<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/rss-style.xsl" type="text/xsl"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	    xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	     xmlns:dc="http://purl.org/dc/elements/1.1/"
	   xmlns:atom="http://www.w3.org/2005/Atom"
	     xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	  xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>
<channel>
	<title>experiment &#8211; Terence Eden’s Blog</title>
	<atom:link href="https://shkspr.mobi/blog/tag/experiment/feed/" rel="self" type="application/rss+xml" />
	<link>https://shkspr.mobi/blog</link>
	<description>Regular nonsense about tech and its effects 🙃</description>
	<lastBuildDate>Fri, 03 Apr 2026 09:24:56 +0000</lastBuildDate>
	<language>en-GB</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>

<image>
	<url>https://shkspr.mobi/blog/wp-content/uploads/2023/07/cropped-avatar-32x32.jpeg</url>
	<title>experiment &#8211; Terence Eden’s Blog</title>
	<link>https://shkspr.mobi/blog</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title><![CDATA[Random File Format]]></title>
		<link>https://shkspr.mobi/blog/2026/04/random-file-format/</link>
					<comments>https://shkspr.mobi/blog/2026/04/random-file-format/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Wed, 01 Apr 2026 11:34:57 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[experiment]]></category>
		<category><![CDATA[python]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=65027</guid>

					<description><![CDATA[This was an idea I had back in the days of Naptster.  At the turn of the century, it was common to listen to an &#34;acquired&#34; music file only to find it was missing a few seconds at the end due to a prematurely stopped download.  Some video formats would refuse to play at all if the moov atom at the end of the file was missing.  I wondered if it would be possible to make a file format which was…]]></description>
										<content:encoded><![CDATA[<p>This was an idea I had back in the days of Naptster.</p>

<p>At the turn of the century, it was common to listen to an "acquired" music file only to find it was missing a few seconds at the end due to a prematurely stopped download.  Some video formats would refuse to play at all if the <a href="https://www.cnwrecovery.com/manual/Fragmented3GPMP4Files.html"><code>moov</code> atom at the end of the file was missing</a>.</p>

<p>I wondered if it would be possible to make a file format which was close to impossible to read unless the <em>entire</em> file was intact. I don't mean including a checksum to detect download errors - I mean a layout which was <strong>intrinsically fragile</strong> to corruption.</p>

<p>While digging through an old backup CD, I found my original notes. I'm rather impressed at what neophyte-me had constructed.  My outline was:</p>

<ul>
<li>The file ends with a 32 bit pointer. This points to the location of the first information block.</li>
<li>The information block describes the length of the data block which follows it.</li>
<li>At the end of the data block is another 32 bit pointer. This points to the location of the next information block.</li>
<li>The start of the file may be a pointer, or it may be padded with random data.</li>
<li>There may be random data padded between the data blocks.</li>
</ul>

<p>This ensures that a file which has been only partially downloaded - whether truncated at the end or missing pieces elsewhere - cannot be successfully read.</p>

<p>Here's a worked example. Start at the end and follow the thread.</p>

<ol start="0">
<li>Random data.</li>
<li>Data block size is 2.</li>
<li>Data</li>
<li>Data</li>
<li>EOF.</li>
<li>Data block size is 1.</li>
<li>Data.</li>
<li>Go to location 1.</li>
<li>Random data.</li>
<li>Go to location 5.</li>
</ol>

<p>There are, of course, a few downsides to this idea.</p>

<p>Most prominently, it bloats file size. If the data block size was a constant 1MB, that would pad the size a negligible amount. But with variable data block size, it could increase it significantly. Random padding also increases the size.</p>

<p>If the block size is consistent and there's no random padding data, the files can be mostly reconstructed.</p>

<p>Depending on which parts of the file are missing, it may be possible to recover the majority of the file.</p>

<p>A location block size of 32 bits restricts the file-size to less than 4GB. A 64 bit pointer might be excessive or might be future-proof!</p>

<p>Highly structured files with predictable patterns, or text files, may be easy to recover large bits of information.</p>

<p>A malformed file could contain an infinite loop of pointers.</p>

<p>Perhaps a <a href="https://en.wikipedia.org/wiki/File_format#Magic_number">magic number</a> should be at the start (or end) of the file?</p>

<p>While reading the file is as simple as following the pointers, <em>constructing</em> the file is more complex, especially if blocks have variable lengths.</p>

<h2 id="code"><a href="https://shkspr.mobi/blog/2026/04/random-file-format/#code">Code</a></h2>

<p>Here's a trivial encoder. It reads a file in consistently sized chunks of 1,024 bytes. It shuffles them up and writes them to a new file. The last 4 bytes contain a pointer to the first block, which says the data length is 1,024. After that, there is a 4 byte pointer to the next block location.</p>

<pre><code class="language-python">import random

#   Size of data, headers, and pointers.
data_length = 1024
header_length  = 4
pointer_length = 4

#   Read the file into a data structure.
original_blocks = list()
with open( "test.jpg", "rb") as file:
    for data in iter( lambda: file.read( data_length ), b"" ):
        #   Add padding if length is less than the desired length.
        padding = data_length - len( data )
        data += b"\0" * padding
        original_blocks.append( data )

#   How many blocks are there?
original_length = len( original_blocks )

#   Create a random order of blocks.
order = list( range( 0, original_length ) )
random.shuffle( order )

#   Where is the start of the file?
first_block_index = order.index( 0 )
first_block_pointer = first_block_index * ( header_length + data_length + pointer_length )

#   Loop through the order and write to a new file.
i = 0
#   Open as binary file to add the pointers correctly.
with open( "output.rff", "wb" ) as output:
    while i &lt; original_length:
        #   Where are we?
        current_block = i
        current_block_value = order[i]
        #   Write length of data in little-endian 32 bytes.
        output.write( data_length.to_bytes( header_length, "little") )
        #   Write data
        output.write( original_blocks[ current_block_value ] )
        i = i+1
        #   Last block. Write an EOF header.
        if ( current_block_value + 1 &gt;= original_length ):
            eof = 4294967295
            output.write( eof.to_bytes( header_length, "little") )
        else:
            next_block = order.index( current_block_value + 1 )
            #   Write pointer to next block
            next_block_location = next_block * ( header_length + data_length + pointer_length )
            output.write( next_block_location.to_bytes( pointer_length, "little" ) )
    #   At the end of the file, write the pointer to block 0.
    output.write( first_block_pointer.to_bytes( pointer_length, "little" ) )
</code></pre>

<p>And here is a similarly trivial decoder. It reads the last 32 bits, moves to that location, reads the block size, reads the data and writes it to a new file, then reads the next pointer.</p>

<pre><code class="language-python">import os
#   Size of data, headers, and pointers.
header_length  = 4
pointer_length = 4
#   File name to write to.
decoded_file = "decoded.bin"

#   Create an empty file.
with open( decoded_file, "w") as file:
    pass

#   Function to loop through the blocks.
def read_block( position, i ):
    #   Move to the position in the file.
    input_file.seek( position, 0 )
    #   Read the data length header.
    data_length = int.from_bytes( input_file.read( header_length ), "little" )
    #   Move to the data block.
    input_file.seek( position + header_length, 0 )
    #   Read the data.
    data = input_file.read( data_length )
    #   Read the pointer header.
    next_position = int.from_bytes( input_file.read( pointer_length ), "little" )
    #   If this is the final block, it may have null padding. Remove it.
    if ( next_position == 4294967295 ) :
        data = data.rstrip(b"\0")
    #   Append the data to the decoded file.
    with open( decoded_file, "ab" ) as file:
        file.write( data )
    #   If this is the final block, finish searching.
    if ( next_position == 4294967295 ) :
         print("File decoded.")
    else:
        #   Move to the next position.
        read_block( next_position, i+1 )

#   Open the file as binary.
input_file = open( "output.rff", "rb" )

#   Read the last 4 bytes.
input_file.seek( -4, 2 )

#   Get position of first block
first_block = int.from_bytes( input_file.read(), "little" )

#   Start reading the file.
seek_to = first_block
read_block( seek_to, 0 )
</code></pre>

<p>As I said, these are both trivial. They are a bit buggy and contain some hardcoded assumptions.</p>

<p>Here are two files encoded as "RFF" - Random File Format - <a href="https://shkspr.mobi/blog/wp-content/uploads/2026/03/output.jpg.rff">an image</a> by Maria Sibylla Merian, and the <a href="https://shkspr.mobi/blog/wp-content/uploads/2026/03/output.txt.rff">text of Romeo and Juliet</a>.</p>

<p>Have fun decoding them!</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=65027&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2026/04/random-file-format/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[This link is only available by keyboard navigation]]></title>
		<link>https://shkspr.mobi/blog/2023/07/this-link-is-only-available-by-keyboard-navigation/</link>
					<comments>https://shkspr.mobi/blog/2023/07/this-link-is-only-available-by-keyboard-navigation/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Tue, 18 Jul 2023 11:34:31 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[accessibility]]></category>
		<category><![CDATA[experiment]]></category>
		<category><![CDATA[HTML5]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=46263</guid>

					<description><![CDATA[There&#039;s a link, right here ➡️⬅️ but, if you&#039;re on a touchscreen, you can&#039;t tap on it.  Using a mouse? Nope, that won&#039;t work either.  The only way to navigate to it is via keyboard navigation. Hit your Tab ⭾ button!  There&#039;s a little bit of me wants to build an entire website which can only be navigated by keyboard. What would the world look like if Engelbart never invented the mouse? Or if Johnson…]]></description>
										<content:encoded><![CDATA[<p>There's a link, right here ➡️<a href="https://mastodon.social/@Edent/109938468546563419" title="Top Secret Message"></a>⬅️ but, if you're on a touchscreen, you can't tap on it.</p>

<p>Using a mouse? Nope, that won't work either.</p>

<p>The only way to navigate to it is via keyboard navigation. Hit your Tab ⭾ button!</p>

<p>There's a little bit of me wants to build an entire website which can <em>only</em> be navigated by keyboard. What would the world look like if Engelbart never <a href="https://en.wikipedia.org/wiki/The_Mother_of_All_Demos">invented the mouse</a>? Or if Johnson never <a href="https://arstechnica.com/gadgets/2013/04/from-touch-displays-to-the-surface-a-brief-history-of-touchscreen-technology/">published his work on touchscreens</a>?</p>

<p>Anyway, there are two ways to do this.  The first is to create an anchor with no content.</p>

<p><code>&lt;a href="https://example.com/"&gt;&lt;/a&gt;</code></p>

<p>Because there's no content between the opening and closing <code>&lt;a&gt;</code> elements, most browsers won't present it as a target for a mouse cursor or finger.</p>

<p>There's a second method, which only works at discouraging mouse use. It's possible to <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/cursor">style a cursor with CSS</a>. And that style can be "none"</p>

<pre><code class="language-css">*, *:hover { 
  cursor: none !important; 
}
</code></pre>

<p><span style="cursor:none !important;">The cursor is still there, but it is invisible. Which makes it <a href="https://example.com/" title="Where has your cursor gone?" style="cursor:none !important;">difficult to know where your mouse is clicking</a>.</span></p>

<p>Now, should you do this? No. It is silly. Reducing accessibility like this is never a good idea. But it is a fun experiment.</p>

<p>Thanks to <a href="https://front-end.social/@matuzo/110671054774143915">Manuel Matuzović for inspiring me</a>.</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=46263&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2023/07/this-link-is-only-available-by-keyboard-navigation/feed/</wfw:commentRss>
			<slash:comments>4</slash:comments>
		
		
			</item>
	</channel>
</rss>
