<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://nullonerror.org/feed.xml" rel="self" type="application/atom+xml" /><link href="https://nullonerror.org/" rel="alternate" type="text/html" /><updated>2026-08-14T13:55:20+00:00</updated><id>https://nullonerror.org/feed.xml</id><title type="html">NULL on error</title><subtitle>NULL on error</subtitle><author><name>Rodrigo Delduca</name><email>rodrigo@delduca.org</email></author><entry><title type="html">As gambiarras mais impiedosas que já fiz: parte 2</title><link href="https://nullonerror.org/2026/08/14/as-gambiarras-mais-impiedosas-que-ja-fiz-parte-2/" rel="alternate" type="text/html" title="As gambiarras mais impiedosas que já fiz: parte 2" /><published>2026-08-14T00:00:00+00:00</published><updated>2026-08-14T00:00:00+00:00</updated><id>https://nullonerror.org/2026/08/14/as-gambiarras-mais-impiedosas-que-ja-fiz-parte-2</id><content type="html" xml:base="https://nullonerror.org/2026/08/14/as-gambiarras-mais-impiedosas-que-ja-fiz-parte-2/"><![CDATA[<p>Dando continuidade à <a href="https://nullonerror.org/2026/08/12/as-gambiarras-mais-impiedosas-que-ja-fiz-parte-1/">parte 1</a>.</p>

<p>Antes da gambiarra da parte 1, eu trabalhava, em meu segundo emprego, com mídia indoor. Para quem não sabe, mídia indoor são aquelas televisões informativas instaladas em elevadores, supermercados e outros locais com média ou grande circulação de pessoas, que também exibem propagandas de vez em quando.</p>

<p>Quando cheguei lá, eles já tinham um sistema que funcionava muito bem. Era um player de vídeo que usava o <a href="http://www.mplayerhq.hu/">MPlayer</a> ou, às vezes, uma distribuição Linux chamada <a href="https://distrowatch.com/table.php?distribution=movix">MoviX</a>. O único problema era o acesso remoto e o gerenciamento das playlists, que às vezes era feito por SSH ou exigia que alguém fosse até o local.</p>

<p>Porém, além do gerenciamento remoto, que seria relativamente fácil de resolver, havia outro problema:</p>

<p>A banda de internet.</p>

<p>Naquela época, não existiam CDNs ou, caso existissem, custavam uma nota preta e eram de difícil acesso. A Amazon Web Services até existia, pois surgiu em 2006, e estamos falando de aproximadamente 2008, mas ainda era desconhecida, cara e não tinha infraestrutura no Brasil. Essa ideia de cadastrar um cartão em um serviço cujo valor oscilava estava completamente fora da realidade da minha bolha naquela época. Talvez uma VPS servisse, mas, de qualquer forma, esse não é o ponto.</p>

<p>Nós tínhamos alguns “servidores”, também conhecidos como computadores pessoais antigos, lentos e que já não estavam sendo utilizados, além de uma conexão de 1 megabit compartilhada com todo o escritório.</p>

<p>Naquela época, eu já era um grande fã da Valve, da Blizzard e de outras empresas que, por baixo dos panos, usavam <a href="https://en.wikipedia.org/wiki/BitTorrent">BitTorrent</a> para distribuir seus jogos. No instalador da Blizzard, inclusive, era possível ver detalhes como a quantidade de seeders e leechers durante o download, além da velocidade da transferência.</p>

<p>Então, munido desse conhecimento, decidi criar um player de vídeo em C++ com uma <a href="https://www.libtorrent.org/">biblioteca de torrent</a> embutida.</p>

<p>O player fazia polling constantemente, pois WebSocket ainda não existia. Ao receber uma playlist, ele calculava a diferença para descobrir quais arquivos precisavam ser baixados e quais deveriam ser excluídos do disco. Depois de identificar as mídias necessárias, iniciava os downloads usando torrent.</p>

<p>Para deixar as coisas ainda mais interessantes, eu usava um <a href="https://www.getright.com/seedtorrent.html">HTTP seed</a>, por meio do qual alguns dos primeiros pedaços eram baixados por HTTP, de forma aleatória.</p>

<p>Na época, tínhamos algumas dezenas de máquinas. Como todas compartilhavam os arquivos entre si, a carga sobre a conexão do escritório diminuía bastante, enquanto os downloads se tornavam muito mais rápidos. Seria possível até saturar a conexão dos locais onde as máquinas estavam instaladas, mas, por precaução, o próprio cliente torrent limitava o uso da banda.</p>

<p>Bem, talvez isso não seja exatamente uma <em>gambiarra</em>, mas foi um feito memorável da minha carreira.</p>

<p>Espero que gostem e compartilhem as gambiarras de vocês nos comentários.</p>

<p>Até!</p>]]></content><author><name>Rodrigo Delduca</name><email>rodrigo@delduca.org</email></author><summary type="html"><![CDATA[Dando continuidade à parte 1.]]></summary></entry><entry><title type="html">As gambiarras mais impiedosas que já fiz: parte 1</title><link href="https://nullonerror.org/2026/08/12/as-gambiarras-mais-impiedosas-que-ja-fiz-parte-1/" rel="alternate" type="text/html" title="As gambiarras mais impiedosas que já fiz: parte 1" /><published>2026-08-12T00:00:00+00:00</published><updated>2026-08-12T00:00:00+00:00</updated><id>https://nullonerror.org/2026/08/12/as-gambiarras-mais-impiedosas-que-ja-fiz-parte-1</id><content type="html" xml:base="https://nullonerror.org/2026/08/12/as-gambiarras-mais-impiedosas-que-ja-fiz-parte-1/"><![CDATA[<blockquote>
  <p>Gambiarra, de modo geral, é conhecida como uma espécie de quebra-galho ou atalho para conseguir algo usando a criatividade, muitas vezes de forma pejorativa. Ressalto que, neste texto, a ideia é justamente enfatizar a criatividade diante de grandes desafios.</p>
</blockquote>

<p>Certa vez, enquanto trabalhava com aplicativos mobile e livros digitais, fui encarregado de renderizar um livro em um aplicativo Android nativo. React Native ainda não existia. Talvez o Sencha Touch já existisse, mas, de qualquer forma, os tablets-alvo eram muito lentos e tinham pouquíssimos recursos.</p>

<p>Pois bem, o que você faria nessa situação? Fácil, não é? Renderizaria as páginas do PDF como imagens JPEG e criaria um aplicativo para paginá-las, a.k.a. um livro digital.</p>

<p>Sim!</p>

<p>Não, claro que não!</p>

<p>Seu gerente gosta de desafiar você e não gostou nem da qualidade das imagens nem do espaço ocupado em disco.</p>

<p>Então, ele propôs usar Adobe Flash. Sim, naquela época ele ainda existia, embora já estivesse no fim da vida.</p>

<p>Mas como? Ninguém sabia.</p>

<p>Foi então que tive a ideia de criar uma WebView de um pixel por um pixel e embutir o Adobe Flash nela para renderizar as páginas sob demanda e com a máxima qualidade. As páginas viriam de um arquivo <code class="language-plaintext highlighter-rouge">.swf</code>, pois o software usado para criar os livros exportava a arte vetorial para SWF. Nos PDFs, por algum motivo, tudo era convertido em bitmap.</p>

<p>Mas surgiu outro problema: como transferir dados entre o aplicativo Android, escrito em Java, a WebView, em JavaScript, e o Adobe Flash?</p>

<p>Criei uma WebView de um pixel por um pixel com o Flash Player rodando dentro dela. Na época, essa era a única maneira de executar Flash no Android, pois o Adobe AIR ainda não existia.</p>

<p>Fazendo o Java chamar <code class="language-plaintext highlighter-rouge">webView.navigate("javascript: ...")</code>, eu conseguia executar uma função JavaScript e passar o bitmap renderizado da página solicitada. Sim, o bitmap inteiro era transmitido como uma string.</p>

<p>Por sorte, a WebView não tinha uma limitação de 4.096 caracteres, ou algo semelhante às limitações comuns em servidores HTTP. Portanto, era possível transmitir páginas muito grandes e em alta resolução.</p>

<p>Feito isso, bastava repassar os dados para o Flash. Mas como?</p>

<p>Exemplo simplificado:</p>

<p>No lado do Flash, em ActionScript:</p>

<div class="language-actionscript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">import</span> <span class="nx">flash</span><span class="p">.</span><span class="nx">external</span><span class="p">.</span><span class="nx">ExternalInterface</span><span class="o">;</span>

<span class="k">if</span> <span class="p">(</span><span class="nx">ExternalInterface</span><span class="p">.</span><span class="nx">available</span><span class="p">)</span> <span class="p">{</span>
  <span class="nx">ExternalInterface</span><span class="p">.</span><span class="nx">addCallback</span><span class="p">(</span><span class="s2">"callFlashMethod"</span><span class="p">,</span> <span class="nx">nativeFlashFunction</span><span class="p">)</span><span class="o">;</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nx">nativeFlashFunction</span><span class="p">(</span><span class="nx">message</span><span class="o">:</span><span class="nx">String</span><span class="p">)</span><span class="o">:</span><span class="nx">String</span> <span class="p">{</span>
  <span class="k">return</span> <span class="s2">"Flash received message: "</span> <span class="o">+</span> <span class="nx">message</span><span class="o">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Então, no lado da WebView, em JavaScript:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">flashPlayer</span><span class="p">.</span><span class="nx">callFlashMethod</span><span class="p">(</span><span class="dl">"</span><span class="s2">Hello from JS!</span><span class="dl">"</span><span class="p">);</span>
</code></pre></div></div>

<p>Feio? É, eu sei. Mas funcionou perfeitamente.</p>

<p>Passamos a ter páginas com alto DPI, zoom, rotação e tudo mais.</p>

<p>Como bônus, ficou bem rápido, pois o aparelho precisava renderizar cada página apenas uma vez. Isso acontecia em menos de meio segundo. Depois, bastava desenhar a imagem na tela. :-)</p>

<h2 id="compartilhe-também-a-sua-gambiarra-nos-comentários">Compartilhe também a sua gambiarra nos comentários</h2>]]></content><author><name>Rodrigo Delduca</name><email>rodrigo@delduca.org</email></author><summary type="html"><![CDATA[Gambiarra, de modo geral, é conhecida como uma espécie de quebra-galho ou atalho para conseguir algo usando a criatividade, muitas vezes de forma pejorativa. Ressalto que, neste texto, a ideia é justamente enfatizar a criatividade diante de grandes desafios.]]></summary></entry><entry><title type="html">Nostalgia</title><link href="https://nullonerror.org/2026/08/07/nostalgia/" rel="alternate" type="text/html" title="Nostalgia" /><published>2026-08-07T00:00:00+00:00</published><updated>2026-08-07T00:00:00+00:00</updated><id>https://nullonerror.org/2026/08/07/nostalgia</id><content type="html" xml:base="https://nullonerror.org/2026/08/07/nostalgia/"><![CDATA[<blockquote>
  <p>“A nostalgia é um sentimento permanente. Em outras palavras, sentimos nostalgia mesmo vivendo em nosso próprio país, ao lado das pessoas que mais amamos. Apesar de um lar feliz e de uma família feliz, um homem pode sofrer de nostalgia simplesmente porque sente que sua alma está sendo limitada, que ela não se desenvolve da maneira como deseja. A nostalgia é o sentimento de impotência diante do mundo, a dor de não conseguir transmitir sua espiritualidade aos outros.”</p>

  <p><strong>Andrei Tarkóvski</strong></p>
</blockquote>

<p>Se você chegou aqui pelo feed RSS, deixe um comentário. Quero ver se ainda há assinantes. :-)</p>

<p>Esse vai ser um post diferente.</p>

<p>Antigamente eu gostava de compartilhar snippets de código. Hoje, com as LLMs, acho que faz mais sentido perguntar para elas do que correr o risco de dar um copy-paste de um Zé Ninguém da internet.</p>

<p>Não sei se é a meia-idade batendo, mas sinto muita saudade da internet dos anos 2000.
Tínhamos salas de IRC sempre cheias, zines, e era praticamente obrigatório todo site ter um feed RSS. Até o Google fazer o favor de matar o Google Reader, que, na minha opinião, era a melhor rede social que eles já criaram. Sim, eu considero o Reader uma rede social. Lembro de chegar ao trabalho e a primeira coisa que ia fazer era abrir o Reader para ver os comentários dos meus amigos em notícias, tirinhas, blogs e artigos.</p>

<p>As tirinhas também viviam uma época de ouro. Irmãos Brain, Nerdson, Nebulosa’s Nerd Bar e tantas outras faziam parte da rotina de muita gente.</p>

<p>O YouTube também era diferente. Quase não existia clickbait. Quem dedicava tempo para gravar e publicar um vídeo normalmente fazia isso porque gostava do assunto, não porque queria transformar aquilo em sua principal fonte de renda ou em uma renda extra.
Talvez seja só nostalgia. Ou talvez a internet realmente tenha mudado mais do que a gente percebe.</p>

<p>Fóruns de internet também eram muito populares. Eu frequentava bastante o unidev.com.br e o pdj.com.br. Fiz <em>muitos</em> amigos por lá, amizades que mantenho até hoje, mesmo depois de tantas décadas.</p>

<p>Vou citar apenas os nicks da época: <em>tiozorak</em>, <em>tiotoddy</em>, <em>vertexportus</em>, <em>crocidb</em>, <em>vinigodoy</em>, <em>n2liquid</em>, <em>killguns</em>, <em>alext</em>, <em>lord eternal</em>, <em>zizaco</em>, <em>billguedes</em> entre outros. Se esqueci de alguém, me perdoe!</p>

<p>E eu, o <em>skhaz</em>!</p>

<p>Acho que, por ser millennial e a maioria dos meus amigos também ser, éramos muito engajados. Gostávamos de dar pitaco em tudo, trocar ideias e fofocar. Sem contar o ócio. Não havia séries para maratonar, nem muito conteúdo no YouTube, etc. :-)</p>

<p>Além de participar de fóruns, eu também tentei manter um blog. Fiz alguns amigos na blogosfera e, recentemente, “restaurei” o <strong>skhaz.com</strong> em <a href="https://skhaz.github.io/skhaz.com">skhaz.github.io/skhaz.com</a>.</p>

<p>E por falar em restauração e velharias, também restaurei algumas zines publicadas entre ~2000 e ~2010. Você pode conferi-las em: <a href="https://skhaz.github.io/zines">skhaz.github.io/zines</a>.</p>

<p>E os e-mails? Ah, os e-mails… Que saudade de receber um e-mail escrito por um humano.
Eu os usava para tudo: enviar arquivos, organizar eventos, trocar ideias ou simplesmente participar de grupos, como o Google Groups. Foi lá que também fiz vários amigos, participei de eventos e aprendi muito.</p>

<p>Nas lista de <a href="https://groups.google.com/g/ccppbrasil">C e C++ Brasil</a>, fiz amigos como Alan Silva (esse cara tem um excelente gosto musical \m/ e fazemos aniversário no mesmo dia), Rodrigo Madera, Rodrigo Strauss, Wanderlei Caloni, Mauro Persano, Wander Costa, Aleksey Covacevice (o passatempo desse cara é encontrar bugs nos compiladores), Jan Palach e Vinicius Jarina.</p>

<p>E não sou apenas eu que estou falando isso. Coincidentemente, o Caloni, que, além de ser um amigo das baixarias, foi uma das pessoas que me influenciaram a pôr a cara a tapa na internet, publicou algo semelhante.</p>

<p><a href="https://caloni.com.br/blog/breve-nota-sobre-o-futuro-do-blogue.html">Blogue do Caloni: Breve nota sobre o futuro do blogue</a></p>

<blockquote>
  <p>Os próximos que eu arriscar escrever caminharão menos em construção e passagem de conhecimento e mais em relatos de experiência.</p>
</blockquote>

<p>É isso. De besta para besta.</p>]]></content><author><name>Rodrigo Delduca</name><email>rodrigo@delduca.org</email></author><summary type="html"><![CDATA[“A nostalgia é um sentimento permanente. Em outras palavras, sentimos nostalgia mesmo vivendo em nosso próprio país, ao lado das pessoas que mais amamos. Apesar de um lar feliz e de uma família feliz, um homem pode sofrer de nostalgia simplesmente porque sente que sua alma está sendo limitada, que ela não se desenvolve da maneira como deseja. A nostalgia é o sentimento de impotência diante do mundo, a dor de não conseguir transmitir sua espiritualidade aos outros.” Andrei Tarkóvski]]></summary></entry><entry><title type="html">Hiding information inside images</title><link href="https://nullonerror.org/2026/02/13/hiding-information-inside-images/" rel="alternate" type="text/html" title="Hiding information inside images" /><published>2026-02-13T00:00:00+00:00</published><updated>2026-02-13T00:00:00+00:00</updated><id>https://nullonerror.org/2026/02/13/hiding-information-inside-images</id><content type="html" xml:base="https://nullonerror.org/2026/02/13/hiding-information-inside-images/"><![CDATA[<p>As a child, there was a game called <a href="http://educador.brasilescola.com/estrategias-ensino/mensagem-secreta.htm">secret message</a> which consisted of writing something on a sheet of paper with lemon juice and handing it to a friend who knew that by holding a candle underneath, a message would be revealed — without any of the intermediaries ever knowing.</p>

<p>We can say that this game was a form of <strong>steganography</strong></p>

<blockquote>
  <p>“Steganography (from the Greek ‘hidden writing’) is the study and use of techniques to conceal the existence of a message within another, a form of security through obscurity. In other words, steganography is the particular branch of cryptology that consists of disguising one piece of writing within another in order to mask its true meaning.” - <a href="http://en.wikipedia.org/wiki/Steganography">Wikipedia</a></p>
</blockquote>

<p>Today I’m going to introduce another type of steganography — one that uses images to hide text inside pixels!</p>

<p>An image is composed of pixels, and each pixel represents the smallest possible color point using N bits. The more bits per color, the more colors a pixel can represent.</p>

<p>In my example I will focus only on <em>32-bit</em> images (<em>RGBA</em> or <em>ARGB</em>) where <em>8 bits</em> are used per color component. Obviously the technique is easily adaptable to other formats.</p>

<p><img src="/public/2015-04-05-escondendo-informacoes-dentro-de-imagens/rgba.png" alt="image" title="https://commons.wikimedia.org/wiki/File:HexRGBAbits.png" /></p>

<p>What happens if I change a single bit of the red component of every pixel? Even with the original image side by side, we would be unable to notice the difference — only a <em>hashing</em> algorithm like SHA1 could tell the files apart.</p>

<p>Therefore, I can use this same trick of altering pixels to hide a message inside the image, and only someone who knows the implementation will know how to extract the message.</p>

<p>But to do this we need to separate each bit of the message and change only one or two bits of each color component. It sounds like very little space, but if we do the math, an 800×600 image — which is considered low resolution today — can hold</p>

<blockquote>
  <p>800 width _ 600 height = 480000 pixels<br />
480000 pixels _ 4 components = 1920000 bits<br />
1920000 bits / 8 = 240000 bytes</p>
</blockquote>

<p>In other words, we can fit a message of up to ~30 KB in an 800×600 image :)</p>

<p>Remember, we are talking about pixels, not file size — that depends on the chosen format. And speaking of format, this only works with <a href="http://en.wikipedia.org/wiki/Lossless_compression#Graphics">lossless</a> formats.</p>

<h2 id="implementation">Implementation</h2>

<p>To achieve our goal we need to master the <strong>ancient art of bit twiddling</strong>. There is an excellent resource on the subject called: <a href="https://graphics.stanford.edu/~seander/bithacks.html">Bit Twiddling Hacks</a></p>

<p>Writing a hidden message:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">bool</span> <span class="nf">write</span><span class="p">(</span><span class="k">const</span> <span class="n">QString</span><span class="o">&amp;</span> <span class="n">in</span><span class="p">,</span> <span class="k">const</span> <span class="n">QString</span><span class="o">&amp;</span> <span class="n">out</span><span class="p">,</span> <span class="k">const</span> <span class="n">QString</span><span class="o">&amp;</span> <span class="n">text</span><span class="p">){</span>
  <span class="n">QImage</span> <span class="n">image</span><span class="p">;</span>
  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">image</span><span class="p">.</span><span class="n">load</span><span class="p">(</span><span class="n">in</span><span class="p">))</span>
      <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>

  <span class="n">QImage</span> <span class="n">result</span> <span class="o">=</span> <span class="n">image</span><span class="p">.</span><span class="n">copy</span><span class="p">();</span>
  <span class="kt">int</span> <span class="n">size</span> <span class="o">=</span> <span class="n">text</span><span class="p">.</span><span class="n">size</span><span class="p">();</span>

  <span class="n">QByteArray</span> <span class="n">bytes</span><span class="p">;</span>
  <span class="n">bytes</span><span class="p">.</span><span class="n">reserve</span><span class="p">(</span><span class="n">size</span> <span class="o">+</span> <span class="n">headerSize</span><span class="p">);</span>
  <span class="n">bytes</span> <span class="o">+=</span> <span class="n">QString</span><span class="p">(</span><span class="s">"%1"</span><span class="p">).</span><span class="n">arg</span><span class="p">(</span><span class="n">size</span><span class="p">,</span> <span class="n">headerSize</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="n">QChar</span><span class="p">(</span><span class="sc">'0'</span><span class="p">));</span>
  <span class="n">bytes</span> <span class="o">+=</span> <span class="n">text</span><span class="p">.</span><span class="n">toLocal8Bit</span><span class="p">();</span>

  <span class="n">QBitArray</span> <span class="n">bits</span> <span class="o">=</span> <span class="n">byteArrayToBitarray</span><span class="p">(</span><span class="n">bytes</span><span class="p">);</span>

  <span class="c1">// Iterate over each pixel in the image</span>
  <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">index</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">y</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">y</span> <span class="o">&lt;</span> <span class="n">image</span><span class="p">.</span><span class="n">height</span><span class="p">();</span> <span class="o">++</span><span class="n">y</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">x</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">x</span> <span class="o">&lt;</span> <span class="n">image</span><span class="p">.</span><span class="n">width</span><span class="p">();</span> <span class="o">++</span><span class="n">x</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">if</span> <span class="p">(</span><span class="n">index</span> <span class="o">&gt;=</span> <span class="n">bits</span><span class="p">.</span><span class="n">count</span><span class="p">())</span>
        <span class="k">break</span><span class="p">;</span>

      <span class="c1">// Extract each color component individually</span>
      <span class="n">QRgb</span> <span class="n">pixel</span> <span class="o">=</span> <span class="n">image</span><span class="p">.</span><span class="n">pixel</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">);</span>
      <span class="kt">int</span> <span class="n">red</span>   <span class="o">=</span> <span class="n">qRed</span><span class="p">(</span><span class="n">pixel</span><span class="p">);</span>
      <span class="kt">int</span> <span class="n">green</span> <span class="o">=</span> <span class="n">qGreen</span><span class="p">(</span><span class="n">pixel</span><span class="p">);</span>
      <span class="kt">int</span> <span class="n">blue</span>  <span class="o">=</span> <span class="n">qBlue</span><span class="p">(</span><span class="n">pixel</span><span class="p">);</span>
      <span class="kt">int</span> <span class="n">alpha</span> <span class="o">=</span> <span class="n">qAlpha</span><span class="p">(</span><span class="n">pixel</span><span class="p">);</span>

      <span class="c1">// For each component, take one bit from the message</span>
      <span class="c1">// and turn the last bit of the component on or off</span>
      <span class="c1">// to turn on:  component | (1 &lt;&lt; 0x00)</span>
      <span class="c1">// to turn off: component &amp; ~(1 &lt;&lt; 0x00)</span>
      <span class="n">red</span>   <span class="o">=</span> <span class="n">bits</span><span class="p">[</span><span class="n">index</span> <span class="o">+</span> <span class="mi">0</span><span class="p">]</span> <span class="o">?</span> <span class="n">red</span>   <span class="o">|</span> <span class="p">(</span><span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mh">0x00</span><span class="p">)</span> <span class="o">:</span> <span class="n">red</span>   <span class="o">&amp;</span> <span class="o">~</span><span class="p">(</span><span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mh">0x00</span><span class="p">);</span>
      <span class="n">green</span> <span class="o">=</span> <span class="n">bits</span><span class="p">[</span><span class="n">index</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]</span> <span class="o">?</span> <span class="n">green</span> <span class="o">|</span> <span class="p">(</span><span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mh">0x00</span><span class="p">)</span> <span class="o">:</span> <span class="n">green</span> <span class="o">&amp;</span> <span class="o">~</span><span class="p">(</span><span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mh">0x00</span><span class="p">);</span>
      <span class="n">blue</span>  <span class="o">=</span> <span class="n">bits</span><span class="p">[</span><span class="n">index</span> <span class="o">+</span> <span class="mi">2</span><span class="p">]</span> <span class="o">?</span> <span class="n">blue</span>  <span class="o">|</span> <span class="p">(</span><span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mh">0x00</span><span class="p">)</span> <span class="o">:</span> <span class="n">blue</span>  <span class="o">&amp;</span> <span class="o">~</span><span class="p">(</span><span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mh">0x00</span><span class="p">);</span>
      <span class="n">alpha</span> <span class="o">=</span> <span class="n">bits</span><span class="p">[</span><span class="n">index</span> <span class="o">+</span> <span class="mi">3</span><span class="p">]</span> <span class="o">?</span> <span class="n">alpha</span> <span class="o">|</span> <span class="p">(</span><span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mh">0x00</span><span class="p">)</span> <span class="o">:</span> <span class="n">alpha</span> <span class="o">&amp;</span> <span class="o">~</span><span class="p">(</span><span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mh">0x00</span><span class="p">);</span>

      <span class="c1">// Same thing for the second-to-last bit</span>
      <span class="n">red</span>   <span class="o">=</span> <span class="n">bits</span><span class="p">[</span><span class="n">index</span> <span class="o">+</span> <span class="mi">4</span><span class="p">]</span> <span class="o">?</span> <span class="n">red</span>   <span class="o">|</span> <span class="p">(</span><span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mh">0x01</span><span class="p">)</span> <span class="o">:</span> <span class="n">red</span>   <span class="o">&amp;</span> <span class="o">~</span><span class="p">(</span><span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mh">0x01</span><span class="p">);</span>
      <span class="n">green</span> <span class="o">=</span> <span class="n">bits</span><span class="p">[</span><span class="n">index</span> <span class="o">+</span> <span class="mi">5</span><span class="p">]</span> <span class="o">?</span> <span class="n">green</span> <span class="o">|</span> <span class="p">(</span><span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mh">0x01</span><span class="p">)</span> <span class="o">:</span> <span class="n">green</span> <span class="o">&amp;</span> <span class="o">~</span><span class="p">(</span><span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mh">0x01</span><span class="p">);</span>
      <span class="n">blue</span>  <span class="o">=</span> <span class="n">bits</span><span class="p">[</span><span class="n">index</span> <span class="o">+</span> <span class="mi">6</span><span class="p">]</span> <span class="o">?</span> <span class="n">blue</span>  <span class="o">|</span> <span class="p">(</span><span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mh">0x01</span><span class="p">)</span> <span class="o">:</span> <span class="n">blue</span>  <span class="o">&amp;</span> <span class="o">~</span><span class="p">(</span><span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mh">0x01</span><span class="p">);</span>
      <span class="n">alpha</span> <span class="o">=</span> <span class="n">bits</span><span class="p">[</span><span class="n">index</span> <span class="o">+</span> <span class="mi">7</span><span class="p">]</span> <span class="o">?</span> <span class="n">alpha</span> <span class="o">|</span> <span class="p">(</span><span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mh">0x01</span><span class="p">)</span> <span class="o">:</span> <span class="n">alpha</span> <span class="o">&amp;</span> <span class="o">~</span><span class="p">(</span><span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mh">0x01</span><span class="p">);</span>

      <span class="c1">// Write the pixel back to its original position</span>
      <span class="n">result</span><span class="p">.</span><span class="n">setPixel</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">qRgba</span><span class="p">(</span><span class="n">red</span><span class="p">,</span> <span class="n">green</span><span class="p">,</span> <span class="n">blue</span><span class="p">,</span> <span class="n">alpha</span><span class="p">));</span>

      <span class="n">index</span> <span class="o">+=</span> <span class="mi">8</span><span class="p">;</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="k">return</span> <span class="n">result</span><span class="p">.</span><span class="n">save</span><span class="p">(</span><span class="n">out</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Reading a hidden message from an image:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">QByteArray</span> <span class="nf">read</span><span class="p">(</span><span class="k">const</span> <span class="n">QString</span><span class="o">&amp;</span> <span class="n">filename</span><span class="p">)</span> <span class="p">{</span>
  <span class="n">QBitArray</span> <span class="n">bits</span><span class="p">(</span><span class="mi">8</span><span class="p">);</span>
  <span class="n">QByteArray</span> <span class="n">bytes</span><span class="p">;</span>
  <span class="n">bytes</span><span class="p">.</span><span class="n">reserve</span><span class="p">(</span><span class="n">headerSize</span><span class="p">);</span>
  <span class="kt">int</span> <span class="n">bytesToRead</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

  <span class="n">QImage</span> <span class="n">image</span><span class="p">;</span>
  <span class="k">if</span> <span class="p">(</span><span class="n">image</span><span class="p">.</span><span class="n">load</span><span class="p">(</span><span class="n">filename</span><span class="p">))</span> <span class="p">{</span>
    <span class="c1">// Iterate over each pixel in the image</span>
    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">y</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">y</span> <span class="o">&lt;</span> <span class="n">image</span><span class="p">.</span><span class="n">height</span><span class="p">();</span> <span class="o">++</span><span class="n">y</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">x</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">x</span> <span class="o">&lt;</span> <span class="n">image</span><span class="p">.</span><span class="n">width</span><span class="p">();</span> <span class="o">++</span><span class="n">x</span><span class="p">)</span> <span class="p">{</span>
        <span class="kt">uint32_t</span> <span class="n">index</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
        <span class="n">QRgb</span> <span class="n">pixel</span> <span class="o">=</span> <span class="n">image</span><span class="p">.</span><span class="n">pixel</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">);</span>

        <span class="c1">// For each pixel, extract the last and second-to-last bit</span>
        <span class="c1">// of each color component</span>
        <span class="n">bits</span><span class="p">[</span><span class="n">index</span><span class="o">++</span><span class="p">]</span> <span class="o">=</span> <span class="n">qRed</span><span class="p">(</span><span class="n">pixel</span><span class="p">)</span>   <span class="o">&amp;</span> <span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mh">0x00</span><span class="p">;</span>
        <span class="n">bits</span><span class="p">[</span><span class="n">index</span><span class="o">++</span><span class="p">]</span> <span class="o">=</span> <span class="n">qGreen</span><span class="p">(</span><span class="n">pixel</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mh">0x00</span><span class="p">;</span>
        <span class="n">bits</span><span class="p">[</span><span class="n">index</span><span class="o">++</span><span class="p">]</span> <span class="o">=</span> <span class="n">qBlue</span><span class="p">(</span><span class="n">pixel</span><span class="p">)</span>  <span class="o">&amp;</span> <span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mh">0x00</span><span class="p">;</span>
        <span class="n">bits</span><span class="p">[</span><span class="n">index</span><span class="o">++</span><span class="p">]</span> <span class="o">=</span> <span class="n">qAlpha</span><span class="p">(</span><span class="n">pixel</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mh">0x00</span><span class="p">;</span>

        <span class="n">bits</span><span class="p">[</span><span class="n">index</span><span class="o">++</span><span class="p">]</span> <span class="o">=</span> <span class="n">qRed</span><span class="p">(</span><span class="n">pixel</span><span class="p">)</span>   <span class="o">&amp;</span> <span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mh">0x01</span><span class="p">;</span>
        <span class="n">bits</span><span class="p">[</span><span class="n">index</span><span class="o">++</span><span class="p">]</span> <span class="o">=</span> <span class="n">qGreen</span><span class="p">(</span><span class="n">pixel</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mh">0x01</span><span class="p">;</span>
        <span class="n">bits</span><span class="p">[</span><span class="n">index</span><span class="o">++</span><span class="p">]</span> <span class="o">=</span> <span class="n">qBlue</span><span class="p">(</span><span class="n">pixel</span><span class="p">)</span>  <span class="o">&amp;</span> <span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mh">0x01</span><span class="p">;</span>
        <span class="n">bits</span><span class="p">[</span><span class="n">index</span><span class="o">++</span><span class="p">]</span> <span class="o">=</span> <span class="n">qAlpha</span><span class="p">(</span><span class="n">pixel</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mh">0x01</span><span class="p">;</span>

        <span class="c1">// Convert the 8 bits into 1 byte and append to the bytes list</span>
        <span class="n">bytes</span> <span class="o">+=</span> <span class="n">bitArrayToByteArray</span><span class="p">(</span><span class="n">bits</span><span class="p">);</span>

        <span class="c1">// We need to know how many bytes to read;</span>
        <span class="c1">// for that, a header with this information</span>
        <span class="c1">// is inserted at the very beginning of the write process</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">bytesToRead</span> <span class="o">&amp;&amp;</span> <span class="n">bytes</span><span class="p">.</span><span class="n">size</span><span class="p">()</span> <span class="o">==</span> <span class="n">headerSize</span><span class="p">)</span> <span class="p">{</span>
          <span class="kt">bool</span> <span class="n">ok</span><span class="p">;</span>
          <span class="n">bytesToRead</span> <span class="o">=</span> <span class="n">bytes</span><span class="p">.</span><span class="n">toInt</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ok</span><span class="p">);</span>
          <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">ok</span><span class="p">)</span>
            <span class="k">return</span> <span class="n">bytes</span><span class="p">;</span>

          <span class="n">bytes</span><span class="p">.</span><span class="n">clear</span><span class="p">();</span>
          <span class="n">bytes</span><span class="p">.</span><span class="n">reserve</span><span class="p">(</span><span class="n">bytesToRead</span><span class="p">);</span>
        <span class="p">}</span>

        <span class="c1">// Read everything there was to read — return the bytes</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">bytes</span><span class="p">.</span><span class="n">size</span><span class="p">()</span> <span class="o">==</span> <span class="n">bytesToRead</span><span class="p">)</span> <span class="p">{</span>
          <span class="k">return</span> <span class="n">bytes</span><span class="p">;</span>
        <span class="p">}</span>
      <span class="p">}</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="k">return</span> <span class="n">bytes</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="proof">Proof</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ file gioconda.png
gioconda.png: PNG image data, 404 x 410, 8-bit/color RGBA, non-interlaced
$ ./stenog -i gioconda.png -o gioconda_stenog.png -m "flipping bits whilst updating pixels"
$ file gioconda_stenog.png
gioconda_stenog.png: PNG image data, 404 x 410, 8-bit/color RGBA, non-interlaced
$ ls -lah *.png
-rw-r--r--@ 1 Skhaz  staff   378K Apr  3 17:33 gioconda.png
-rw-r--r--  1 Skhaz  staff   394K Apr  3 17:46 gioconda_stenog.png
$ ./stenog -i gioconda_stenog.png
flipping bits whilst updating pixels
</code></pre></div></div>

<p>The file size changed slightly due to the format used — in this case, PNG.</p>

<p><em>This technique can be used to hide not only text, but also other images, sounds, or any kind of data.</em></p>

<h2 id="talk-is-cheap-show-me-the-code">Talk is cheap. Show me the code</h2>

<p>The source code can be found in this <a href="https://gist.github.com/skhaz/4e83e245f41560634be4">gist</a></p>

<p><img src="/public/2015-04-05-escondendo-informacoes-dentro-de-imagens/gioconda_stenog.png" alt="decipher-me-or-I-shall-devour-you" class="center" /></p>]]></content><author><name>Rodrigo Delduca</name><email>rodrigo@delduca.org</email></author><summary type="html"><![CDATA[As a child, there was a game called secret message which consisted of writing something on a sheet of paper with lemon juice and handing it to a friend who knew that by holding a candle underneath, a message would be revealed — without any of the intermediaries ever knowing.]]></summary></entry><entry><title type="html">Carimbo now have a better stack trace and Sentry integration</title><link href="https://nullonerror.org/2025/09/11/carimbo-now-have-a-better-stack-trace-and-sentry-integration/" rel="alternate" type="text/html" title="Carimbo now have a better stack trace and Sentry integration" /><published>2025-09-11T00:00:00+00:00</published><updated>2025-09-11T00:00:00+00:00</updated><id>https://nullonerror.org/2025/09/11/carimbo-now-have-a-better-stack-trace-and-sentry-integration</id><content type="html" xml:base="https://nullonerror.org/2025/09/11/carimbo-now-have-a-better-stack-trace-and-sentry-integration/"><![CDATA[<p>As I’ve already said countless times, I’m working on my first game for Steam, which you can check out online at <a href="https://reprobate.site/">reprobate.site</a>.</p>

<p>Since it’s a paid game, I need to provide proper bug support. With that in mind, and based on both professional and personal experience, I decided to use <a href="https://sentry.io/">Sentry</a>.</p>

<p>At first, integrating C++ and Lua should have been straightforward, but I ran into some issues when using the Conan package manager.</p>

<p>Initially, the package wasn’t being included in the compiler’s include flags, which led me to open an issue both on <a href="https://conan.io/center">Conan Center</a> and on <a href="https://conan.io/center/recipes/sentry-native">Sentry Native</a>.</p>

<p>After spending a whole day on this, I eventually found out that the fix was actually pretty simple. Now Carimbo has native support for Sentry, both on the web (WebAssembly) and natively (Android, iOS, Linux, Windows, and macOS).</p>

<p>Here’s how I managed to get it working.</p>

<h3 id="cmake--conan">CMake &amp; Conan</h3>

<p>This was certainly my biggest problem. For some reason, even when following Conan’s documentation, I couldn’t get it to include the header path for Sentry Native. In the end, my solution looked like this:</p>

<div class="language-cmake highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">find_package</span><span class="p">(</span>sentry CONFIG QUIET<span class="p">)</span>

...

<span class="nb">if</span><span class="p">(</span>sentry_FOUND<span class="p">)</span>
  <span class="nb">target_link_libraries</span><span class="p">(</span><span class="si">${</span><span class="nv">PROJECT_NAME</span><span class="si">}</span> PRIVATE sentry-native::sentry-native<span class="p">)</span>
  <span class="nb">target_compile_definitions</span><span class="p">(</span><span class="si">${</span><span class="nv">PROJECT_NAME</span><span class="si">}</span> PRIVATE HAVE_SENTRY=1<span class="p">)</span>
<span class="nb">endif</span><span class="p">()</span>
</code></pre></div></div>

<h3 id="web--native-at-same-time">Web &amp; Native at same time</h3>

<p>The native part was pretty easy, but for the web part I had an insight while walking, because I realized I could inject JavaScript using the Emscripten API.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">enginefactory</span><span class="o">&amp;</span> <span class="n">enginefactory</span><span class="o">::</span><span class="n">with_sentry</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&amp;</span> <span class="n">dsn</span><span class="p">)</span> <span class="k">noexcept</span> <span class="p">{</span>
  <span class="k">if</span> <span class="p">(</span><span class="n">dsn</span><span class="p">.</span><span class="n">empty</span><span class="p">())</span> <span class="p">{</span>
    <span class="k">return</span> <span class="o">*</span><span class="k">this</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="cp">#ifdef EMSCRIPTEN
</span>    <span class="k">const</span> <span class="k">auto</span> <span class="n">script</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">format</span><span class="p">(</span>
      <span class="s">R"javascript(
        (function(){{
          const __dsn="{}";
          if (window.Sentry &amp;&amp; window.__sentry_inited__) return;
          const script = document.createElement('script');
          script.src = 'https://cdn.jsdelivr.net/npm/@sentry/browser@latest/build/bundle.min.js';
          script.crossOrigin = 'anonymous';
          script.defer = true;
          script.onload = function(){{
            if (!window.Sentry) return;
            window.Sentry.init({{ dsn: __dsn }});
            window.__sentry_inited__ = true;
          }};
          document.head.appendChild(script);
        }})();
      )javascript"</span><span class="p">,</span>
      <span class="n">dsn</span>
    <span class="p">);</span>

    <span class="n">emscripten_run_script</span><span class="p">(</span><span class="n">script</span><span class="p">.</span><span class="n">c_str</span><span class="p">());</span>
  <span class="cp">#endif
</span>
  <span class="cp">#if defined(HAVE_SENTRY) &amp;&amp; !defined(SANDBOX)
</span>    <span class="k">auto</span> <span class="o">*</span><span class="n">options</span> <span class="o">=</span> <span class="n">sentry_options_new</span><span class="p">();</span>
    <span class="n">sentry_options_set_dsn</span><span class="p">(</span><span class="n">options</span><span class="p">,</span> <span class="n">dsn</span><span class="p">.</span><span class="n">c_str</span><span class="p">());</span>
    <span class="c1">// sentry_options_set_debug(options, 1);</span>
    <span class="c1">// sentry_options_set_logger_level(options, SENTRY_LEVEL_DEBUG);</span>
    <span class="n">sentry_init</span><span class="p">(</span><span class="n">options</span><span class="p">);</span>
  <span class="cp">#endif
</span>
  <span class="k">return</span> <span class="o">*</span><span class="k">this</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="better-lua-stack-traces">Better Lua Stack Traces</h3>

<p>Since the game assets are stored in a compressed file, PhysicsFS provides an API to handle them transparently. It’s great for distributing the game — you only need the cartridge.zip and the executable — and it works even better on the web. The engine must provide a searcher so that Lua can find the game’s other Lua scripts. For this, I use a custom searcher: it first looks for the scripts inside the game package, and if they’re not found, it falls back to the interpreter’s default search to load them from the standard library.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">sol</span><span class="o">::</span><span class="n">object</span> <span class="nf">searcher</span><span class="p">(</span><span class="n">sol</span><span class="o">::</span><span class="n">this_state</span> <span class="n">state</span><span class="p">,</span> <span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&amp;</span> <span class="n">module</span><span class="p">)</span> <span class="p">{</span>
  <span class="n">sol</span><span class="o">::</span><span class="n">state_view</span> <span class="n">lua</span><span class="p">{</span><span class="n">state</span><span class="p">};</span>

  <span class="k">const</span> <span class="k">auto</span> <span class="n">filename</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">format</span><span class="p">(</span><span class="s">"scripts/{}.lua"</span><span class="p">,</span> <span class="n">module</span><span class="p">);</span>
  <span class="k">const</span> <span class="k">auto</span> <span class="n">buffer</span> <span class="o">=</span> <span class="n">storage</span><span class="o">::</span><span class="n">io</span><span class="o">::</span><span class="n">read</span><span class="p">(</span><span class="n">filename</span><span class="p">);</span>
  <span class="n">std</span><span class="o">::</span><span class="n">string_view</span> <span class="n">script</span><span class="p">{</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="k">const</span> <span class="kt">char</span> <span class="o">*&gt;</span><span class="p">(</span><span class="n">buffer</span><span class="p">.</span><span class="n">data</span><span class="p">()),</span> <span class="n">buffer</span><span class="p">.</span><span class="n">size</span><span class="p">()};</span>

  <span class="k">const</span> <span class="k">auto</span> <span class="n">loader</span> <span class="o">=</span> <span class="n">lua</span><span class="p">.</span><span class="n">load</span><span class="p">(</span><span class="n">script</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">format</span><span class="p">(</span><span class="s">"@{}"</span><span class="p">,</span> <span class="n">filename</span><span class="p">));</span>
  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">loader</span><span class="p">.</span><span class="n">valid</span><span class="p">())</span> <span class="p">[[</span><span class="n">unlikely</span><span class="p">]]</span> <span class="p">{</span>
    <span class="n">sol</span><span class="o">::</span><span class="n">error</span> <span class="n">err</span> <span class="o">=</span> <span class="n">loader</span><span class="p">;</span>
    <span class="k">throw</span> <span class="n">std</span><span class="o">::</span><span class="n">runtime_error</span><span class="p">(</span><span class="n">err</span><span class="p">.</span><span class="n">what</span><span class="p">());</span>
  <span class="p">}</span>

  <span class="k">return</span> <span class="n">sol</span><span class="o">::</span><span class="n">make_object</span><span class="p">(</span><span class="n">lua</span><span class="p">,</span> <span class="n">loader</span><span class="p">.</span><span class="n">get</span><span class="o">&lt;</span><span class="n">sol</span><span class="o">::</span><span class="n">protected_function</span><span class="o">&gt;</span><span class="p">());</span>
<span class="p">}</span>

<span class="n">lua</span><span class="p">[</span><span class="s">"searcher"</span><span class="p">]</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">searcher</span><span class="p">;</span>

<span class="k">const</span> <span class="k">auto</span> <span class="n">inject</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">format</span><span class="p">(</span><span class="s">R"lua(
  local list = package.searchers or package.loaders
  table.insert(list, searcher)
)lua"</span><span class="p">);</span>

<span class="n">lua</span><span class="p">.</span><span class="n">script</span><span class="p">(</span><span class="n">inject</span><span class="p">);</span>
</code></pre></div></div>

<p>To improve stack traces, the secret lies in the second parameter of lua.load. You can pass a string starting with <code class="language-plaintext highlighter-rouge">@</code> followed by the file name.</p>

<p>This alone gives you a much richer stack trace.</p>

<h3 id="error-handling">Error handling</h3>

<p>In Carimbo, I have a terminate hook that catches any exception and atexit hooks to always handle cleanup.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[[</span><span class="n">noreturn</span><span class="p">]]</span> <span class="kt">void</span> <span class="nf">fail</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">if</span> <span class="p">(</span><span class="k">const</span> <span class="k">auto</span> <span class="n">ptr</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">current_exception</span><span class="p">())</span> <span class="p">{</span>
    <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">error</span> <span class="o">=</span> <span class="nb">nullptr</span><span class="p">;</span>

    <span class="k">try</span> <span class="p">{</span>
      <span class="n">std</span><span class="o">::</span><span class="n">rethrow_exception</span><span class="p">(</span><span class="n">ptr</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">bad_exception</span><span class="o">&amp;</span><span class="p">)</span> <span class="p">{</span>
    <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">exception</span><span class="o">&amp;</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
      <span class="n">error</span> <span class="o">=</span> <span class="n">e</span><span class="p">.</span><span class="n">what</span><span class="p">();</span>
    <span class="p">}</span> <span class="k">catch</span> <span class="p">(...)</span> <span class="p">{</span>
      <span class="n">error</span> <span class="o">=</span> <span class="s">"Unhandled unknown exception"</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">error</span><span class="p">)</span> <span class="p">{</span>
      <span class="cp">#ifdef HAVE_SENTRY
</span>        <span class="k">const</span> <span class="k">auto</span> <span class="n">exc</span> <span class="o">=</span> <span class="n">sentry_value_new_exception</span><span class="p">(</span><span class="s">"exception"</span><span class="p">,</span> <span class="n">error</span><span class="p">);</span>
        <span class="k">const</span> <span class="k">auto</span> <span class="n">ev</span> <span class="o">=</span> <span class="n">sentry_value_new_event</span><span class="p">();</span>

        <span class="n">sentry_event_add_exception</span><span class="p">(</span><span class="n">ev</span><span class="p">,</span> <span class="n">exc</span><span class="p">);</span>

        <span class="n">sentry_capture_event</span><span class="p">(</span><span class="n">ev</span><span class="p">);</span>
      <span class="cp">#endif
</span>
      <span class="cp">#ifdef HAVE_STACKTRACE
</span>        <span class="n">boost</span><span class="o">::</span><span class="n">stacktrace</span><span class="o">::</span><span class="n">stacktrace</span> <span class="n">st</span><span class="p">;</span>
        <span class="n">std</span><span class="o">::</span><span class="n">println</span><span class="p">(</span><span class="n">stderr</span><span class="p">,</span> <span class="s">"Stack trace:</span><span class="se">\n</span><span class="s">{}</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">boost</span><span class="o">::</span><span class="n">stacktrace</span><span class="o">::</span><span class="n">to_string</span><span class="p">(</span><span class="n">st</span><span class="p">));</span>
      <span class="cp">#endif
</span>
      <span class="n">std</span><span class="o">::</span><span class="n">println</span><span class="p">(</span><span class="n">stderr</span><span class="p">,</span> <span class="s">"{}"</span><span class="p">,</span> <span class="n">error</span><span class="p">);</span>

      <span class="n">SDL_ShowSimpleMessageBox</span><span class="p">(</span><span class="n">SDL_MESSAGEBOX_ERROR</span><span class="p">,</span> <span class="s">"Ink Spill Disaster"</span><span class="p">,</span> <span class="n">error</span><span class="p">,</span> <span class="nb">nullptr</span><span class="p">);</span>

      <span class="cp">#ifdef DEBUG
</span>        <span class="cp">#ifdef _MSC_VER
</span>          <span class="n">__debugbreak</span><span class="p">();</span>
        <span class="cp">#else
</span>          <span class="n">raise</span><span class="p">(</span><span class="n">SIGTRAP</span><span class="p">);</span>
        <span class="cp">#endif
</span>      <span class="cp">#endif
</span>    <span class="p">}</span>
  <span class="p">}</span>

  <span class="n">std</span><span class="o">::</span><span class="n">exit</span><span class="p">(</span><span class="n">EXIT_FAILURE</span><span class="p">);</span>
<span class="p">}</span>

<span class="n">application</span><span class="o">::</span><span class="n">application</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">**</span><span class="n">argv</span><span class="p">)</span> <span class="p">{</span>
  <span class="n">UNUSED</span><span class="p">(</span><span class="n">argc</span><span class="p">);</span>
  <span class="n">UNUSED</span><span class="p">(</span><span class="n">argv</span><span class="p">);</span>

  <span class="n">std</span><span class="o">::</span><span class="n">set_terminate</span><span class="p">(</span><span class="n">fail</span><span class="p">);</span>

  <span class="k">constexpr</span> <span class="k">const</span> <span class="k">auto</span> <span class="n">fn</span> <span class="o">=</span> <span class="p">[](</span><span class="kt">int</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">std</span><span class="o">::</span><span class="n">exit</span><span class="p">(</span><span class="n">EXIT_SUCCESS</span><span class="p">);</span>
  <span class="p">};</span>

  <span class="n">std</span><span class="o">::</span><span class="n">signal</span><span class="p">(</span><span class="n">SIGINT</span><span class="p">,</span> <span class="n">fn</span><span class="p">);</span>
  <span class="n">std</span><span class="o">::</span><span class="n">signal</span><span class="p">(</span><span class="n">SIGTERM</span><span class="p">,</span> <span class="n">fn</span><span class="p">);</span>

  <span class="cp">#ifdef HAVE_SENTRY
</span>    <span class="n">std</span><span class="o">::</span><span class="n">atexit</span><span class="p">([]</span> <span class="p">{</span> <span class="n">sentry_close</span><span class="p">();</span> <span class="p">});</span>
  <span class="cp">#endif
</span>
  <span class="n">std</span><span class="o">::</span><span class="n">atexit</span><span class="p">([]</span> <span class="p">{</span> <span class="n">PHYSFS_deinit</span><span class="p">();</span> <span class="p">});</span>
  <span class="n">std</span><span class="o">::</span><span class="n">atexit</span><span class="p">([]</span> <span class="p">{</span> <span class="n">SDL_Quit</span><span class="p">();</span> <span class="p">});</span>

  <span class="n">SDL_Init</span><span class="p">(</span><span class="n">SDL_INIT_GAMEPAD</span> <span class="o">|</span> <span class="n">SDL_INIT_VIDEO</span><span class="p">);</span>
  <span class="n">PHYSFS_init</span><span class="p">(</span><span class="n">argv</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span>
  <span class="cp">#ifdef HAVE_STEAM
</span>    <span class="n">SteamAPI_InitSafe</span><span class="p">();</span>
  <span class="cp">#endif
</span><span class="p">}</span>

<span class="kt">int32_t</span> <span class="n">application</span><span class="o">::</span><span class="n">run</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">static_assert</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">endian</span><span class="o">::</span><span class="n">native</span> <span class="o">==</span> <span class="n">std</span><span class="o">::</span><span class="n">endian</span><span class="o">::</span><span class="n">little</span><span class="p">);</span>

  <span class="k">const</span> <span class="k">auto</span><span class="o">*</span> <span class="n">p</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">getenv</span><span class="p">(</span><span class="s">"CARTRIDGE"</span><span class="p">);</span>

  <span class="n">storage</span><span class="o">::</span><span class="n">filesystem</span><span class="o">::</span><span class="n">mount</span><span class="p">(</span><span class="n">p</span> <span class="o">?</span> <span class="n">p</span> <span class="o">:</span> <span class="s">"cartridge.zip"</span><span class="p">,</span> <span class="s">"/"</span><span class="p">);</span>

  <span class="k">auto</span> <span class="n">se</span> <span class="o">=</span> <span class="n">scriptengine</span><span class="p">();</span>
  <span class="n">se</span><span class="p">.</span><span class="n">run</span><span class="p">();</span>

  <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="conclusion">Conclusion</h3>

<p>This way, I can provide Sentry support in a practically universal and abstract manner for the engine’s user.</p>

<p>You can find more details about the engine and its implementation in the official repository: <a href="https://github.com/willtobyte/carimbo">github.com/willtobyte/carimbo</a>.</p>]]></content><author><name>Rodrigo Delduca</name><email>rodrigo@delduca.org</email></author><summary type="html"><![CDATA[As I’ve already said countless times, I’m working on my first game for Steam, which you can check out online at reprobate.site.]]></summary></entry><entry><title type="html">Replacing Lua’s math.random module with the Xorshift algorithm</title><link href="https://nullonerror.org/2025/08/02/replacing-lua-s-math-random-module-with-the-xorshift-algorithm/" rel="alternate" type="text/html" title="Replacing Lua’s math.random module with the Xorshift algorithm" /><published>2025-08-02T00:00:00+00:00</published><updated>2025-08-02T00:00:00+00:00</updated><id>https://nullonerror.org/2025/08/02/replacing-lua-s-math-random-module-with-the-xorshift-algorithm</id><content type="html" xml:base="https://nullonerror.org/2025/08/02/replacing-lua-s-math-random-module-with-the-xorshift-algorithm/"><![CDATA[<p>I recently discovered that <em>V8</em>, the engine behind <em>Node.js</em> and <em>Chrome</em>, has been using <code class="language-plaintext highlighter-rouge">xorshift128+</code> since <strong><em>2014</em></strong>. Out of curiosity, I checked what the <em>Lua VM</em> uses, and to my surprise, it relies on the standard C library, which is extremely slow.</p>

<p>You can find more details here: <a href="https://github.com/v8/v8/blob/main/src/numbers/math-random.cc">v8/src/numbers/math-random.cc at main · v8/v8</a></p>

<p>I’m currently working on a <a href="https://reprobate.site/">game</a> that uses random in the main loop, which is quite slow. So to work around that, I implemented a pseudo-random function.</p>

<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">local</span> <span class="n">seed</span> <span class="o">=</span> <span class="nb">os.time</span><span class="p">()</span>
<span class="kd">local</span> <span class="k">function</span> <span class="nf">random</span><span class="p">()</span>
  <span class="n">seed</span> <span class="o">=</span> <span class="p">(</span><span class="mi">1103515245</span> <span class="o">*</span> <span class="n">seed</span> <span class="o">+</span> <span class="mi">12345</span><span class="p">)</span> <span class="o">%</span> <span class="mi">2147483648</span>
  <span class="k">return</span> <span class="n">seed</span>
<span class="k">end</span>
</code></pre></div></div>

<p>This is a <em>Linear Congruential Generator (LCG)</em> — one of the oldest and simplest methods for generating pseudorandom numbers.
For the noise effect, I end up calling random up to 6 times per loop, which is extremely costly, and not even the C++ implementation of <code class="language-plaintext highlighter-rouge">xorshift128+</code> below could save me.</p>

<p>Anyway, I ended up replacing Lua’s implementation with the one below, because optimizations are always welcome — especially when the gain in randomness is significantly higher.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="n">std</span><span class="o">::</span><span class="n">array</span><span class="o">&lt;</span><span class="kt">uint64_t</span><span class="p">,</span> <span class="mi">2</span><span class="o">&gt;</span> <span class="n">prng_state</span><span class="p">;</span>

<span class="kt">void</span> <span class="nf">seed</span><span class="p">(</span><span class="kt">uint64_t</span> <span class="n">value</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">constexpr</span> <span class="kt">uint64_t</span> <span class="n">mix</span> <span class="o">=</span> <span class="mh">0xdeadbeefcafebabeULL</span><span class="p">;</span>
  <span class="k">if</span> <span class="p">(</span><span class="n">value</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="n">value</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
  <span class="n">prng_state</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="n">value</span><span class="p">;</span>
  <span class="n">prng_state</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="n">value</span> <span class="o">^</span> <span class="n">mix</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">uint64_t</span> <span class="n">xorshift128plus</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">const</span> <span class="k">auto</span> <span class="n">s1</span> <span class="o">=</span> <span class="n">prng_state</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
  <span class="k">const</span> <span class="k">auto</span> <span class="n">s0</span> <span class="o">=</span> <span class="n">prng_state</span><span class="p">[</span><span class="mi">1</span><span class="p">];</span>
  <span class="k">const</span> <span class="k">auto</span> <span class="n">result</span> <span class="o">=</span> <span class="n">s0</span> <span class="o">+</span> <span class="n">s1</span><span class="p">;</span>

  <span class="n">prng_state</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="n">s0</span><span class="p">;</span>
  <span class="n">prng_state</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="n">s1</span> <span class="o">^</span> <span class="p">(</span><span class="n">s1</span> <span class="o">&lt;&lt;</span> <span class="mi">23</span><span class="p">))</span> <span class="o">^</span> <span class="n">s0</span> <span class="o">^</span> <span class="p">((</span><span class="n">s1</span> <span class="o">^</span> <span class="p">(</span><span class="n">s1</span> <span class="o">&lt;&lt;</span> <span class="mi">23</span><span class="p">))</span> <span class="o">&gt;&gt;</span> <span class="mi">18</span><span class="p">)</span> <span class="o">^</span> <span class="p">(</span><span class="n">s0</span> <span class="o">&gt;&gt;</span> <span class="mi">5</span><span class="p">);</span>

  <span class="k">return</span> <span class="n">result</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">double</span> <span class="n">xorshift_random_double</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">static</span> <span class="k">constexpr</span> <span class="k">const</span> <span class="k">auto</span> <span class="n">inv_max</span> <span class="o">=</span> <span class="mf">1.0</span> <span class="o">/</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">double</span><span class="o">&gt;</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">numeric_limits</span><span class="o">&lt;</span><span class="kt">uint64_t</span><span class="o">&gt;::</span><span class="n">max</span><span class="p">());</span>

  <span class="k">return</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">double</span><span class="o">&gt;</span><span class="p">(</span><span class="n">xorshift128plus</span><span class="p">())</span> <span class="o">*</span> <span class="n">inv_max</span><span class="p">;</span>
<span class="p">}</span>

<span class="n">lua_Integer</span> <span class="n">xorshift_random_int</span><span class="p">(</span><span class="k">const</span> <span class="n">lua_Integer</span> <span class="n">low</span><span class="p">,</span> <span class="k">const</span> <span class="n">lua_Integer</span> <span class="n">high</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">const</span> <span class="k">auto</span> <span class="n">ulow</span> <span class="o">=</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">uint64_t</span><span class="o">&gt;</span><span class="p">(</span><span class="n">low</span><span class="p">);</span>
  <span class="k">const</span> <span class="k">auto</span> <span class="n">range</span> <span class="o">=</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">uint64_t</span><span class="o">&gt;</span><span class="p">(</span><span class="n">high</span> <span class="o">-</span> <span class="n">low</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span>
  <span class="k">return</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="n">lua_Integer</span><span class="o">&gt;</span><span class="p">(</span><span class="n">ulow</span> <span class="o">+</span> <span class="p">(</span><span class="n">xorshift128plus</span><span class="p">()</span> <span class="o">%</span> <span class="n">range</span><span class="p">));</span>
<span class="p">}</span>

<span class="k">const</span> <span class="k">auto</span> <span class="n">now</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">chrono</span><span class="o">::</span><span class="n">high_resolution_clock</span><span class="o">::</span><span class="n">now</span><span class="p">().</span><span class="n">time_since_epoch</span><span class="p">().</span><span class="n">count</span><span class="p">();</span>
<span class="n">seed</span><span class="p">(</span><span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">uint64_t</span><span class="o">&gt;</span><span class="p">(</span><span class="n">now</span><span class="p">));</span>

<span class="n">lua</span><span class="p">[</span><span class="s">"math"</span><span class="p">][</span><span class="s">"random"</span><span class="p">]</span> <span class="o">=</span> <span class="n">sol</span><span class="o">::</span><span class="n">overload</span><span class="p">(</span>
  <span class="p">[]()</span> <span class="o">-&gt;</span> <span class="kt">double</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">xorshift_random_double</span><span class="p">();</span>
  <span class="p">},</span>
  <span class="p">[](</span><span class="n">lua_Integer</span> <span class="n">upper</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">lua_Integer</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">xorshift_random_int</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">upper</span><span class="p">);</span>
  <span class="p">},</span>
  <span class="p">[](</span><span class="n">lua_Integer</span> <span class="n">lower</span><span class="p">,</span> <span class="n">lua_Integer</span> <span class="n">upper</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">lua_Integer</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">xorshift_random_int</span><span class="p">(</span><span class="n">lower</span><span class="p">,</span> <span class="n">upper</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">);</span>

<span class="n">lua</span><span class="p">[</span><span class="s">"math"</span><span class="p">][</span><span class="s">"randomseed"</span><span class="p">]</span> <span class="o">=</span> <span class="p">[](</span><span class="n">lua_Integer</span> <span class="n">seed_value</span><span class="p">)</span> <span class="p">{</span>
  <span class="n">seed</span><span class="p">(</span><span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">uint64_t</span><span class="o">&gt;</span><span class="p">(</span><span class="n">seed_value</span><span class="p">));</span>
<span class="p">};</span>
</code></pre></div></div>]]></content><author><name>Rodrigo Delduca</name><email>rodrigo@delduca.org</email></author><summary type="html"><![CDATA[I recently discovered that V8, the engine behind Node.js and Chrome, has been using xorshift128+ since 2014. Out of curiosity, I checked what the Lua VM uses, and to my surprise, it relies on the standard C library, which is extremely slow.]]></summary></entry><entry><title type="html">Poor’s Man Shaders</title><link href="https://nullonerror.org/2025/07/29/poor-s-man-shaders/" rel="alternate" type="text/html" title="Poor’s Man Shaders" /><published>2025-07-29T00:00:00+00:00</published><updated>2025-07-29T00:00:00+00:00</updated><id>https://nullonerror.org/2025/07/29/poor-s-man-shaders</id><content type="html" xml:base="https://nullonerror.org/2025/07/29/poor-s-man-shaders/"><![CDATA[<blockquote>
  <p>Spoiler: it’s not shaders</p>
</blockquote>

<p>I’m waiting for a universal solution that the SDL developers are working on — cross-platform, multi-API shaders, the <a href="https://github.com/libsdl-org/SDL_shadercross">SDL_shadercross</a>. The idea is that you write shaders in a single language, and at runtime, they get compiled for the target GPU. Unfortunately, it’s a large and complex project, and it will take time before it becomes stable.</p>

<p>In the meantime, in my <a href="https://github.com/willtobyte/carimbo">Carimbo</a>  engine, I was wondering if I could implement something similar to shaders — something that would allow Lua code to write arbitrary pixels into a buffer and stream that buffer into a texture.</p>

<p>So I created what I call a canvas, which is basically a texture the same size as the screen, rendered after certain elements.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">canvas</span><span class="o">::</span><span class="n">canvas</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">shared_ptr</span><span class="o">&lt;</span><span class="n">renderer</span><span class="o">&gt;</span> <span class="n">renderer</span><span class="p">)</span>
    <span class="o">:</span> <span class="n">_renderer</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">renderer</span><span class="p">))</span> <span class="p">{</span>
  <span class="kt">int32_t</span> <span class="n">lw</span><span class="p">,</span> <span class="n">lh</span><span class="p">;</span>
  <span class="n">SDL_RendererLogicalPresentation</span> <span class="n">mode</span><span class="p">;</span>
  <span class="n">SDL_GetRenderLogicalPresentation</span><span class="p">(</span><span class="o">*</span><span class="n">_renderer</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">lw</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">lh</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">mode</span><span class="p">);</span>

  <span class="n">float_t</span> <span class="n">sx</span><span class="p">,</span> <span class="n">sy</span><span class="p">;</span>
  <span class="n">SDL_GetRenderScale</span><span class="p">(</span><span class="o">*</span><span class="n">_renderer</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">sx</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">sy</span><span class="p">);</span>

  <span class="k">const</span> <span class="k">auto</span> <span class="n">width</span> <span class="o">=</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">int32_t</span><span class="o">&gt;</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">lround</span><span class="p">(</span><span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span><span class="p">(</span><span class="n">lw</span><span class="p">)</span> <span class="o">/</span> <span class="n">sx</span><span class="p">));</span>
  <span class="k">const</span> <span class="k">auto</span> <span class="n">height</span> <span class="o">=</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">int32_t</span><span class="o">&gt;</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">lround</span><span class="p">(</span><span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span><span class="p">(</span><span class="n">lh</span><span class="p">)</span> <span class="o">/</span> <span class="n">sy</span><span class="p">));</span>

  <span class="n">SDL_Texture</span> <span class="o">*</span><span class="n">texture</span> <span class="o">=</span> <span class="n">SDL_CreateTexture</span><span class="p">(</span><span class="o">*</span><span class="n">_renderer</span><span class="p">,</span> <span class="n">SDL_PIXELFORMAT_ARGB8888</span><span class="p">,</span> <span class="n">SDL_TEXTUREACCESS_STREAMING</span><span class="p">,</span> <span class="n">width</span><span class="p">,</span> <span class="n">height</span><span class="p">);</span>
  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">texture</span><span class="p">)</span> <span class="p">[[</span><span class="n">unlikely</span><span class="p">]]</span> <span class="p">{</span>
    <span class="k">throw</span> <span class="n">std</span><span class="o">::</span><span class="n">runtime_error</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">format</span><span class="p">(</span><span class="s">"[SDL_CreateTexture] {}"</span><span class="p">,</span> <span class="n">SDL_GetError</span><span class="p">()));</span>
  <span class="p">}</span>

  <span class="n">SDL_SetTextureBlendMode</span><span class="p">(</span><span class="n">texture</span><span class="p">,</span> <span class="n">SDL_BLENDMODE_BLEND</span><span class="p">);</span>

  <span class="n">_framebuffer</span><span class="p">.</span><span class="n">reset</span><span class="p">(</span><span class="n">texture</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">canvas</span><span class="o">::</span><span class="n">set_pixels</span><span class="p">(</span><span class="k">const</span> <span class="kt">uint32_t</span><span class="o">*</span> <span class="n">pixels</span><span class="p">)</span> <span class="k">noexcept</span> <span class="p">{</span>
  <span class="k">const</span> <span class="k">auto</span> <span class="n">ptr</span> <span class="o">=</span> <span class="n">_framebuffer</span><span class="p">.</span><span class="n">get</span><span class="p">();</span>
  <span class="k">const</span> <span class="k">auto</span> <span class="n">pitch</span> <span class="o">=</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span><span class="p">(</span><span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">size_t</span><span class="o">&gt;</span><span class="p">(</span><span class="n">ptr</span><span class="o">-&gt;</span><span class="n">w</span><span class="p">)</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">uint32_t</span><span class="p">));</span>

  <span class="n">SDL_UpdateTexture</span><span class="p">(</span><span class="n">ptr</span><span class="p">,</span> <span class="nb">nullptr</span><span class="p">,</span> <span class="n">pixels</span><span class="p">,</span> <span class="n">pitch</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The set_pixel function receives a pointer to a uint32_t buffer that exactly matches the texture size. This pointer is actually a Lua string, which I found to be the most performant way to transfer data between Lua and C++ without relying on preallocated buffers.</p>

<p>This way:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">lua</span><span class="p">.</span><span class="n">new_usertype</span><span class="o">&lt;</span><span class="n">graphics</span><span class="o">::</span><span class="n">canvas</span><span class="o">&gt;</span><span class="p">(</span>
  <span class="s">"Canvas"</span><span class="p">,</span>
  <span class="n">sol</span><span class="o">::</span><span class="n">no_constructor</span><span class="p">,</span>
  <span class="s">"pixels"</span><span class="p">,</span> <span class="n">sol</span><span class="o">::</span><span class="n">property</span><span class="p">(</span>
    <span class="p">[](</span><span class="k">const</span> <span class="n">graphics</span><span class="o">::</span><span class="n">canvas</span><span class="o">&amp;</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">return</span> <span class="nb">nullptr</span><span class="p">;</span>
    <span class="p">},</span>
    <span class="p">[](</span><span class="n">graphics</span><span class="o">::</span><span class="n">canvas</span><span class="o">&amp;</span> <span class="n">canvas</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">data</span><span class="p">)</span> <span class="p">{</span>
      <span class="n">canvas</span><span class="p">.</span><span class="n">set_pixels</span><span class="p">(</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="k">const</span> <span class="kt">uint32_t</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">data</span><span class="p">));</span>
    <span class="p">}</span>
  <span class="p">)</span>
<span class="p">);</span>
</code></pre></div></div>

<p>On Lua side:</p>

<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">function</span> <span class="nf">Effect</span><span class="p">:</span><span class="n">new</span><span class="p">(</span><span class="n">width</span><span class="p">,</span> <span class="n">height</span><span class="p">)</span>
  <span class="kd">local</span> <span class="n">w</span><span class="p">,</span> <span class="n">h</span> <span class="o">=</span> <span class="n">width</span> <span class="ow">or</span> <span class="mi">480</span><span class="p">,</span> <span class="n">height</span> <span class="ow">or</span> <span class="mi">270</span>
  <span class="kd">local</span> <span class="n">canvas</span> <span class="o">=</span> <span class="n">engine</span><span class="p">:</span><span class="n">canvas</span><span class="p">()</span>
  <span class="kd">local</span> <span class="n">self</span> <span class="o">=</span> <span class="nb">setmetatable</span><span class="p">({</span>
    <span class="n">canvas</span> <span class="o">=</span> <span class="n">canvas</span><span class="p">,</span>
    <span class="n">w</span> <span class="o">=</span> <span class="n">w</span><span class="p">,</span>
    <span class="n">h</span> <span class="o">=</span> <span class="n">h</span><span class="p">,</span>
  <span class="p">},</span> <span class="n">Effect</span><span class="p">)</span>

  <span class="k">return</span> <span class="n">self</span>
<span class="k">end</span>

<span class="k">function</span> <span class="nf">Effect</span><span class="p">:</span><span class="n">loop</span><span class="p">()</span>
  <span class="c1">-- Some cool effect...</span>
  <span class="n">self</span><span class="p">.</span><span class="n">canvas</span><span class="p">.</span><span class="n">pixels</span> <span class="o">=</span> <span class="n">rep</span><span class="p">(</span><span class="n">char</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">220</span><span class="p">),</span> <span class="n">self</span><span class="p">.</span><span class="n">w</span> <span class="o">*</span> <span class="n">self</span><span class="p">.</span><span class="n">h</span><span class="p">)</span><span class="err">`</span>
<span class="k">end</span>

</code></pre></div></div>

<p>Some effects I’ve created so far:</p>

<p><a href="https://youtu.be/GUWTWRQuzxw">https://youtu.be/GUWTWRQuzxw</a></p>

<p><a href="https://youtu.be/usJ9QM7V8BI">https://youtu.be/usJ9QM7V8BI</a></p>

<p><a href="https://youtu.be/DUhQmL91cNA">https://youtu.be/DUhQmL91cNA</a></p>]]></content><author><name>Rodrigo Delduca</name><email>rodrigo@delduca.org</email></author><summary type="html"><![CDATA[Spoiler: it’s not shaders]]></summary></entry><entry><title type="html">AI will replace programmers—just not yet, because it still generates very extremely inefficient code.</title><link href="https://nullonerror.org/2025/07/12/ai-will-replace-programmers-just-not-yet-because-it-currently-generates-extremely-inefficient-code/" rel="alternate" type="text/html" title="AI will replace programmers—just not yet, because it still generates very extremely inefficient code." /><published>2025-07-12T00:00:00+00:00</published><updated>2025-07-12T00:00:00+00:00</updated><id>https://nullonerror.org/2025/07/12/ai-will-replace-programmers-just-not-yet-because-it-currently-generates-extremely-inefficient-code</id><content type="html" xml:base="https://nullonerror.org/2025/07/12/ai-will-replace-programmers-just-not-yet-because-it-currently-generates-extremely-inefficient-code/"><![CDATA[<p>I was working on my engine, which includes a sort of canvas where Lua code can generate chunks of pixels and send them in batches for the C++ engine to render.
This worked very well and smoothly at 60 frames per second with no frame drops at low resolutions (240p, which is the screen size of my games).
However, when I happened to try 1080p, the frame rate dropped.</p>

<p>Since I was in a rush and a bit lazy—because I can’t afford to spend too much time on personal projects—I decided to use AI to optimize it, and this was the best solution I could squeeze out.
It went from 40 FPS down to 17, much worse than the initial implementation!</p>

<p>AI Code:</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[](</span><span class="n">graphics</span><span class="o">::</span><span class="n">canvas</span><span class="o">&amp;</span> <span class="n">canvas</span><span class="p">,</span> <span class="n">sol</span><span class="o">::</span><span class="n">table</span> <span class="n">table</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">const</span> <span class="k">auto</span> <span class="n">n</span> <span class="o">=</span> <span class="n">table</span><span class="p">.</span><span class="n">size</span><span class="p">();</span>
  <span class="k">static</span> <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">uint32_t</span><span class="o">&gt;</span> <span class="n">buffer</span><span class="p">;</span>

  <span class="k">if</span> <span class="p">(</span><span class="n">buffer</span><span class="p">.</span><span class="n">size</span><span class="p">()</span> <span class="o">!=</span> <span class="n">n</span><span class="p">)</span> <span class="p">[[</span><span class="n">unlikely</span><span class="p">]]</span> <span class="p">{</span>
    <span class="n">buffer</span><span class="p">.</span><span class="n">resize</span><span class="p">(</span><span class="n">n</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="k">const</span> <span class="k">auto</span> <span class="n">L</span> <span class="o">=</span> <span class="n">table</span><span class="p">.</span><span class="n">lua_state</span><span class="p">();</span>

  <span class="n">table</span><span class="p">.</span><span class="n">push</span><span class="p">();</span>
  <span class="k">const</span> <span class="kt">int</span> <span class="n">table_idx</span> <span class="o">=</span> <span class="n">lua_gettop</span><span class="p">(</span><span class="n">L</span><span class="p">);</span>

  <span class="kt">uint32_t</span><span class="o">*</span> <span class="k">const</span> <span class="n">data</span> <span class="o">=</span> <span class="n">buffer</span><span class="p">.</span><span class="n">data</span><span class="p">();</span>

  <span class="k">constexpr</span> <span class="n">std</span><span class="o">::</span><span class="kt">size_t</span> <span class="n">batch_size</span> <span class="o">=</span> <span class="mi">8</span><span class="p">;</span>
  <span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="kt">size_t</span> <span class="n">full_batches</span> <span class="o">=</span> <span class="n">n</span> <span class="o">/</span> <span class="n">batch_size</span><span class="p">;</span>

  <span class="k">for</span> <span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="kt">size_t</span> <span class="n">batch</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">batch</span> <span class="o">&lt;</span> <span class="n">full_batches</span><span class="p">;</span> <span class="o">++</span><span class="n">batch</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="kt">size_t</span> <span class="n">start_idx</span> <span class="o">=</span> <span class="n">batch</span> <span class="o">*</span> <span class="n">batch_size</span><span class="p">;</span>

    <span class="k">for</span> <span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="kt">size_t</span> <span class="n">j</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">j</span> <span class="o">&lt;</span> <span class="n">batch_size</span><span class="p">;</span> <span class="o">++</span><span class="n">j</span><span class="p">)</span> <span class="p">{</span>
      <span class="n">lua_rawgeti</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="n">table_idx</span><span class="p">,</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span><span class="p">(</span><span class="n">start_idx</span> <span class="o">+</span> <span class="n">j</span> <span class="o">+</span> <span class="mi">1</span><span class="p">));</span>
    <span class="p">}</span>

    <span class="k">for</span> <span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="kt">size_t</span> <span class="n">j</span> <span class="o">=</span> <span class="n">batch_size</span><span class="p">;</span> <span class="n">j</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">;</span> <span class="o">--</span><span class="n">j</span><span class="p">)</span> <span class="p">{</span>
      <span class="n">data</span><span class="p">[</span><span class="n">start_idx</span> <span class="o">+</span> <span class="n">j</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">uint32_t</span><span class="o">&gt;</span><span class="p">(</span><span class="n">lua_tointeger</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">));</span>
      <span class="n">lua_pop</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="kt">size_t</span> <span class="n">remaining_start</span> <span class="o">=</span> <span class="n">full_batches</span> <span class="o">*</span> <span class="n">batch_size</span><span class="p">;</span>
  <span class="k">for</span> <span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="kt">size_t</span> <span class="n">i</span> <span class="o">=</span> <span class="n">remaining_start</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">n</span><span class="p">;</span> <span class="o">++</span><span class="n">i</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">lua_rawgeti</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="n">table_idx</span><span class="p">,</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span><span class="p">(</span><span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">));</span>
    <span class="n">data</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">uint32_t</span><span class="o">&gt;</span><span class="p">(</span><span class="n">lua_tointeger</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">));</span>
    <span class="n">lua_pop</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="n">lua_pop</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
  <span class="n">canvas</span><span class="p">.</span><span class="n">set_pixels</span><span class="p">(</span><span class="n">buffer</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Naturally, the code was not just complex, but also way slower.</p>

<p>That’s when I decided to take my brain off the shelf and came up with this solution:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[](</span><span class="n">graphics</span><span class="o">::</span><span class="n">canvas</span><span class="o">&amp;</span> <span class="n">canvas</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">data</span><span class="p">)</span> <span class="p">{</span>
  <span class="n">canvas</span><span class="p">.</span><span class="n">set_pixels</span><span class="p">(</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="k">const</span> <span class="kt">uint32_t</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">data</span><span class="p">));</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Kabum! Smooth 60 frames per second, even at 8K resolution or higher.</p>]]></content><author><name>Rodrigo Delduca</name><email>rodrigo@delduca.org</email></author><summary type="html"><![CDATA[I was working on my engine, which includes a sort of canvas where Lua code can generate chunks of pixels and send them in batches for the C++ engine to render. This worked very well and smoothly at 60 frames per second with no frame drops at low resolutions (240p, which is the screen size of my games). However, when I happened to try 1080p, the frame rate dropped.]]></summary></entry><entry><title type="html">How to avoid dynamic linking of Steam’s client library using a very old trick</title><link href="https://nullonerror.org/2025/06/03/how-to-avoid-dynamic-linking-of-steam-s-client-library-using-a-very-old-trick/" rel="alternate" type="text/html" title="How to avoid dynamic linking of Steam’s client library using a very old trick" /><published>2025-06-03T00:00:00+00:00</published><updated>2025-06-03T00:00:00+00:00</updated><id>https://nullonerror.org/2025/06/03/how-to-avoid-dynamic-linking-of-steam-s-client-library-using-a-very-old-trick</id><content type="html" xml:base="https://nullonerror.org/2025/06/03/how-to-avoid-dynamic-linking-of-steam-s-client-library-using-a-very-old-trick/"><![CDATA[<p>As you know, this blog is more focused on sharing code snippets than on teaching, so today I’m going to show you something I recently discovered.</p>

<p>If you’ve been following me, you know I’ve been working in my free time on a 2D game engine where creators can build games using only Lua — and I’d say, even fairly complex ones.</p>

<p>Right now, I’m working on a point-and-click game that you can play here: https://bereprobate.com/. It’s built using this same engine, and I’m publishing builds in parallel to Steam and the Web using GitHub Actions.</p>

<p>The thing is, Steam — which is the main target platform for this game — supports achievements, and I want to include them. But to use achievements, you have to link the Steam library to your engine. The problem is, doing that creates a dependency on that library in the binaries, which I don’t want. I also don’t want to maintain a separate build just for that.</p>

<p>Then I thought: “Why not load the Steam library dynamically? Use LoadLibraryA on Windows and dlopen on macOS. (Sorry Linux — it’s Proton-only for now.)”</p>

<p>I tried the experiment below, and it worked. If the DLL/dylib is present, the Steam features work just fine. If not, everything runs normally.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">"steam.hpp"</span><span class="cp">
</span>
<span class="cp">#if defined(_WIN32)
</span>  <span class="cp">#include</span> <span class="cpf">&lt;windows.h&gt;</span><span class="cp">
</span>  <span class="cp">#define DYNLIB_HANDLE HMODULE
</span>  <span class="cp">#define DYNLIB_LOAD(name) LoadLibraryA(name)
</span>  <span class="cp">#define DYNLIB_SYM(lib, name) GetProcAddress(lib, name)
</span>  <span class="cp">#define STEAM_LIB_NAME "steam_api64.dll"
#elif defined(__APPLE__)
</span>  <span class="cp">#include</span> <span class="cpf">&lt;dlfcn.h&gt;</span><span class="cp">
</span>  <span class="cp">#define DYNLIB_HANDLE void*
</span>  <span class="cp">#define DYNLIB_LOAD(name) dlopen(name, RTLD_LAZY)
</span>  <span class="cp">#define DYNLIB_SYM(lib, name) dlsym(lib, name)
</span>  <span class="cp">#define STEAM_LIB_NAME "libsteam_api.dylib"
#endif
</span>
<span class="cp">#ifndef S_CALLTYPE
</span>  <span class="cp">#define S_CALLTYPE __cdecl
#endif
</span>
<span class="cp">#if defined(DYNLIB_LOAD)
</span>
<span class="k">using</span> <span class="n">SteamAPI_InitSafe_t</span>     <span class="o">=</span> <span class="kt">bool</span><span class="p">(</span><span class="n">S_CALLTYPE</span> <span class="o">*</span><span class="p">)();</span>
<span class="k">using</span> <span class="n">SteamAPI_Shutdown_t</span>     <span class="o">=</span> <span class="kt">void</span><span class="p">(</span><span class="n">S_CALLTYPE</span> <span class="o">*</span><span class="p">)();</span>
<span class="k">using</span> <span class="n">SteamAPI_RunCallbacks_t</span> <span class="o">=</span> <span class="kt">void</span><span class="p">(</span><span class="n">S_CALLTYPE</span> <span class="o">*</span><span class="p">)();</span>
<span class="k">using</span> <span class="n">SteamUserStats_t</span>        <span class="o">=</span> <span class="kt">void</span><span class="o">*</span><span class="p">(</span><span class="n">S_CALLTYPE</span> <span class="o">*</span><span class="p">)();</span>
<span class="k">using</span> <span class="n">GetAchievement_t</span>        <span class="o">=</span> <span class="kt">bool</span><span class="p">(</span><span class="n">S_CALLTYPE</span> <span class="o">*</span><span class="p">)(</span><span class="kt">void</span><span class="o">*</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span><span class="p">,</span> <span class="kt">bool</span><span class="o">*</span><span class="p">);</span>
<span class="k">using</span> <span class="n">SetAchievement_t</span>        <span class="o">=</span> <span class="kt">bool</span><span class="p">(</span><span class="n">S_CALLTYPE</span> <span class="o">*</span><span class="p">)(</span><span class="kt">void</span><span class="o">*</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span><span class="p">);</span>
<span class="k">using</span> <span class="n">StoreStats_t</span>            <span class="o">=</span> <span class="kt">bool</span><span class="p">(</span><span class="n">S_CALLTYPE</span> <span class="o">*</span><span class="p">)(</span><span class="kt">void</span><span class="o">*</span><span class="p">);</span>

<span class="k">static</span> <span class="n">DYNLIB_HANDLE</span> <span class="n">hSteamApi</span> <span class="o">=</span> <span class="n">DYNLIB_LOAD</span><span class="p">(</span><span class="n">STEAM_LIB_NAME</span><span class="p">);</span>

<span class="cp">#define LOAD_SYMBOL(name, sym) reinterpret_cast&lt;name&gt;(reinterpret_cast&lt;void*&gt;(DYNLIB_SYM(hSteamApi, sym)))
</span>
<span class="k">static</span> <span class="k">const</span> <span class="k">auto</span> <span class="n">pSteamAPI_InitSafe</span>     <span class="o">=</span> <span class="n">LOAD_SYMBOL</span><span class="p">(</span><span class="n">SteamAPI_InitSafe_t</span><span class="p">,</span> <span class="s">"SteamAPI_InitSafe"</span><span class="p">);</span>
<span class="k">static</span> <span class="k">const</span> <span class="k">auto</span> <span class="n">pSteamAPI_Shutdown</span>     <span class="o">=</span> <span class="n">LOAD_SYMBOL</span><span class="p">(</span><span class="n">SteamAPI_Shutdown_t</span><span class="p">,</span> <span class="s">"SteamAPI_Shutdown"</span><span class="p">);</span>
<span class="k">static</span> <span class="k">const</span> <span class="k">auto</span> <span class="n">pSteamAPI_RunCallbacks</span> <span class="o">=</span> <span class="n">LOAD_SYMBOL</span><span class="p">(</span><span class="n">SteamAPI_RunCallbacks_t</span><span class="p">,</span> <span class="s">"SteamAPI_RunCallbacks"</span><span class="p">);</span>
<span class="k">static</span> <span class="k">const</span> <span class="k">auto</span> <span class="n">pSteamUserStats</span>        <span class="o">=</span> <span class="n">LOAD_SYMBOL</span><span class="p">(</span><span class="n">SteamUserStats_t</span><span class="p">,</span> <span class="s">"SteamAPI_SteamUserStats_v013"</span><span class="p">);</span>
<span class="k">static</span> <span class="k">const</span> <span class="k">auto</span> <span class="n">pGetAchievement</span>        <span class="o">=</span> <span class="n">LOAD_SYMBOL</span><span class="p">(</span><span class="n">GetAchievement_t</span><span class="p">,</span> <span class="s">"SteamAPI_ISteamUserStats_GetAchievement"</span><span class="p">);</span>
<span class="k">static</span> <span class="k">const</span> <span class="k">auto</span> <span class="n">pSetAchievement</span>        <span class="o">=</span> <span class="n">LOAD_SYMBOL</span><span class="p">(</span><span class="n">SetAchievement_t</span><span class="p">,</span> <span class="s">"SteamAPI_ISteamUserStats_SetAchievement"</span><span class="p">);</span>
<span class="k">static</span> <span class="k">const</span> <span class="k">auto</span> <span class="n">pStoreStats</span>            <span class="o">=</span> <span class="n">LOAD_SYMBOL</span><span class="p">(</span><span class="n">StoreStats_t</span><span class="p">,</span> <span class="s">"SteamAPI_ISteamUserStats_StoreStats"</span><span class="p">);</span>

<span class="kt">bool</span> <span class="nf">SteamAPI_InitSafe</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">return</span> <span class="n">pSteamAPI_InitSafe</span> <span class="o">?</span> <span class="n">pSteamAPI_InitSafe</span><span class="p">()</span> <span class="o">:</span> <span class="nb">false</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">SteamAPI_Shutdown</span><span class="p">()</span> <span class="p">{</span>
  <span class="n">pSteamAPI_Shutdown</span> <span class="o">?</span> <span class="n">pSteamAPI_Shutdown</span><span class="p">()</span> <span class="o">:</span> <span class="kt">void</span><span class="p">();</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">SteamAPI_RunCallbacks</span><span class="p">()</span> <span class="p">{</span>
  <span class="n">pSteamAPI_RunCallbacks</span> <span class="o">?</span> <span class="n">pSteamAPI_RunCallbacks</span><span class="p">()</span> <span class="o">:</span> <span class="kt">void</span><span class="p">();</span>
<span class="p">}</span>

<span class="kt">void</span><span class="o">*</span> <span class="n">SteamUserStats</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">return</span> <span class="n">pSteamUserStats</span> <span class="o">?</span> <span class="n">pSteamUserStats</span><span class="p">()</span> <span class="o">:</span> <span class="nb">nullptr</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">bool</span> <span class="n">GetAchievement</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">name</span><span class="p">)</span> <span class="p">{</span>
  <span class="kt">bool</span> <span class="n">achieved</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
  <span class="k">return</span> <span class="n">pGetAchievement</span> <span class="o">?</span> <span class="p">(</span><span class="n">pGetAchievement</span><span class="p">(</span><span class="n">SteamUserStats</span><span class="p">(),</span> <span class="n">name</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">achieved</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="n">achieved</span><span class="p">)</span> <span class="o">:</span> <span class="nb">false</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">bool</span> <span class="n">SetAchievement</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">name</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">return</span> <span class="n">pSetAchievement</span> <span class="o">?</span> <span class="n">pSetAchievement</span><span class="p">(</span><span class="n">SteamUserStats</span><span class="p">(),</span> <span class="n">name</span><span class="p">)</span> <span class="o">:</span> <span class="nb">false</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">bool</span> <span class="n">StoreStats</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">return</span> <span class="n">pStoreStats</span> <span class="o">?</span> <span class="n">pStoreStats</span><span class="p">(</span><span class="n">SteamUserStats</span><span class="p">())</span> <span class="o">:</span> <span class="nb">false</span><span class="p">;</span>
<span class="p">}</span>

<span class="cp">#else
</span>
<span class="kt">bool</span> <span class="n">SteamAPI_InitSafe</span><span class="p">()</span>            <span class="p">{</span> <span class="k">return</span> <span class="nb">false</span><span class="p">;</span> <span class="p">}</span>
<span class="kt">void</span> <span class="n">SteamAPI_Shutdown</span><span class="p">()</span>           <span class="p">{}</span>
<span class="kt">void</span> <span class="n">SteamAPI_RunCallbacks</span><span class="p">()</span>       <span class="p">{}</span>
<span class="kt">void</span><span class="o">*</span> <span class="n">SteamUserStats</span><span class="p">()</span>             <span class="p">{</span> <span class="k">return</span> <span class="nb">nullptr</span><span class="p">;</span> <span class="p">}</span>
<span class="kt">bool</span> <span class="n">GetAchievement</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span><span class="o">*</span><span class="p">)</span>   <span class="p">{</span> <span class="k">return</span> <span class="nb">false</span><span class="p">;</span> <span class="p">}</span>
<span class="kt">bool</span> <span class="n">SetAchievement</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span><span class="o">*</span><span class="p">)</span>   <span class="p">{</span> <span class="k">return</span> <span class="nb">false</span><span class="p">;</span> <span class="p">}</span>
<span class="kt">bool</span> <span class="n">StoreStats</span><span class="p">()</span>                  <span class="p">{</span> <span class="k">return</span> <span class="nb">false</span><span class="p">;</span> <span class="p">}</span>

<span class="cp">#endif
</span></code></pre></div></div>

<p>Achivement class</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="n">achievement</span><span class="o">::</span><span class="n">unlock</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">id</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">SteamUserStats</span><span class="p">())</span> <span class="p">{</span>
    <span class="k">return</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="k">const</span> <span class="k">auto</span><span class="o">*</span> <span class="n">ptr</span> <span class="o">=</span> <span class="n">id</span><span class="p">.</span><span class="n">c_str</span><span class="p">();</span>

  <span class="k">if</span> <span class="p">(</span><span class="n">GetAchievement</span><span class="p">(</span><span class="n">ptr</span><span class="p">))</span> <span class="p">{</span>
    <span class="k">return</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="n">SetAchievement</span><span class="p">(</span><span class="n">ptr</span><span class="p">);</span>
  <span class="n">StoreStats</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Binding</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">steam</span><span class="o">::</span><span class="n">achievement</span> <span class="n">achievement</span><span class="p">;</span>

<span class="n">lua</span><span class="p">.</span><span class="n">new_usertype</span><span class="o">&lt;</span><span class="n">steam</span><span class="o">::</span><span class="n">achievement</span><span class="o">&gt;</span><span class="p">(</span>
  <span class="s">"Achievement"</span><span class="p">,</span>
  <span class="s">"unlock"</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">steam</span><span class="o">::</span><span class="n">achievement</span><span class="o">::</span><span class="n">unlock</span>
<span class="p">);</span>

<span class="n">lua</span><span class="p">[</span><span class="s">"achievement"</span><span class="p">]</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">achievement</span><span class="p">;</span>
</code></pre></div></div>

<p>Usage</p>

<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">achievement</span><span class="p">:</span><span class="n">unlock</span><span class="p">(</span><span class="s2">"NEW_ACHIEVEMENT_1_3"</span><span class="p">)</span>
</code></pre></div></div>]]></content><author><name>Rodrigo Delduca</name><email>rodrigo@delduca.org</email></author><summary type="html"><![CDATA[As you know, this blog is more focused on sharing code snippets than on teaching, so today I’m going to show you something I recently discovered.]]></summary></entry><entry><title type="html">I’ve built an IoT device to let my family know when I’m in a meeting</title><link href="https://nullonerror.org/2025/05/11/i-have-built-an-iot-device-to-let-my-family-know-when-i-am-in-a-meeting/" rel="alternate" type="text/html" title="I’ve built an IoT device to let my family know when I’m in a meeting" /><published>2025-05-11T00:00:00+00:00</published><updated>2025-05-11T00:00:00+00:00</updated><id>https://nullonerror.org/2025/05/11/i-have-built-an-iot-device-to-let-my-family-know-when-i-am-in-a-meeting</id><content type="html" xml:base="https://nullonerror.org/2025/05/11/i-have-built-an-iot-device-to-let-my-family-know-when-i-am-in-a-meeting/"><![CDATA[<h1 id="introducing-the-iot-device-tabajara-im-in-a-meeting">Introducing the IoT device Tabajara: “I’m in a meeting.”</h1>

<p>Do you work from home, and do people in your household always show up at the worst possible moments?</p>

<p>Let me introduce the “I’m in Meeting” IoT device: it lights up at your office door whenever you turn on your webcam.</p>

<p>It consists of an ESP32 with <code class="language-plaintext highlighter-rouge">mDNS</code> connected to Wi-Fi, using the Arduino framework for simplicity. The ESP32 exposes an HTTP server that handles a PATCH request on the /camera endpoint. This endpoint receives a JSON payload with a status of “on” or “off”, and turns the LED panel red or blue accordingly.</p>

<p>For those who don’t know, mDNS (or Bonjour on Apple platforms) is a way to assign an IP address to a .local hostname for the device, so I don’t need to figure out its IP manually—just use the local domain.</p>

<p>Super convenient, right?</p>

<p>On the other side, I have a Python daemon that periodically queries Apple’s API to check if any cameras are in use, and then sends a PATCH request with “on” or “off” to http://esp32.local/camera.</p>

<p>Very simple, but quite useful.</p>

<p>See in action here <a href="https://youtu.be/c-cD_JLuCuQ">https://youtu.be/c-cD_JLuCuQ</a></p>

<p>Source code <a href="https://github.com/skhaz/onair">https://github.com/skhaz/onair</a></p>

<p>See you!</p>]]></content><author><name>Rodrigo Delduca</name><email>rodrigo@delduca.org</email></author><summary type="html"><![CDATA[Introducing the IoT device Tabajara: “I’m in a meeting.”]]></summary></entry></feed>