<?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://hals.app/feed.xml" rel="self" type="application/atom+xml" /><link href="https://hals.app/" rel="alternate" type="text/html" /><updated>2026-02-16T15:07:21+00:00</updated><id>https://hals.app/feed.xml</id><title type="html">hals.app</title><subtitle>Some thoughts while not coding stuff.</subtitle><author><name>Nils Henrik Hals</name></author><entry><title type="html">Require Auth for OpenAPI and Scalar/Swagger in .NET 10 APIs</title><link href="https://hals.app/blog/dotnet-openapi-scalar-security/" rel="alternate" type="text/html" title="Require Auth for OpenAPI and Scalar/Swagger in .NET 10 APIs" /><published>2026-02-16T00:00:00+00:00</published><updated>2026-02-16T00:00:00+00:00</updated><id>https://hals.app/blog/dotnet-openapi-scalar-security</id><content type="html" xml:base="https://hals.app/blog/dotnet-openapi-scalar-security/"><![CDATA[<p>How to secure the OpenApi schema and Scalar browser in a Microsoft Asp Net Core Web Api using Microsoft Identity Web for authentication.</p>

<h2 id="intro">Intro</h2>

<p>In my <a href="/blog/dotnet-openapi-scalar-oauth2/">previous post</a> I showed how to setup OpenApi and Scalar in a Microsoft Asp Net Core Web Api. In this post I will show how to secure the OpenApi schema and Scalar browser using Microsoft Identity Web for authentication.</p>

<p>The OpenApi schema and Scalar browser can contain sensitive information about your API, such as the available endpoints, full documentation, request and response schemas, and authentication requirements.</p>

<p>Microsoft <a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/openapi/aspnetcore-openapi#configure-openapi-document-generation">recommends</a> OpenApi enabled only in development environments.
But securing the OpenApi schema allows you to safely expose these resources in production environments as well, without risking unintended information disclosure to unauthorized users.</p>

<h2 id="prerequisites">Prerequisites</h2>

<p>A dotnet Asp Net Core Web Api running .NET 10.</p>

<p>Existing authorization setup with OAuth2 (I’m using <code class="language-plaintext highlighter-rouge">Microsoft.Identity.Web</code> in this guide)</p>

<p>Install these nuget packages:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">Scalar.AspNetCore</code> (I used <code class="language-plaintext highlighter-rouge">v2.12.41</code>) (Note, alternatives like <code class="language-plaintext highlighter-rouge">swagger-ui</code> may also work.)</li>
  <li><code class="language-plaintext highlighter-rouge">Microsoft.AspNetCore.OpenApi</code> (I used <code class="language-plaintext highlighter-rouge">v10.0.3</code>)</li>
</ul>

<h2 id="setting-up-openapi-and-scalar">Setting up OpenApi and Scalar</h2>

<p>See the guide in my previous post: <a href="/blog/dotnet-openapi-scalar-oauth2/">Microsoft OpenApi Setup with Scalar</a></p>

<h2 id="authentication-configuration">Authentication configuration</h2>

<p>Assuming your website has setup authentication with Microsoft Identity Web, your setup looks something like this:</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">builder</span><span class="p">.</span><span class="n">Services</span><span class="p">.</span><span class="nf">AddMicrosoftIdentityWebApiAuthentication</span><span class="p">(</span><span class="n">builder</span><span class="p">.</span><span class="n">Configuration</span><span class="p">)</span>
    <span class="p">.</span><span class="nf">EnableTokenAcquisitionToCallDownstreamApi</span><span class="p">()</span>
    <span class="p">.</span><span class="nf">AddDownstreamApi</span><span class="p">(</span>
        <span class="n">DownstreamApiService</span><span class="p">.</span><span class="n">DownstreamApiServiceName</span><span class="p">,</span>
        <span class="n">builder</span><span class="p">.</span><span class="n">Configuration</span><span class="p">.</span><span class="nf">GetSection</span><span class="p">(</span>
            <span class="s">$"DownstreamApis:</span><span class="p">{</span><span class="n">DownstreamApiConfig</span><span class="p">.</span><span class="n">DownstreamApiName</span><span class="p">}</span><span class="s">"</span>
        <span class="p">)</span>
    <span class="p">)</span>
    <span class="p">.</span><span class="nf">AddInMemoryTokenCaches</span><span class="p">();</span>

<span class="c1">// Snip ...</span>

<span class="kt">var</span> <span class="n">app</span> <span class="p">=</span> <span class="n">builder</span><span class="p">.</span><span class="nf">Build</span><span class="p">();</span>
<span class="c1">// Snip ...</span>

<span class="n">app</span><span class="p">.</span><span class="nf">UseAuthentication</span><span class="p">();</span>
<span class="n">app</span><span class="p">.</span><span class="nf">UseAuthorization</span><span class="p">();</span>
<span class="c1">// Snip ...</span>

<span class="n">app</span><span class="p">.</span><span class="nf">MapOpenApi</span><span class="p">();</span>
<span class="n">app</span><span class="p">.</span><span class="nf">MapScalarApiReference</span><span class="p">(</span><span class="s">"/scalar"</span><span class="p">,</span> <span class="n">options</span> <span class="p">=&gt;</span> <span class="p">...);</span>
<span class="c1">// Snip ...</span>
</code></pre></div></div>

<h3 id="adding-multiple-authentication-schemes">Adding multiple Authentication Schemes</h3>

<p>As the WebApi authentication setup is designed for APIs, it uses the JwtBearer authentication scheme by default. This is not suitable for authenticating users in the browser, as it does not support interactive login flows. To authenticate users in the browser, we need to use the OpenIdConnect authentication scheme, which is designed for web applications and supports interactive login flows. This is easily added by adding the <code class="language-plaintext highlighter-rouge">AddMicrosoftIdentityWebAppAuthentication</code> method to the services, which adds the OpenIdConnect authentication scheme.</p>

<p>First out:</p>

<p>Add a <code class="language-plaintext highlighter-rouge">callbackPath</code> to your authentication setup.</p>

<pre><code class="language-json5">// in your appsettings.json
"AzureAd": {
  // Snip other AzureAd config
  "CallbackPath": "/signin-oidc",
}
</code></pre>

<p class="notice--info">You may have to add redirect URIs in your Azure AD app registration for the OpenIdConnect authentication to work. The redirect URI should be in the format <code class="language-plaintext highlighter-rouge">https://&lt;your-api-base-url&gt;/signin-oidc</code>.</p>

<p>Then add the <code class="language-plaintext highlighter-rouge">AddMicrosoftIdentityWebAppAuthentication</code> (NOTE: App, not Api) to your services:</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">builder</span><span class="p">.</span><span class="n">Services</span><span class="p">.</span><span class="nf">AddMicrosoftIdentityWebApiAuthentication</span><span class="p">(</span>
    <span class="c1">// Snip (see above)</span>
<span class="p">)</span>

<span class="c1">// Note: IdentityWebApp, not Api! Otherwise identical to the Api setup (You may not require downstream api setup here though, depending on your use-case)</span>
<span class="n">builder</span><span class="p">.</span><span class="n">Services</span><span class="p">.</span><span class="nf">AddMicrosoftIdentityWebAppAuthentication</span><span class="p">(</span><span class="n">builder</span><span class="p">.</span><span class="n">Configuration</span><span class="p">)</span>
    <span class="p">.</span><span class="nf">EnableTokenAcquisitionToCallDownstreamApi</span><span class="p">()</span>
    <span class="p">.</span><span class="nf">AddDownstreamApi</span><span class="p">(</span>
        <span class="n">DownstreamApiService</span><span class="p">.</span><span class="n">DownstreamApiServiceName</span><span class="p">,</span>
        <span class="n">builder</span><span class="p">.</span><span class="n">Configuration</span><span class="p">.</span><span class="nf">GetSection</span><span class="p">(</span>
            <span class="s">$"DownstreamApis:</span><span class="p">{</span><span class="n">DownstreamApiConfig</span><span class="p">.</span><span class="n">DownstreamApiName</span><span class="p">}</span><span class="s">"</span>
        <span class="p">)</span>
    <span class="p">)</span>
    <span class="p">.</span><span class="nf">AddInMemoryTokenCaches</span><span class="p">();</span>
</code></pre></div></div>

<p>We now have two authentication schemes, one for the API and one for the Web App. The Web App scheme is used by Scalar to authenticate users in the browser, and the API scheme is used to authenticate requests to the API.</p>

<p>We want to use the Api authentication for most routes, so we setup a default authentication scheme:</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">builder</span><span class="p">.</span><span class="n">Services</span><span class="p">.</span><span class="nf">AddAuthorization</span><span class="p">(</span><span class="n">options</span> <span class="p">=&gt;</span>
<span class="p">{</span>
    <span class="n">options</span><span class="p">.</span><span class="n">FallbackPolicy</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">AuthorizationPolicyBuilder</span><span class="p">()</span>
        <span class="p">.</span><span class="nf">AddAuthenticationSchemes</span><span class="p">(</span>
            <span class="n">OpenIdConnectDefaults</span><span class="p">.</span><span class="n">AuthenticationScheme</span><span class="p">,</span> <span class="c1">// Optional: Allows us to use the OpenIdConnect cookie if authenticated</span>
            <span class="n">JwtBearerDefaults</span><span class="p">.</span><span class="n">AuthenticationScheme</span> <span class="c1">// This is the default scheme used for API authentication</span>
        <span class="p">)</span>
        <span class="p">.</span><span class="nf">RequireAuthenticatedUser</span><span class="p">()</span>
        <span class="p">.</span><span class="nf">Build</span><span class="p">();</span>
<span class="p">});</span>
</code></pre></div></div>

<p>Now we setup the Scalar Auth Policy: This has the OpenIdConnect scheme as default,
which will cause it to redirect to the Azure AD login page when authentication is required.</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">scalarUiAuthenticationPolicy</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">AuthorizationPolicyBuilder</span><span class="p">()</span>
    <span class="p">.</span><span class="nf">AddAuthenticationSchemes</span><span class="p">(</span>
        <span class="n">JwtBearerDefaults</span><span class="p">.</span><span class="n">AuthenticationScheme</span><span class="p">,</span>
        <span class="n">OpenIdConnectDefaults</span><span class="p">.</span><span class="n">AuthenticationScheme</span> <span class="c1">// Intentionally use OpenIdConnect last as it will redirect to Azure AD login page.</span>
    <span class="p">)</span>
    <span class="p">.</span><span class="nf">AddRequirements</span><span class="p">(</span><span class="k">new</span> <span class="nf">DenyAnonymousAuthorizationRequirement</span><span class="p">())</span>
    <span class="p">.</span><span class="nf">Build</span><span class="p">();</span>
</code></pre></div></div>

<p>Then we have to setup the OpenApi schema to use the Web App authentication scheme, and require the correct scopes.</p>

<p>Add “RequireAuthorization” to the OpenApi endpoint to require authentication
for the OpenApi document itself. This uses the “default” authentication scheme,
which we set to the API scheme above.</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">app</span><span class="p">.</span><span class="nf">MapOpenApi</span><span class="p">().</span><span class="nf">RequireAuthorization</span><span class="p">(</span><span class="n">scalarUiAuthenticationPolicy</span><span class="p">);</span>
</code></pre></div></div>

<p>Finally we add the RequireAuthorization to the Scalar endpoint</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">app</span><span class="p">.</span><span class="nf">MapScalarApiReference</span><span class="p">(</span><span class="n">options</span> <span class="p">=&gt;</span>
    <span class="p">{</span>
        <span class="c1">// Snip Scalar setup (see previous post)</span>
    <span class="p">})</span>
    <span class="p">.</span><span class="nf">RequireAuthorization</span><span class="p">(</span><span class="n">scalarUiAuthenticationPolicy</span><span class="p">);</span>
</code></pre></div></div>

<h2 id="fin">Fin</h2>

<p>Good!</p>

<p>Now you should be able to run the API locally and see that a login is required to access the OpenApi document and the Scalar UI.</p>

<h2 id="end">End</h2>

<p>// Nils Henrik</p>

<p>This post was written by a human.</p>]]></content><author><name>Nils Henrik Hals</name></author><category term="Blog" /><category term="dotnet" /><category term="azure" /><category term="security" /><summary type="html"><![CDATA[How to secure the OpenApi schema and Scalar browser in a Microsoft Asp Net Core Web Api using Microsoft Identity Web for authentication.]]></summary></entry><entry><title type="html">Scroll Faster using CapsLock and Autohotkey</title><link href="https://hals.app/blog/capslock-scroll-faster/" rel="alternate" type="text/html" title="Scroll Faster using CapsLock and Autohotkey" /><published>2025-11-22T00:00:00+00:00</published><updated>2025-11-22T00:00:00+00:00</updated><id>https://hals.app/blog/capslock-scroll-faster</id><content type="html" xml:base="https://hals.app/blog/capslock-scroll-faster/"><![CDATA[<p>I wanted a global faster scrolling option in windows.</p>

<p>In apps like VSCode when you hold ALT the Scroll Wheel on your mouse scrolls about 5 times faster.</p>

<p>I wanted this functionality in every app. Long websites, word documents, pdfs etc.</p>

<p>To achieve this I created this AutoHotKey script.</p>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#Requires AutoHotkey v2.0
</span>
<span class="c">#SingleInstance Force
</span>
<span class="c">; Fast scrolling when Caps Lock is held down
</span>
<span class="err">SetCapsLockState</span> <span class="err">("AlwaysOff")</span> <span class="c">; Disable Standard Caps Lock functionality (Actual caps-lock will not work any more)
</span>
<span class="err">scrollSpeedMultiplier</span> <span class="err">:=</span> <span class="err">5</span> <span class="c">; Scroll speed multiplier
</span>
<span class="c">; Define fast scroll behavior when Caps Lock is held down
</span><span class="err">CapsLock</span> <span class="err">&amp;</span> <span class="err">WheelUp::</span>
<span class="err">{</span>
    <span class="err">Send("{WheelUp</span> <span class="err">"</span> <span class="err">scrollSpeedMultiplier</span> <span class="err">"}")</span>
    <span class="err">return</span>
<span class="err">}</span>

<span class="err">CapsLock</span> <span class="err">&amp;</span> <span class="err">WheelDown::</span>
<span class="err">{</span>
    <span class="err">Send("{WheelDown</span> <span class="err">"</span> <span class="err">scrollSpeedMultiplier</span> <span class="err">"}")</span>
    <span class="err">return</span>
<span class="err">}</span>

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

<p>This script totally disables the capslock, but replaces it to be a modern modifier key.</p>

<p>Save your file as <code class="language-plaintext highlighter-rouge">CapsScrollSpeed.ahk</code> and add it to your startup folder to auto enable the script on every startup. The startup folder can be found by entering entering <code class="language-plaintext highlighter-rouge">shell:startup</code> in the “URL” bar in Explorer.</p>

<h2 id="end">End</h2>

<p>Thats it for this time.</p>

<p>// Nils Henrik</p>

<p>This post was written by a human.</p>]]></content><author><name>Nils Henrik Hals</name></author><category term="Blog" /><category term="shorts" /><summary type="html"><![CDATA[I wanted a global faster scrolling option in windows.]]></summary></entry><entry><title type="html">Microsoft OpenApi OAuth2 Setup with Scalar</title><link href="https://hals.app/blog/dotnet-openapi-scalar-oauth2/" rel="alternate" type="text/html" title="Microsoft OpenApi OAuth2 Setup with Scalar" /><published>2025-11-19T00:00:00+00:00</published><updated>2025-11-19T00:00:00+00:00</updated><id>https://hals.app/blog/dotnet-openapi-scalar-oauth2</id><content type="html" xml:base="https://hals.app/blog/dotnet-openapi-scalar-oauth2/"><![CDATA[<p>A quick guide to setting up OpenApi for OAuth2 (with Azure AD / EntraID) and integrating it with the <a href="https://scalar.com">Scalar</a> openapi browser.</p>

<h2 id="prerequisites">Prerequisites</h2>

<p>A dotnet Web Api running .NET 10.</p>

<p>Existing authorization setup with OAuth2 (I’m using <code class="language-plaintext highlighter-rouge">Microsoft.Identity.Web</code> in this guide)</p>

<p>Install these nuget packages:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">Scalar.AspNetCore</code> (I used <code class="language-plaintext highlighter-rouge">v2.10.3</code>)</li>
  <li><code class="language-plaintext highlighter-rouge">Microsoft.AspNetCore.OpenApi</code> (I used <code class="language-plaintext highlighter-rouge">v10.0.0</code>)</li>
</ul>

<h2 id="setting-up-openapi-and-scalar">Setting up OpenApi and Scalar</h2>

<p>Following the Microsoft docs: <a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/openapi/overview">https://learn.microsoft.com/en-us/aspnet/core/fundamentals/openapi/overview</a> and the Scalar docs:
<a href="https://guides.scalar.com/scalar/scalar-api-references/integrations/net-aspnet-core/integration">https://guides.scalar.com/scalar/scalar-api-references/integrations/net-aspnet-core/integration</a></p>

<p>Add the OpenApi service (this registers the code to generate the OpenApi schema):</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">services</span><span class="p">.</span><span class="nf">AddOpenApi</span><span class="p">();</span>
</code></pre></div></div>

<p>And register routes to serve the generated json:
Also register the Scalar api here.</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="n">app</span><span class="p">.</span><span class="n">Environment</span><span class="p">.</span><span class="nf">IsDevelopment</span><span class="p">())</span>
<span class="p">{</span>
    <span class="n">app</span><span class="p">.</span><span class="nf">MapOpenApi</span><span class="p">();</span> <span class="c1">// Expose the openapi json</span>
    <span class="n">app</span><span class="p">.</span><span class="nf">MapScalarApiReference</span><span class="p">(</span><span class="s">"/scalar"</span><span class="p">);</span> <span class="c1">// Enable Scalar on the "/scalar" route</span>
<span class="p">}</span>
</code></pre></div></div>

<p>You should now be able to start your API and find a OpenApi json at <code class="language-plaintext highlighter-rouge">/openapi/v1.json</code> and to view it visually in the <code class="language-plaintext highlighter-rouge">/scalar</code> route.</p>

<h2 id="configuring-oauth2">Configuring OAuth2</h2>

<p>To configure OAuth2 for your API you need to do two things:</p>

<ol>
  <li>Alter the OpenApi schema with custom changes.</li>
  <li>Setup Scalar to recognize the Authorization scheme, and configure some extra steps.</li>
</ol>

<h3 id="altering-the-open-api">Altering the Open Api</h3>

<p>We need to change the OpenApi initialization and add a <em>Transformer</em></p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">services</span><span class="p">.</span><span class="nf">AddOpenApi</span><span class="p">(</span><span class="n">options</span> <span class="p">=&gt;</span>
<span class="p">{</span>
    <span class="n">options</span><span class="p">.</span><span class="n">AddDocumentTransformer</span><span class="p">&lt;</span><span class="n">CustomOpenApiTransformer</span><span class="p">&gt;();</span>
<span class="p">});</span>
</code></pre></div></div>

<p>The transformer is code to adjust the generated OpenApi schema. A short example of what a class could look like is below.</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">CustomOpenApiTransformer</span><span class="p">(</span><span class="n">IConfiguration</span> <span class="n">configuration</span><span class="p">)</span> <span class="p">:</span> <span class="n">IOpenApiDocumentTransformer</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="n">Task</span> <span class="nf">TransformAsync</span><span class="p">(</span>
        <span class="n">OpenApiDocument</span> <span class="n">document</span><span class="p">,</span>
        <span class="n">OpenApiDocumentTransformerContext</span> <span class="n">context</span><span class="p">,</span>
        <span class="n">CancellationToken</span> <span class="n">cancellationToken</span>
    <span class="p">)</span>
    <span class="p">{</span>
        <span class="n">document</span><span class="p">.</span><span class="n">Info</span> <span class="p">=</span> <span class="k">new</span> <span class="n">OpenApiInfo</span>
        <span class="p">{</span>
            <span class="n">Title</span> <span class="p">=</span> <span class="s">"Echo AyelixProxyApi"</span>
        <span class="p">};</span>

        <span class="c1">// Code from below should be placed here...</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Here we need to add two parts:</p>

<ol>
  <li>A SecuritySchema</li>
  <li>A SecurityRequirement</li>
</ol>

<h4 id="securityschema">SecuritySchema</h4>

<p>The security schema describes a specific security setup.</p>

<p>For a dotnet api using Azure AD and OAuth2 this should be:</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">authority</span> <span class="p">=</span> <span class="n">configuration</span><span class="p">.</span><span class="nf">GetOrThrow</span><span class="p">(</span><span class="s">"AzureAd:Authority"</span><span class="p">);</span> <span class="c1">// Learn more here https://learn.microsoft.com/en-us/entra/identity-platform/msal-client-application-configuration#authority</span>
<span class="kt">var</span> <span class="n">audience</span> <span class="p">=</span> <span class="n">configuration</span><span class="p">.</span><span class="nf">GetOrThrow</span><span class="p">(</span><span class="s">"AzureAd:Audience"</span><span class="p">);</span> <span class="c1">// In this example this is the guid Client Id of the Api</span>
<span class="kt">var</span> <span class="n">schemaKey</span> <span class="p">=</span> <span class="s">"OAuth2"</span><span class="p">;</span> <span class="c1">// This name is used as a key, and could be anything as long as you are consistent.</span>
<span class="kt">var</span> <span class="n">scopes</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Dictionary</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">,</span> <span class="kt">string</span><span class="p">&gt;</span>
<span class="p">{</span>
    <span class="p">{</span>
        <span class="s">$"</span><span class="p">{</span><span class="n">audience</span><span class="p">}</span><span class="s">/.default"</span><span class="p">,</span> <span class="c1">// Actual Scope</span>
        <span class="s">"All scopes"</span> <span class="c1">// Human readable description</span>
    <span class="p">},</span>
<span class="p">};</span>
<span class="kt">var</span> <span class="n">securitySchemes</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Dictionary</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">,</span> <span class="n">IOpenApiSecurityScheme</span><span class="p">&gt;</span>
<span class="p">{</span>
    <span class="p">[</span><span class="n">schemaKey</span><span class="p">]</span> <span class="p">=</span> <span class="k">new</span> <span class="n">OpenApiSecurityScheme</span>
    <span class="p">{</span>
        <span class="n">Type</span> <span class="p">=</span> <span class="n">SecuritySchemeType</span><span class="p">.</span><span class="n">OAuth2</span><span class="p">,</span>
        <span class="n">Scheme</span> <span class="p">=</span> <span class="s">"OAuth2"</span><span class="p">,</span>
        <span class="n">Flows</span> <span class="p">=</span> <span class="k">new</span> <span class="n">OpenApiOAuthFlows</span>
        <span class="p">{</span>
            <span class="n">Implicit</span> <span class="p">=</span> <span class="k">new</span> <span class="n">OpenApiOAuthFlow</span>
            <span class="p">{</span>
                <span class="n">AuthorizationUrl</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Uri</span><span class="p">(</span><span class="s">$"</span><span class="p">{</span><span class="n">authority</span><span class="p">}</span><span class="s">/oauth2/v2.0/authorize"</span><span class="p">),</span>
                <span class="n">TokenUrl</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Uri</span><span class="p">(</span><span class="s">$"</span><span class="p">{</span><span class="n">authority</span><span class="p">}</span><span class="s">/oauth2/v2.0/token"</span><span class="p">),</span>
                <span class="n">RefreshUrl</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Uri</span><span class="p">(</span><span class="s">$"</span><span class="p">{</span><span class="n">authority</span><span class="p">}</span><span class="s">/oauth2/v2.0/token"</span><span class="p">),</span>
                <span class="n">Scopes</span> <span class="p">=</span> <span class="n">scopes</span><span class="p">,</span>
            <span class="p">},</span>
        <span class="p">},</span>
    <span class="p">},</span>
<span class="p">};</span>
<span class="n">document</span><span class="p">.</span><span class="n">Components</span> <span class="p">??=</span> <span class="k">new</span> <span class="nf">OpenApiComponents</span><span class="p">();</span>
<span class="n">document</span><span class="p">.</span><span class="n">Components</span><span class="p">.</span><span class="n">SecuritySchemes</span> <span class="p">=</span> <span class="n">securitySchemes</span><span class="p">;</span>
</code></pre></div></div>

<p>Above we defined a schema, now we have to require this schema for the endpoints.</p>

<h4 id="securityrequirement">SecurityRequirement</h4>

<p>A security requirement can be placed on every endpoint, or on the entire document. For our use-case we just put it on everything, and require the same scope for all endpoints. This could be adjusted according to your api design.</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">securityRequirement</span> <span class="p">=</span> <span class="k">new</span> <span class="n">OpenApiSecurityRequirement</span>
<span class="p">{</span>
    <span class="p">[</span><span class="k">new</span> <span class="nf">OpenApiSecuritySchemeReference</span><span class="p">(</span><span class="n">schemaKey</span><span class="p">,</span> <span class="n">document</span><span class="p">)]</span> <span class="p">=</span> <span class="p">[..</span><span class="n">scopes</span><span class="p">.</span><span class="n">Keys</span><span class="p">],</span>
<span class="p">};</span>

<span class="n">document</span><span class="p">.</span><span class="n">Security</span> <span class="p">=</span> <span class="p">[</span><span class="n">securityRequirement</span><span class="p">];</span>
</code></pre></div></div>

<p>Thats all. Insert this code in the <code class="language-plaintext highlighter-rouge">TransformAsync</code> method.</p>

<p>You now have the setup and your openapi.json file should have all the required mapping.</p>

<h3 id="setup-scalar">Setup Scalar</h3>

<p>Finally we need to configure Scalar with the expected ClientId to use for the Web frontend.</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">app</span><span class="p">.</span><span class="nf">MapScalarApiReference</span><span class="p">(</span><span class="s">"/scalar"</span><span class="p">,</span> <span class="n">options</span> <span class="p">=&gt;</span>
<span class="p">{</span>
    <span class="n">options</span>
        <span class="p">.</span><span class="nf">AddPreferredSecuritySchemes</span><span class="p">(</span><span class="s">"OAuth2"</span><span class="p">)</span> <span class="c1">// This is the schemaKey from above</span>
        <span class="p">.</span><span class="nf">AddImplicitFlow</span><span class="p">(</span>
            <span class="s">"OAuth2"</span><span class="p">,</span> <span class="c1">// Again: schemaKey</span>
            <span class="n">flow</span> <span class="p">=&gt;</span>
            <span class="p">{</span>
                <span class="n">flow</span><span class="p">.</span><span class="n">ClientId</span> <span class="p">=</span> <span class="n">builder</span><span class="p">.</span><span class="n">Configuration</span><span class="p">.</span><span class="nf">GetOrThrow</span><span class="p">(</span><span class="s">"Scalar:OAuthClientId"</span><span class="p">);</span> <span class="c1">// The ClientId to use. Can usually be `AzureAd:ClientId`</span>
                <span class="kt">var</span> <span class="n">audience</span> <span class="p">=</span> <span class="n">builder</span><span class="p">.</span><span class="n">Configuration</span><span class="p">.</span><span class="nf">GetOrThrow</span><span class="p">(</span><span class="s">"AzureAd:Audience"</span><span class="p">);</span>
                <span class="n">flow</span><span class="p">.</span><span class="n">SelectedScopes</span> <span class="p">=</span> <span class="p">[</span><span class="s">$"</span><span class="p">{</span><span class="n">audience</span><span class="p">}</span><span class="s">/.default"</span><span class="p">];</span> <span class="c1">// Same scopes as defined in the OpenApi transformer!</span>
            <span class="p">}</span>
        <span class="p">)</span>
<span class="p">});</span>
</code></pre></div></div>

<p class="notice--info">You may need to add a redirect Uri to your configuration now. By default the redirect Url should be <code class="language-plaintext highlighter-rouge">http://localhost/scalar</code> (Redirect uris to localhost do not have to include the port)</p>

<p>That’s all!</p>

<p>You can now browse and authenticate to your API in a quick little local setup!</p>

<h2 id="end">End</h2>

<p>// Nils Henrik</p>

<p>This post was written by a human.</p>]]></content><author><name>Nils Henrik Hals</name></author><category term="Blog" /><category term="dotnet" /><category term="azure" /><summary type="html"><![CDATA[A quick guide to setting up OpenApi for OAuth2 (with Azure AD / EntraID) and integrating it with the Scalar openapi browser.]]></summary></entry><entry><title type="html">Fixing Crash on Resume from Sleep in Bluefin 42</title><link href="https://hals.app/blog/bluefin-bluetooth/" rel="alternate" type="text/html" title="Fixing Crash on Resume from Sleep in Bluefin 42" /><published>2025-09-28T00:00:00+00:00</published><updated>2025-09-28T00:00:00+00:00</updated><id>https://hals.app/blog/bluefin-bluetooth</id><content type="html" xml:base="https://hals.app/blog/bluefin-bluetooth/"><![CDATA[<p>My Bluefin Linux crashed on resume from Suspend/sleep.</p>

<p>The cause seems to have been the bluetooth adapter, as disabling the bluetooth before sleeping avoided the issue. You should test if disabling bluetooth manually fixes sleep resume for you. If disabling bluetooth actually fixes sleep this guide should help!</p>

<p>The guide was used on <code class="language-plaintext highlighter-rouge">Bluefin 42.20250928.1</code> with the kernel version <code class="language-plaintext highlighter-rouge">Linux 6.15.10-200.fc42.x86_64</code>. I believe the guide should be compatible with all universal-blue variants if needed.</p>

<p>A fix on the net was suggested to disable the bluetooth on sleep, and re-enable on resume. As I’m not that familiar with the systemd services I have documented the exact steps to follow below.
(Suggested here <a href="https://www.reddit.com/r/Fedora/comments/1g7ke8e/comment/ly84xrf/">https://www.reddit.com/r/Fedora/comments/1g7ke8e/comment/ly84xrf/</a>)</p>

<p>The commands and paths needed for me were:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>nano /etc/systemd/system/bt-sleep-fix.service
</code></pre></div></div>

<p>This opens the nano text editor.</p>

<p>In there paste this file:</p>

<div class="language-conf highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[<span class="n">Unit</span>]
<span class="n">Description</span>=<span class="n">Disable</span> <span class="n">Bluetooth</span> <span class="n">before</span> <span class="n">going</span> <span class="n">to</span> <span class="n">sleep</span>
<span class="n">Before</span>=<span class="n">sleep</span>.<span class="n">target</span>
<span class="n">StopWhenUnneeded</span>=<span class="n">yes</span>

[<span class="n">Service</span>]
<span class="n">Type</span>=<span class="n">oneshot</span>
<span class="n">RemainAfterExit</span>=<span class="n">yes</span>

<span class="n">ExecStart</span>=/<span class="n">usr</span>/<span class="n">sbin</span>/<span class="n">rfkill</span> <span class="n">block</span> <span class="n">bluetooth</span>
<span class="n">ExecStop</span>=/<span class="n">usr</span>/<span class="n">sbin</span>/<span class="n">rfkill</span> <span class="n">unblock</span> <span class="n">bluetooth</span>

[<span class="n">Install</span>]
<span class="n">WantedBy</span>=<span class="n">sleep</span>.<span class="n">target</span>

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

<p>Save the file as <code class="language-plaintext highlighter-rouge">bt-sleep-fix.service</code></p>

<p>Apply <code class="language-plaintext highlighter-rouge">chmod 644</code> to it (makes the file readable by all users on the system)</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo chmod </span>644 /etc/systemd/system/bt-sleep-fix.service
</code></pre></div></div>

<p>Now we need to register the service with systemd:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl start bt-sleep-fix.service
systemctl <span class="nb">enable </span>bt-sleep-fix.service
</code></pre></div></div>

<p>This should now enable and disable the bluetooth adapter automatically on sleep and resume. This avoids the crash on resume for me! Nice!</p>

<h2 id="end">End</h2>

<p>Thats it! I hope this guide helps you get your system resuming from sleep, while keeping bluetooth capabilities!</p>

<p>// Nils Henrik</p>]]></content><author><name>Nils Henrik Hals</name></author><category term="Blog" /><category term="shorts" /><summary type="html"><![CDATA[My Bluefin Linux crashed on resume from Suspend/sleep.]]></summary></entry><entry><title type="html">Flåklypa Grand Prix (2000) on Windows 10+ and Mac</title><link href="https://hals.app/blog/fgp-modern/" rel="alternate" type="text/html" title="Flåklypa Grand Prix (2000) on Windows 10+ and Mac" /><published>2024-12-28T00:00:00+00:00</published><updated>2024-12-28T00:00:00+00:00</updated><id>https://hals.app/blog/fgp-modern</id><content type="html" xml:base="https://hals.app/blog/fgp-modern/"><![CDATA[<p>The Flåklypa Grand Prix videogame, originally released in 2000, holds a cherished place in the hearts of Norwegians. Its engaging minigames have defined a generation, with over 400,000 copies sold. In 2002, a “Golden Edition” (Gullutgave) was released, further cementing its legacy.</p>

<p>However, these classic games are not officially compatible with modern systems like Windows 10 and 11 and MacOS, leaving fans longing for a way to relive their childhood memories on secure, up-to-date PCs.</p>

<p>This post will guide you step-by-step through the process of running Flåklypa Grand Prix Gullutgave on both macOS and modern Windows systems. Please note, you’ll need to own the original Flåklypa Grand Prix Gullutgave CDs and a USB CD drive.</p>

<p>Let’s dive into the setup process so you can rediscover the nostalgic magic of Flåklypa Grand Prix!</p>

<h2 id="mac-setup-guide-using-whisky-on-macos">Mac Setup Guide (Using Whisky on macOS)</h2>

<p>Follow these steps to install and run <strong>Flåklypa Grand Prix Gullutgave</strong> on a Mac (M1+) using <strong>Wine</strong> with <strong>Whisky</strong>.</p>

<h3 id="1-prepare-the-game-files">1. Prepare the Game Files</h3>

<ol>
  <li>Insert your original <strong>Flåklypa Gullutgave</strong> CDs into a USB CD drive.</li>
  <li>Copy all files from the CDs to two folders on your Mac. Ensure the folders are organized as follows:
    <ul>
      <li><strong>DISC_1/</strong>: Files from the white CD.</li>
      <li><strong>DISC_2/</strong>: Files from the second CD.</li>
    </ul>
  </li>
</ol>

<hr />

<h3 id="2-download-required-files">2. Download Required Files</h3>

<ol>
  <li>
    <p><strong>Update Patch 12</strong>:</p>

    <ul>
      <li>Download the <code class="language-plaintext highlighter-rouge">FGPGOLD_UPD12.exe</code> update patch from this site: <a href="https://sites.google.com/view/flaklypafix">FlåklypaFix</a>.</li>
    </ul>
  </li>
  <li>
    <p><strong>NO-CD Patch</strong>:</p>

    <ul>
      <li>Download a NO-CD patch for Flåklypa Gullutgave 12. One example is hosted on the Internet Archive: <a href="https://web.archive.org/web/20241226130042/https://www.old-games.ru/forum/attachments/fgp-upd12-7z.249506/">NO-CD Patch</a>.</li>
      <li>Extract the file and place the <code class="language-plaintext highlighter-rouge">FGP.exe</code> into a folder named <code class="language-plaintext highlighter-rouge">no-cd/</code>.</li>
    </ul>
  </li>
  <li>
    <p><strong>dgVoodoo2 Compatibility Fix</strong>:</p>
    <ul>
      <li>Download version <code class="language-plaintext highlighter-rouge">2.79.x</code> of dgVoodoo2 from <a href="https://github.com/legluondunet/mlls-tools/blob/master/dgVoodoo2/dgVoodoo2_79_3.zip">this repository</a> or from <a href="https://github.com/lutris/dgvoodoo2/releases/download/v2.79.1/dgvoodoo2-v2.79.1.tar.xz">this Lutris release page</a>.</li>
      <li>Extract the files and copy <code class="language-plaintext highlighter-rouge">dgVoodoo.conf</code>, <code class="language-plaintext highlighter-rouge">ddraw.dll</code>, and <code class="language-plaintext highlighter-rouge">d3dimm.dll</code> from the <code class="language-plaintext highlighter-rouge">/MS/x86/</code> folder into a new folder named <code class="language-plaintext highlighter-rouge">compability-fix/</code>.</li>
    </ul>
  </li>
</ol>

<hr />

<h3 id="3-install-whisky">3. Install Whisky</h3>

<ol>
  <li>
    <p>Download and install <strong>Whisky</strong> from <a href="https://getwhisky.app">getwhisky.app</a>.</p>

    <ul>
      <li>This guide is tested on Whisky version <code class="language-plaintext highlighter-rouge">v2.3.4</code>.</li>
    </ul>
  </li>
  <li>
    <p>Open Whisky and create a new “Bottle”:</p>
    <ul>
      <li>Set the Windows version to <strong>Windows XP</strong>.</li>
      <li>Wait a moment for the image to load (this may take up to a minute).</li>
    </ul>
  </li>
</ol>

<hr />

<h3 id="4-configure-the-bottle">4. Configure the Bottle</h3>

<ol>
  <li>
    <p>Open the Bottle settings and click <strong>Winetricks…</strong>:</p>

    <ul>
      <li>Go to the <strong>DLLs</strong> tab.</li>
      <li>Find <code class="language-plaintext highlighter-rouge">dsound</code> and click <strong>Run</strong> to install it.</li>
    </ul>
  </li>
  <li>
    <p>Configure Wine:</p>

    <ul>
      <li>Click <strong>Bottle Configuration</strong> → <strong>Open Wine Configuration</strong>.</li>
      <li>Go to the <strong>Libraries</strong> tab and add the following overrides:
        <ul>
          <li><code class="language-plaintext highlighter-rouge">security</code></li>
          <li><code class="language-plaintext highlighter-rouge">ddraw</code> (Ignore the performance warning.)</li>
          <li><code class="language-plaintext highlighter-rouge">d3dimm</code></li>
        </ul>
      </li>
      <li>Press <strong>OK</strong> to save the settings.</li>
    </ul>
  </li>
  <li>
    <p>Enable DXVK:</p>
    <ul>
      <li>Go back to <strong>Bottle Configuration</strong> and enable <strong>DXVK</strong>.</li>
    </ul>
  </li>
</ol>

<hr />

<h3 id="5-prepare-the-game-files-in-the-bottle">5. Prepare the Game Files in the Bottle</h3>

<ol>
  <li>Open the Bottle’s <strong>C Drive</strong>:
    <ul>
      <li>Click <strong>Open C Drive</strong> in the Bottle Configuration.</li>
      <li>Copy the following folders into the <code class="language-plaintext highlighter-rouge">C:/</code> directory of the Bottle:
        <ul>
          <li><code class="language-plaintext highlighter-rouge">DISC_1/</code></li>
          <li><code class="language-plaintext highlighter-rouge">DISC_2/</code></li>
          <li><code class="language-plaintext highlighter-rouge">patch12/</code> (containing <code class="language-plaintext highlighter-rouge">FGPGOLD_UPD12.exe</code>)</li>
          <li><code class="language-plaintext highlighter-rouge">no-cd/</code> (containing the NO-CD patched <code class="language-plaintext highlighter-rouge">FGP.exe</code>)</li>
          <li><code class="language-plaintext highlighter-rouge">compability-fix/</code> (containing <code class="language-plaintext highlighter-rouge">dgVoodoo.conf</code>, <code class="language-plaintext highlighter-rouge">ddraw.dll</code>, <code class="language-plaintext highlighter-rouge">d3dimm.dll</code>).</li>
        </ul>
      </li>
    </ul>
  </li>
</ol>

<hr />

<h3 id="6-install-the-game">6. Install the Game</h3>

<ol>
  <li>Run the installer:
    <ul>
      <li>In Whisky, click <strong>Run…</strong> and navigate to <code class="language-plaintext highlighter-rouge">C:/DISC_1/setup.exe</code>.</li>
      <li>Follow the installation steps, leaving all settings at their default values.</li>
      <li><strong>Important:</strong> Do not start the game when the installation finishes.</li>
    </ul>
  </li>
</ol>

<hr />

<h3 id="7-apply-update-patch-12">7. Apply Update Patch 12</h3>

<ol>
  <li>Run the patch installer:
    <ul>
      <li>Click <strong>Run…</strong> again and navigate to <code class="language-plaintext highlighter-rouge">C:/patch12/FGPGOLD_UPD12.exe</code>.</li>
      <li>Follow the installation steps. <strong>Do not start the game</strong> when finished.</li>
    </ul>
  </li>
</ol>

<hr />

<h3 id="8-apply-the-no-cd-patch">8. Apply the NO-CD Patch</h3>

<ol>
  <li>Replace the game executable:
    <ul>
      <li>Navigate to the game installation folder: <code class="language-plaintext highlighter-rouge">C:/Program Files (x86)/Flåklypa Grand Prix/</code>.</li>
      <li>Copy the <code class="language-plaintext highlighter-rouge">FGP.exe</code> file from the <code class="language-plaintext highlighter-rouge">no-cd/</code> folder and replace the existing <code class="language-plaintext highlighter-rouge">FGP.exe</code> file in the game folder.</li>
    </ul>
  </li>
</ol>

<hr />

<h3 id="9-apply-compatibility-fixes">9. Apply Compatibility Fixes</h3>

<ol>
  <li>Copy dgVoodoo2 files:
    <ul>
      <li>Copy <code class="language-plaintext highlighter-rouge">dgVoodoo.conf</code>, <code class="language-plaintext highlighter-rouge">ddraw.dll</code>, and <code class="language-plaintext highlighter-rouge">d3dimm.dll</code> from the <code class="language-plaintext highlighter-rouge">compability-fix/</code> folder into the game installation folder (<code class="language-plaintext highlighter-rouge">C:/Program Files (x86)/Flåklypa Grand Prix/</code>).</li>
      <li>Ensure these files are in the same folder as <code class="language-plaintext highlighter-rouge">FGP.exe</code> (not in subfolders).</li>
    </ul>
  </li>
</ol>

<hr />

<h3 id="10-pin-the-game-in-whisky">10. Pin the Game in Whisky</h3>

<ol>
  <li>
    <p>Pin the game executable:</p>

    <ul>
      <li>Go to the Bottle settings and click <strong>Pin Program</strong>.</li>
      <li>Set the name to <strong>Flåklypa</strong>.</li>
      <li>Locate and select the game executable: <code class="language-plaintext highlighter-rouge">C:/Program Files (x86)/Flåklypa Grand Prix/FGP.exe</code>.</li>
    </ul>
  </li>
  <li>
    <p>Start the game:</p>
    <ul>
      <li>Press the <strong>Play</strong> button in Whisky (green triangle).</li>
      <li>If the game does not start, right-click the pinned program and select <strong>Run…</strong>.</li>
    </ul>
  </li>
</ol>

<h3 id="known-issues-on-mac">Known Issues on Mac</h3>

<ol>
  <li>
    <p><strong>Animations may fail to load</strong>:</p>

    <ul>
      <li>If animations freeze, press <code class="language-plaintext highlighter-rouge">Space</code> to skip them, and then restart the animation.</li>
    </ul>
  </li>
  <li>
    <p><strong>Helicopter game freezes</strong>:</p>
    <ul>
      <li>The helicopter mini-game may experience periodic freezes. Unfortunately, there is no known fix for this.</li>
    </ul>
  </li>
</ol>

<p>The game should now be running!™️ Relive the nostalgic adventure of <strong>Flåklypa Grand Prix Gullutgave</strong>.</p>

<h2 id="windows-10-and-11-setup-guide">Windows 10 and 11 Setup Guide</h2>

<p>This guide outlines the process to install and run <strong>Flåklypa Grand Prix Gullutgave</strong> on modern Windows systems (Windows 10 and 11).</p>

<h3 id="1-prepare-the-game-files-1">1. Prepare the Game Files</h3>

<ol>
  <li>Insert your original <strong>Flåklypa Gullutgave</strong> CDs into a USB CD-drive.</li>
  <li>Copy all files from the CDs to two folders on your PC:
    <ul>
      <li><strong>DISC_1/</strong>: Copy files from the first (white) CD.</li>
      <li><strong>DISC_2/</strong>: Copy files from the second CD.</li>
    </ul>
  </li>
</ol>

<h3 id="2-download-required-files-1">2. Download Required Files</h3>

<ol>
  <li>
    <p><strong>Update Patch 12</strong>:</p>

    <ul>
      <li>Download the <code class="language-plaintext highlighter-rouge">FGPGOLD_UPD12.exe</code> update patch from this site: <a href="https://sites.google.com/view/flaklypafix">FlåklypaFix</a>.</li>
    </ul>
  </li>
  <li>
    <p><strong>NO-CD Patch</strong>:</p>

    <ul>
      <li>Download a NO-CD patch for Flåklypa Gullutgave 12. One example is hosted on the Internet Archive: <a href="https://web.archive.org/web/20241226130042/https://www.old-games.ru/forum/attachments/fgp-upd12-7z.249506/">NO-CD Patch</a>. Unzip the file and copy the <code class="language-plaintext highlighter-rouge">FGP.exe</code> file to a new folder named <code class="language-plaintext highlighter-rouge">no-cd/</code>.</li>
    </ul>
  </li>
  <li>
    <p><strong>dgVoodoo2 Compatibility Fix</strong>:</p>
    <ul>
      <li>Download version <code class="language-plaintext highlighter-rouge">2.79.x</code> of dgVoodoo2 from <a href="https://github.com/legluondunet/mlls-tools/blob/master/dgVoodoo2/dgVoodoo2_79_3.zip">https://github.com/legluondunet/mlls-tools/blob/master/dgVoodoo2/dgVoodoo2_79_3.zip</a></li>
      <li>Extract the files and copy <code class="language-plaintext highlighter-rouge">dgVoodoo.conf</code>from the folder, and <code class="language-plaintext highlighter-rouge">ddraw.dll</code>, and <code class="language-plaintext highlighter-rouge">d3dimm.dll</code> from the <code class="language-plaintext highlighter-rouge">/MS/x86/</code> folder to a new folder named <code class="language-plaintext highlighter-rouge">compability-fix/</code>.</li>
    </ul>
  </li>
</ol>

<h3 id="3-install-the-game">3. Install the Game</h3>

<ol>
  <li>Run the <code class="language-plaintext highlighter-rouge">setup.exe</code> from <code class="language-plaintext highlighter-rouge">DISC_1/</code>:
    <ul>
      <li>Navigate to <code class="language-plaintext highlighter-rouge">DISC_1/setup.exe</code> and double-click to start the installer.</li>
      <li>Follow the installation steps, leaving all settings at their default values.</li>
      <li><strong>Important:</strong> Do not start the game when the installation finishes.</li>
    </ul>
  </li>
</ol>

<h3 id="4-apply-update-patch-12">4. Apply Update Patch 12</h3>

<ol>
  <li>Run the patch installer:
    <ul>
      <li>Navigate to the location where you downloaded the patch (<code class="language-plaintext highlighter-rouge">FGPGOLD_UPD12.exe</code>) and double-click to run it.</li>
      <li>Follow the installation steps. Do not start the game when finished.</li>
    </ul>
  </li>
</ol>

<h3 id="5-apply-the-no-cd-patch">5. Apply the NO-CD Patch</h3>

<ol>
  <li>Replace the game executable:
    <ul>
      <li>Navigate to the game installation folder (default is <code class="language-plaintext highlighter-rouge">C:/Program Files (x86)/Flåklypa Grand Prix/</code>).</li>
      <li>Copy the <code class="language-plaintext highlighter-rouge">FGP.exe</code> file from the <code class="language-plaintext highlighter-rouge">no-cd/</code> folder and paste it into the game installation directory. Replace the existing <code class="language-plaintext highlighter-rouge">FGP.exe</code> file when prompted.</li>
    </ul>
  </li>
</ol>

<h3 id="6-apply-compatibility-fixes">6. Apply Compatibility Fixes</h3>

<ol>
  <li>Copy dgVoodoo2 files:
    <ul>
      <li>Copy <code class="language-plaintext highlighter-rouge">dgVoodoo.conf</code>, <code class="language-plaintext highlighter-rouge">ddraw.dll</code>, and <code class="language-plaintext highlighter-rouge">d3dimm.dll</code> from the <code class="language-plaintext highlighter-rouge">compability-fix/</code> folder into the game installation folder (<code class="language-plaintext highlighter-rouge">C:/Program Files (x86)/Flåklypa Grand Prix/</code>).</li>
    </ul>
  </li>
</ol>

<h3 id="7-compatibility-settings-if-needed">7. Compatibility Settings (If Needed)</h3>

<p>If the game does not run after completing the steps above, try the following:</p>

<ol>
  <li>Locate the <code class="language-plaintext highlighter-rouge">FGP.exe</code> file in the installation folder.</li>
  <li>Right-click on <code class="language-plaintext highlighter-rouge">FGP.exe</code> and select <strong>Properties</strong>.</li>
  <li>Go to the <strong>Compatibility</strong> tab and:
    <ul>
      <li>Check <strong>Run this program in compatibility mode for:</strong> and select <strong>Windows XP (Service Pack 3)</strong>.</li>
      <li>Check <strong>Reduce color mode</strong> and set it to <strong>16-bit (65536) color</strong>.</li>
    </ul>
  </li>
  <li>Click <strong>Apply</strong> and <strong>OK</strong>.</li>
</ol>

<h3 id="8-start-the-game">8. Start the Game</h3>

<ol>
  <li>Open the Windows Start Menu.</li>
  <li>Search for <strong>Flåklypa Grand Prix</strong> and launch the game from the shortcut.</li>
</ol>

<h3 id="troubleshooting">Troubleshooting</h3>

<ul>
  <li>If the game still does not start, navigate to <code class="language-plaintext highlighter-rouge">C:/Program Files (x86)/Flåklypa Grand Prix/</code>, right-click <code class="language-plaintext highlighter-rouge">FGP.exe</code>, and select <strong>Run as Administrator</strong>.</li>
  <li>Some graphics issues may occur depending on your hardware, but the dgVoodoo2 wrapper should resolve most compatibility problems.</li>
</ul>

<h3 id="enjoy">Enjoy!</h3>

<p>Everything should now be working!™️ Enjoy the nostalgia of <strong>Flåklypa Grand Prix Gullutgave</strong>.</p>

<h2 id="why-does-the-game-work-now">Why does the game work now?</h2>

<p>Flåklypa is an old game that relies on DirectX 8 and Direct Draw. The game relies on some graphics driver quirks.</p>

<p>DirectX 8 and DirectDraw is not implemented the same in newer windows, so some older function have been removed. We use a genious wrapper called dgVoodoo2 that in practice re-implements the old features for newer Windows versions. Performance may not equal the original setup, but we get it working!</p>

<p>Additionally we have a NO-CD FGP.exe that removes the need for a CD-key. The latest FGP patch required a key you could get from the flåklypa website, but this site has not been available for many years.</p>

<h2 id="bonus-cheat-codes">Bonus Cheat Codes</h2>

<p>As these seem to be lost in time I write the cheat codes here. Cheat codes was a thing used in the ancient times before you paid to unlock everything in games.</p>

<p>Just type the cheat-code anywhere in the games menus to unlock various things. If the cheat is typed correctly there will be a sound and sometimes a popup window.</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">NOMORETWEAKS</code>: Unlock all games, scenes and activities</li>
  <li><code class="language-plaintext highlighter-rouge">CVG</code> Unlock all activities</li>
  <li><code class="language-plaintext highlighter-rouge">ASSISTANCE</code>: Unlock all games</li>
  <li><code class="language-plaintext highlighter-rouge">BEDTIMESTORY</code> Unlock all story pages in the photo-book</li>
  <li><code class="language-plaintext highlighter-rouge">SHOWMEHEAVEN</code>: Unlock a single puzzle image</li>
  <li><code class="language-plaintext highlighter-rouge">BLOWMEAWAY</code>: Unlock a single new film</li>
  <li><code class="language-plaintext highlighter-rouge">NOMOREDISHES</code>: Receive 3 pieces for the Il Tempo Gigante</li>
  <li><code class="language-plaintext highlighter-rouge">GIMMETRACKS</code>: Unlock new Tracks for the races</li>
  <li><code class="language-plaintext highlighter-rouge">HOTWHEELS</code>: Unlock new cars for the races</li>
  <li><code class="language-plaintext highlighter-rouge">ZOOMIN</code>: Zoom in towards the cursor?</li>
  <li><code class="language-plaintext highlighter-rouge">ZOOMOUT</code>: Zoom back to normal zoom.</li>
</ul>

<h2 id="end">End</h2>

<p>Thats it! I hope this guide helps you get the game working.</p>]]></content><author><name>Nils Henrik Hals</name></author><category term="Blog" /><category term="shorts" /><summary type="html"><![CDATA[The Flåklypa Grand Prix videogame, originally released in 2000, holds a cherished place in the hearts of Norwegians. Its engaging minigames have defined a generation, with over 400,000 copies sold. In 2002, a “Golden Edition” (Gullutgave) was released, further cementing its legacy.]]></summary></entry><entry><title type="html">KTX: Convert png cubemap to ktx2</title><link href="https://hals.app/blog/ktx-cubemap-compressed/" rel="alternate" type="text/html" title="KTX: Convert png cubemap to ktx2" /><published>2024-12-19T00:00:00+00:00</published><updated>2024-12-19T00:00:00+00:00</updated><id>https://hals.app/blog/ktx-cubemap-compressed</id><content type="html" xml:base="https://hals.app/blog/ktx-cubemap-compressed/"><![CDATA[<p>In this post I show you how to create a compressed ktx2 file from a set of cubemap images.</p>

<p>I used the compression to compress a uncompressed 360MB 4k skybox into a 0.5MB(!) file (using a very sparse space-skybox)</p>

<p>The ktx docs are complex to read and short on examples. I offer my solution here if anyone else needs to convert a cubemap to a basisu compressed ktx2 cubemap.</p>

<p>First install the ktx tools. I use windows and downloaded the installer from the “Releases” page on GitHub: <a href="https://github.com/KhronosGroup/KTX-Software/releases">Link</a> (I used v4.3.2)</p>

<p>The below command was all I needed. (You may have to scroll right to see the whole command, or just copy everything.)</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># ktx v4.3.2</span>
ktx create <span class="nt">--format</span> R8G8B8_SRGB <span class="nt">--encode</span> basis-lz <span class="nt">--cubemap</span> ./right.png ./left.png ./top.png ./bottom.png ./front.png ./back.png output.ktx2
</code></pre></div></div>

<p>Breakdown of the command:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">ktx create</code>: This is the command to create a new KTX file.</li>
  <li><code class="language-plaintext highlighter-rouge">--format R8G8B8_SRGB</code>: This specifies the format of the images. R8G8B8_SRGB means the images are in 8-bit RGB format with sRGB color space. (This is probably what you want.)</li>
  <li><code class="language-plaintext highlighter-rouge">--encode basis-lz</code>: This specifies the encoding and compression method to use, in this case, <code class="language-plaintext highlighter-rouge">basis-lz</code> for Basis Universal ETC1S compression. (You may also try <code class="language-plaintext highlighter-rouge">uastc</code> for higher visual quality if needed)</li>
  <li><code class="language-plaintext highlighter-rouge">--cubemap</code>: This flag indicates that the images are to be used as a cubemap.</li>
  <li><code class="language-plaintext highlighter-rouge">./right.png ./left.png ./top.png ./bottom.png ./front.png ./back.png</code>: These are the input images for the cubemap, representing the six faces of the cube as inputs.</li>
  <li><code class="language-plaintext highlighter-rouge">output.ktx2</code>: The last parameter is the name of the output KTX2 file.</li>
</ul>

<p>You may want to decide which encoding you want to use, check the docs for variants.</p>

<p>Thats all for this time.</p>

<p>Note: If you have HDR images this is not yet supported as of writing. But it seems like <code class="language-plaintext highlighter-rouge">UASTC HDR</code> support should be coming.</p>

<p>// Nils Henrik</p>]]></content><author><name>Nils Henrik Hals</name></author><category term="Blog" /><category term="shorts" /><category term="3D" /><summary type="html"><![CDATA[In this post I show you how to create a compressed ktx2 file from a set of cubemap images.]]></summary></entry><entry><title type="html">Reverse proxy external https backend with Caddy</title><link href="https://hals.app/blog/caddy-reverse-proxy/" rel="alternate" type="text/html" title="Reverse proxy external https backend with Caddy" /><published>2024-12-08T00:00:00+00:00</published><updated>2024-12-08T00:00:00+00:00</updated><id>https://hals.app/blog/caddy-reverse-proxy</id><content type="html" xml:base="https://hals.app/blog/caddy-reverse-proxy/"><![CDATA[<p>In this post, I’ll introduce you to Caddy and guide you through configuring it as a reverse proxy for hosting a Single Page App (SPA).</p>

<p>The post expects that you have a caddy server running with a basic Caddyfile, and will help you configure the SPA and https backend configuration.</p>

<h2 id="single-page-apps-and-backend-apis">Single Page Apps and Backend APIs</h2>

<p>Most Single Page Apps rely on a backend API, and sometimes that API is hosted on a different server. If your backend is secured, you might need to send Authorization headers, and the backend server will need to support Cross-Origin Resource Sharing (CORS) headers.</p>

<p>CORS requests involve an additional roundtrip to the server, which can double the request time and increase latency when using your app.</p>

<p>By setting up a reverse proxy, you can “hide” the fact that your backend is hosted on a separate server, improving performance and simplifying your architecture.</p>

<h2 id="a-common-setup">A Common Setup</h2>

<p>A typical reverse proxy setup might look like this:</p>

<p><code class="language-plaintext highlighter-rouge">/index.html</code>: This is retrieved directly from Server A 🟢.</p>

<p><code class="language-plaintext highlighter-rouge">/backend/api/posts</code>: This request is forwarded by Server A to Server B🔵’s <code class="language-plaintext highlighter-rouge">/api/posts</code> route. (Server A then sends the response back to the browser).</p>

<p>In this setup, the browser doesn’t know that it’s actually communicating with a backend hosted on another domain. The browser does not require CORS pre-requests, and its easier to secure your API and website. 😎</p>

<h2 id="what-is-caddy">What is Caddy?</h2>

<p>Caddy is a powerful web server designed to handle a variety of tasks, including serving as a reverse proxy. It’s simple to configure, flexible, and well-suited for modern web applications like SPAs. Caddy fills the same role as other servers such as <code class="language-plaintext highlighter-rouge">nginx</code> or <code class="language-plaintext highlighter-rouge">traefik</code>.</p>

<h2 id="setting-up-caddy-as-a-reverse-proxy-for-spa">Setting Up Caddy as a Reverse Proxy for SPA</h2>

<p>Here’s a basic Caddyfile to configure Caddy as a reverse proxy for an SPA:</p>

<pre><code class="language-caddy"># This file was tested with Caddy v2.9

# Basic reverse proxy setup
yourdomain.example.com {

  # IMPORTANT: `handle_path` automatically "replaces" the route when forwarding.
  # Example: /backend/api/blog/posts will be forwarded to /api/blog/posts.
  # If you don't want this replacement behavior, use `handle` instead.
  handle_path /backend/* {
    reverse_proxy {
      to your_backend_url:8000
      # If your backend is HTTPS, see the example below for additional configuration.
    }
  }

  # This is the basic SPA hosting setup
  handle {
    # Root: We assume the SPA files are located in the /srv directory.
    root * /srv
    # `try_files` will fall back to the index if no matching files are found.
    try_files {path} /index.html

    # file_server will serve static files from the root path.
    file_server
  }
}
</code></pre>

<h2 id="modifying-for-external-https-backends">Modifying for External HTTPS Backends</h2>

<p>If your reverse proxy is forwarding requests to an external server hosted on HTTPS, you’ll need to make a small modification to handle this correctly. Specifically, you need to adjust the Host header.</p>

<p>Here’s how to modify your Caddyfile for this scenario:</p>

<pre><code class="language-caddy">handle_path /backend/* {
  reverse_proxy {
    to https://your_backend_url.example.com
    # If the target server is external and hosted over HTTPS,
    # you need to modify the `Host` header to match the target server's host.
    header_up Host {http.reverse_proxy.upstream.hostport}
    # This ensures the correct Host header is sent for HTTPS requests.
  }
}
</code></pre>

<p>This configuration forwards requests to an external server securely, ensuring proper handling of the Host header for HTTPS connections.</p>

<p class="notice--info">Note: The guide was written with Caddy 2.9 in mind at the end of 2024, and may be outdated when you read this. Please check with the official docs if anything does not work as expected. <a href="https://caddyserver.com/docs/caddyfile/patterns#single-page-apps-spas">https://caddyserver.com/docs/caddyfile/patterns#single-page-apps-spas</a></p>

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

<p>That’s all you need to set up a reverse proxy with Caddy to host an SPA! If you’re interested in learning how to run Caddy with Docker, stay tuned for a future post on that topic.</p>

<p>For more detailed documentation, visit the official Caddy docs: <a href="https://caddyserver.com/docs">https://caddyserver.com/docs</a></p>

<p>// Nils Henrik</p>]]></content><author><name>Nils Henrik Hals</name></author><category term="Blog" /><category term="caddy" /><summary type="html"><![CDATA[In this post, I’ll introduce you to Caddy and guide you through configuring it as a reverse proxy for hosting a Single Page App (SPA).]]></summary></entry><entry><title type="html">shift_space_for_snake_case using PowerToys</title><link href="https://hals.app/blog/underscore-shortcut/" rel="alternate" type="text/html" title="shift_space_for_snake_case using PowerToys" /><published>2024-08-30T00:00:00+00:00</published><updated>2024-08-30T00:00:00+00:00</updated><id>https://hals.app/blog/underscore-shortcut</id><content type="html" xml:base="https://hals.app/blog/underscore-shortcut/"><![CDATA[<p>My biggest problem with <code class="language-plaintext highlighter-rouge">snake_case</code> in programming is that I find it flow-breaking to write the underscore separator character.</p>

<p>If I had a dedicated underscore key on my keyboard it would be a lot easier to cope with.</p>

<p>There are some guides that suggest AutoHotKey remapping a key, but I did not have the opportunity to install AutoHotKey on my work computer.</p>

<p>But the Windows PowerToys package from Microsoft is allowed on my PC, and that has a built in Keyboard Manager.</p>

<p>In PowerToys you are able to remap Shortcuts:</p>

<figure class=""><img src="/assets/images/powertoys-key-remap.png" alt="Screenshot showing the remapping of shortcuts functionality in PowerToys" /><figcaption>
      Remapping shortcuts in PowerToys

    </figcaption></figure>

<p>By using the remap functionality you can easily add remaps for singular apps so they do not get in the way for shortcuts in other apps!</p>

<p>This way I added the shortcut <code class="language-plaintext highlighter-rouge">Shift-Space</code> to write <code class="language-plaintext highlighter-rouge">_</code> instead of doing nothing in Godot Engine! I just need one hand to write a underscore now. Finally I can write Python or GDScript without annoyances!</p>

<p>In the future I might consider remapping <code class="language-plaintext highlighter-rouge">CAPS_LOCK</code> as a dedicated underscore key, as that makes it just a single key-press for an underscore. Making it just as easy to write <code class="language-plaintext highlighter-rouge">snake_case</code> as <code class="language-plaintext highlighter-rouge">camelCase</code>!</p>

<p>Thats it for this time. Hope this helps someone out!</p>

<p>// Nils Henrik</p>]]></content><author><name>Nils Henrik Hals</name></author><category term="Blog" /><category term="shorts" /><summary type="html"><![CDATA[My biggest problem with snake_case in programming is that I find it flow-breaking to write the underscore separator character.]]></summary></entry><entry><title type="html">Enforce lowercase branch names on GitHub</title><link href="https://hals.app/blog/enforce-lowercase-branches-github/" rel="alternate" type="text/html" title="Enforce lowercase branch names on GitHub" /><published>2024-04-19T00:00:00+00:00</published><updated>2024-04-19T00:00:00+00:00</updated><id>https://hals.app/blog/enforce-lowercase-branches-github</id><content type="html" xml:base="https://hals.app/blog/enforce-lowercase-branches-github/"><![CDATA[<p>In this post I describe how to set up a branch name rule to avoid issues with case-sensitive branches in GitHub.</p>

<p>To introduce the problem: Git uses case-sensitive branches, but stores the branches in a file system on disk. If the filesystem is not case sensitive this will cause problems with git.</p>

<p>For example: Given two branches: <code class="language-plaintext highlighter-rouge">feature/cool-feature</code> and <code class="language-plaintext highlighter-rouge">Feature/awesome-button</code>. If a Windows user tries to checkout both these branches git will complain that the second branch is not found. (<a href="https://stackoverflow.com/questions/55051729/github-branches-case-sensitivity-issue">See StackOverflow discussion</a>. You may find it with another casing, but that will then again fail on GitHub when pushing changes! The best way to avoid this (without fixing git itself…) is to enforce lower case branch names.</p>

<p class="notice--info">Note: I suggest enforcing this only if all existing GitHub branches are lower case. Try to adjust the regex to your naming standard if its different from the one suggested in this post.</p>

<p>To enforce lower case branch names in GitHub we can use a repository setting called <code class="language-plaintext highlighter-rouge">Code and automation -&gt; Rules -&gt; Rulesets</code></p>

<p>By creating a ruleset, we can mandate that branch names be lowercase.</p>

<ol>
  <li>Go to your repo. Find the Settings. Find the <code class="language-plaintext highlighter-rouge">Rulesets</code> menu item.</li>
  <li>Create a ruleset:
    <ol>
      <li>Name the ruleset: <code class="language-plaintext highlighter-rouge">Avoid git case-sensitivity issues by enforcing lowercase branch names</code></li>
      <li>Target all branches.</li>
      <li>Enable the “Restrict branch names” rule</li>
      <li>Create a restriction for the branch name:
        <ol>
          <li>Branch name rule</li>
          <li>Must NOT match the following regex</li>
          <li><code class="language-plaintext highlighter-rouge">(?-i)([A-Z])(Branch name must be fully lower case)*</code>
            <ul>
              <li>The (?-i) part is crucial as it disables case-insensitive matching for the following pattern!</li>
              <li>Also note the Optional description <code class="language-plaintext highlighter-rouge">(Branch name must be fully lower case)*</code>. This helps give a message to the user of what is wrong. (Standard error message displayed in git cli is <code class="language-plaintext highlighter-rouge">remote: - Branch name must not match a given regex pattern: (?-i)[A-Z]</code>, so adding the description into the regex helps indicate whats wrong)</li>
            </ul>
          </li>
          <li>Description <code class="language-plaintext highlighter-rouge">Branch name must be fully lowercase</code> (This description is not available in the git terminal, so not sure where its used)</li>
        </ol>
      </li>
    </ol>
  </li>
  <li>Press <code class="language-plaintext highlighter-rouge">Save Changes</code> button and test this in your repo</li>
</ol>

<p>When pushing a branch with a upper-case letter the client will get an error message that’s descriptive enough to pinpoint the problem! This avoids them from pushing the branch, and affecting other users.</p>

<p>Thats it for this time, I hope this helps avoid the branch name case sensitivity issue for you. I’ts also my hope that git or GitHub at some point will fix the issue for all users.</p>

<p>Please vote for my feature suggestion on GitHub if you’d like GitHub to provide a better fix: <a href="https://github.com/orgs/community/discussions/20518">https://github.com/orgs/community/discussions/20518</a></p>

<p>// Nils Henrik</p>]]></content><author><name>Nils Henrik Hals</name></author><category term="Blog" /><category term="git" /><category term="github" /><summary type="html"><![CDATA[In this post I describe how to set up a branch name rule to avoid issues with case-sensitive branches in GitHub.]]></summary></entry><entry><title type="html">Shorts: TeamCity Url Build Trigger</title><link href="https://hals.app/blog/teamcity-url-build-trigger/" rel="alternate" type="text/html" title="Shorts: TeamCity Url Build Trigger" /><published>2024-03-01T00:00:00+00:00</published><updated>2024-03-01T00:00:00+00:00</updated><id>https://hals.app/blog/teamcity-url-build-trigger</id><content type="html" xml:base="https://hals.app/blog/teamcity-url-build-trigger/"><![CDATA[<p>In this post I describe how to configure the polling interval of the TeamCity <code class="language-plaintext highlighter-rouge">Url Build Trigger</code>.</p>

<p>The <a href="https://plugins.jetbrains.com/plugin/9074-url-build-trigger">Url Build Trigger plugin</a> by JetBrains works ok. But it by default polls the target url every 30 seconds for every build configuration.</p>

<p>There’s no configuration options in the UI, no source code, and no documentation. But the plugin is licensed with Apache license, so I de-compiled the JAR file for the plugin and found this setting:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">String</span> <span class="n">pollInterval</span> <span class="o">=</span> <span class="n">context</span><span class="o">.</span><span class="na">getBuildType</span><span class="o">().</span><span class="na">getParameterValue</span><span class="o">(</span><span class="s">"teamcity.internal.url.build.trigger.poll.interval"</span><span class="o">);</span>
</code></pre></div></div>

<p>Adding the following to the teamcity <code class="language-plaintext highlighter-rouge">internal.properties</code> file you can set the polling interval to 10 minutes (or any whole second interval)!:</p>

<div class="language-properties highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Set the "Url build trigger" plugins polling interval to 10 minutes
</span><span class="py">url.build.trigger.poll.interval</span><span class="p">=</span><span class="s">600</span>
</code></pre></div></div>

<p>It will now wait 600 seconds instead of 30! Cool 😎</p>

<p>That’s it for this time!</p>

<p>// Nils Henrik</p>]]></content><author><name>Nils Henrik Hals</name></author><category term="Blog" /><category term="shorts" /><summary type="html"><![CDATA[In this post I describe how to configure the polling interval of the TeamCity Url Build Trigger.]]></summary></entry></feed>