Sonntag, Oktober 11, 2026

Den Ultimate 64 per REST-API steuern – ein VB.NET Projekt


Einleitung⇒ Mit der REST API des Ultimate 64 lässt sich der FPGA-C64-Klon vollständig per Netzwerk steuern. Dieser Artikel zeigt, wie der U64Manager entstand – eine Windows-Anwendung in VB.NET mit Hex-Viewer, FTP-Datei-Browser, Disk-Image-Erstellung und OBS-Streaming-Integration.

U64Manager – Ein VB.NET Tool für den Ultimate 64

Veröffentlicht am 11. Oktober 2026 · Tobonocom’s Retro Journal

Wie alles begann

Wer einen Ultimate 64 besitzt, kennt das Gerät als beeindruckende FPGA-basierte Neuinterpretation des Commodore 64 – mit Netzwerkanbindung, modernem HDMI-Ausgang und einer REST API, die eine vollständige Fernsteuerung ermöglicht. Genau diese API hat mich gereizt: Warum nicht eine eigene Windows-Anwendung bauen, die alle Funktionen komfortabel zugänglich macht?

Das Ergebnis ist der U64Manager – eine Windows Forms Anwendung in Visual Basic .NET, die ich gemeinsam mit Claude entwickelt habe. Der Artikel beschreibt die Entstehung, die Architektur und zeigt konkrete Codebeispiele aus dem Projekt.

Die API des Ultimate 64

Der Ultimate 64 stellt ab Firmware 3.11 eine REST API unter

http://<IP-Adresse>/v1/

bereit. Die wichtigsten Endpunkte sind:

  • /v1/version – API-Version und Verbindungstest
  • /v1/info – Geräteinformationen (Firmware, FPGA, Hostname)
  • /v1/machine – Maschinensteuerung (Reset, Reboot, Speicher lesen/schreiben)
  • /v1/runners – Programme und Mediadateien starten (PRG, CRT, SID, MOD)
  • /v1/drives – Floppy-Laufwerke verwalten, Disk-Images mounten
  • /v1/configs – Konfiguration lesen und schreiben
  • /v1/streams – Video- und Audio-Streams starten und stoppen
  • /v1/files – Disk-Images erstellen

Ab Firmware 3.12 kann die API zusätzlich mit einem Passwort gesichert werden, das über den

X-Password

HTTP-Header übermittelt wird.

Architektur: Sauber geschichtet von Anfang an

Bevor die erste Zeile Code geschrieben wurde, stand die Entscheidung für eine klare Schichtenarchitektur:

U64Manager/
├── U64ApiClient.vb       ← HTTP-Schicht (GET, PUT, POST, Binary)
├── U64Manager.vb         ← Fassade: bündelt alle Services
├── JsonHelper.vb         ← JSON-Parsing mit Newtonsoft.Json
├── C64Theme.vb           ← Zentrales Farbschema
├── HexViewerControl.vb   ← Hex-Viewer UserControl
├── FileBrowserControl.vb ← FTP-Datei-Browser UserControl
├── CreateDiskImageForm.vb← Dialog für Disk-Image-Erstellung
├── MainForm.vb           ← Windows Forms GUI
├── Models/
│   ├── ApiResponse.vb    ← Einheitliches Antwort-Objekt
│   ├── U64Info.vb        ← Gerätinfo-Datenmodell
│   └── U64DriveInfo.vb   ← Laufwerks-Datenmodell
└── Services/
    ├── AboutService.vb   ← /v1/version, /v1/info
    ├── MachineService.vb ← /v1/machine
    ├── RunnerService.vb  ← /v1/runners
    ├── DriveService.vb   ← /v1/drives
    ├── ConfigService.vb  ← /v1/configs
    ├── StreamService.vb  ← /v1/streams
    ├── FileService.vb    ← /v1/files
    └── FtpService.vb     ← FTP-Zugriff auf Dateisystem

Die zentrale U64ApiClient-Klasse kapselt die gesamte HTTP-Kommunikation. Alle Requests laufen über

HttpClient

– asynchron mit

Async/Await

, damit die GUI immer reaktionsfähig bleibt:

Public Class U64ApiClient
    Implements IDisposable
    Private ReadOnly _httpClient As HttpClient
    Private _baseUrl As String
    Private _password As String
    Public Sub New(baseUrl As String, Optional password As String = ”")
        _baseUrl = baseUrl.TrimEnd(”/”c)
        _password = password
        _httpClient = New HttpClient()
        _httpClient.Timeout = TimeSpan.FromSeconds(10)
        UpdatePasswordHeader()
    End Sub
    Private Sub UpdatePasswordHeader()
        _httpClient.DefaultRequestHeaders.Remove(”X-Password”)
        If Not String.IsNullOrEmpty(_password) Then
            _httpClient.DefaultRequestHeaders.Add(”X-Password”, _password)
        End If
    End Sub
    Public Async Function GetAsync(path As String) As Task(Of ApiResponse)
        Dim url = BuildUrl(path)
        Try
            Dim response = Await _httpClient.GetAsync(url)
            Return Await BuildApiResponse(response)
        Catch ex As TaskCanceledException
            Return ApiResponse.Timeout(url)
        Catch ex As HttpRequestException
            Return ApiResponse.FromConnectionError(url, ex.Message)
        End Try
    End Function
    Public Async Function PostBinaryAsync(path As String, fileBytes As Byte(), fileName As String) As Task(Of ApiResponse)
        Dim url = BuildUrl(path)
        Dim content = New ByteArrayContent(fileBytes)
        content.Headers.Add(”Content-Type”, ”application/octet-stream”)
        content.Headers.Add(”Content-Disposition”, ”attachment; filename=”"” & fileName & ”"”")
        Dim response = Await _httpClient.PostAsync(url, content)
        Return Await BuildApiResponse(response)
    End Function
End Class

Ein wichtiger Lernmoment während der Entwicklung: Die U64 API erwartet Binärdateien (SID, PRG, CRT) als direkten

ByteArrayContent

mit

Content-Disposition

Header – nicht als Multipart-Form-Data. Das war zunächst die Ursache für einen hartnäckigen 400-Fehler beim SID-Abspielen.

Die Service-Schicht

Jeder API-Bereich hat seinen eigenen Service. Der MachineService zeigt das Prinzip:

Public Class MachineService
    Private ReadOnly _client As U64ApiClient
    Public Sub New(client As U64ApiClient)
        _client = client
    End Sub
    Public Async Function ResetAsync() As Task(Of ApiResponse)
        Return Await _client.PutAsync(”/v1/machine:reset”)
    End Function
    Public Async Function WriteMemAsync(address As String, data As String) As Task(Of ApiResponse)
        Dim path = $”/v1/machine:writemem?address={address.ToUpper()}&data={data.ToUpper()}”
        Return Await _client.PutAsync(path)
    End Function
    Public Async Function ReadMemAsync(address As String, Optional length As Integer = 256) As Task(Of Byte())
        Dim path = $”/v1/machine:readmem?address={address.ToUpper()}&length={length}”
        Return Await _client.GetBinaryAsync(path)
    End Function
End Class

Die U64Manager-Fassadenklasse bündelt alle Services zu einer einheitlichen Schnittstelle:

‘ Verbindung herstellen
Dim u64 As New U64Manager(”http://192.168.2.135”)
‘ Maschine steuern
Await u64.Machine.ResetAsync()
‘ Speicher schreiben (Border-Farbe auf Grün setzen)
Await u64.Machine.WriteMemAsync(”D020”, ”05”)
‘ SID-Datei von lokalem PC abspielen
Await u64.Runners.PlaySidFromFileAsync(”C:SIDCommando.sid”, songNr:=0)
‘ Disk-Image mounten
Await u64.Drives.MountImageFromFileAsync(U64Drive.A, ”C:GamesMayhem.d64”)
‘ Ressourcen freigeben
u64.Dispose()

JSON-Parsing mit Newtonsoft.Json

Die API liefert JSON-Antworten. Der JsonHelper parst diese in typisierte Objekte. Ein Beispiel für das Gerätinfo-Modell:

Public Class U64Info
    <JsonProperty(”product”)>
    Public Property Product As String
    <JsonProperty(”firmware_version”)>
    Public Property FirmwareVersion As String
    <JsonProperty(”fpga_version”)>
    Public Property FpgaVersion As String
    <JsonProperty(”hostname”)>
    Public Property Hostname As String
    <JsonProperty(”unique_id”)>
    Public Property UniqueId As String
End Class

Die tatsächliche Gerätantwort sieht dann so aus:

[13:18:14]   Produkt:   C64 Ultimate
[13:18:14]   Firmware:  3.14
[13:18:14]   FPGA:      121
[13:18:14]   Core:      1.47
[13:18:14]   Hostname:  C64-Ultimate
[13:18:14]   Unique ID: C4E17C

Der Hex-Viewer mit C64-Dekodierung

Eine der interessantesten Funktionen ist der eingebaute Hex-Viewer.

C64UManager_HEXview.png

Er liest direkt aus dem C64-Speicher via DMA und stellt die Bytes in drei Spalten dar: Adresse, Hex-Werte und dekodierter Text.
Da der C64 verschiedene Zeichenkodierungen verwendet, unterstützt der Viewer vier Dekodierungsmodi:

  • Screencodes ($0400) – für den Bildschirmspeicher. Buchstaben A-Z liegen hier bei den Werten 1-26
  • PETSCII ($0200+) – für den Tastaturpuffer und BASIC-Bereich
  • ASCII – für Programme die Standard-ASCII verwenden
  • Keine (Hex only) – reine Hex-Darstellung ohne Textspalte

Der Screencode-Decoder zeigt wie unterschiedlich die C64-Zeichensätze zur bekannten ASCII-Tabelle sind:

Private Function DecodeScreencode(b As Byte) As String
    Select Case b
        Case 32 : Return ” ”           ’ Leerzeichen
        Case 1 To 26 : Return Chr(b + 64)  ’ A-Z (Wert 1=A, 2=B, …)
        Case 65 To 90 : Return Chr(b + 32).ToString().ToLower() ’ a-z
        Case 48 To 57 : Return Chr(b)  ’ 0-9
        Case 42 : Return ”*”
        Case 46 : Return ”.”
        ’ …
        Case Else : Return ”.”
    End Select
End Function

Beim Lesen des Bildschirmspeichers ab $0400 im Screencode-Modus wird die C64-Startmeldung direkt lesbar:

**** COMMODORE 64 BASIC V2 ****

Datei-Browser via FTP

Die REST API des U64 bietet kein Verzeichnis-Listing – aber der U64 unterstützt FTP-Zugriff auf sein Dateisystem. Der FtpService nutzt den eingebauten .NET

FtpWebRequest

um das Dateisystem zu durchsuchen. FTP muss im U64-Menü unter Network Settings → FTP Server aktiviert werden.

Der Browser zeigt Dateien mit passenden Icons je nach Typ und ermöglicht per Doppelklick oder Rechtsklick direkte Aktionen:

  • 🎵 SID/MOD – direkt abspielen
  • 💾 PRG – laden oder starten
  • 🎮 CRT – als Cartridge starten
  • 💿 D64/D71/D81 – als Laufwerk mounten

Neue Disk-Images können direkt im aktuellen Verzeichnis des Browsers erstellt werden – D64 (35 oder 40 Tracks), D71, D81 und DNP werden unterstützt.

OBS Streaming-Integration

Eine besonders praktische Funktion für Content-Creator und Streamer: Der U64 kann seinen Video- und Audio-Ausgang über das Netzwerk streamen. Mit dem C64Stream Plugin für OBS Studio (github.com/chrisgleissner/c64stream) landet das Bild direkt in OBS – ganz ohne Capture-Karte.

C64UManager_Stream.png

Das Plugin-Installationsverzeichnis unter Windows lautet:

C:ProgramDataobs-studioobs-plugins64bitc64stream.dll

Im Stream-Tab der Anwendung lassen sich Video- (Port 11000) und Audio-Stream (Port 11001) bequem starten und stoppen – einzeln oder zusammen. Ein Live-Status zeigt ob der Stream gerade läuft.

Das C64-Farbthema

Natürlich durfte ein passendes Design nicht fehlen. Das zentrale C64Theme-Modul definiert alle Farben im klassischen C64-Stil und wendet sie rekursiv auf alle Controls an:

Public Module C64Theme
    Public ReadOnly ColorBackground As Color = Color.FromArgb(0, 0, 210)
    Public ReadOnly ColorText As Color = Color.FromArgb(170, 170, 255)
    Public ReadOnly ColorWarning As Color = Color.FromArgb(255, 255, 85)
    Public ReadOnly ColorSuccess As Color = Color.FromArgb(85, 255, 85)
    Public ReadOnly FontNormal As New Font(”Consolas”, 11, FontStyle.Regular)
    Public Sub Apply(frm As Form)
        frm.BackColor = ColorBackground
        frm.ForeColor = ColorText
        frm.Font = FontNormal
        ApplyToControls(frm.Controls)
    End Sub
End Module

Dunkelblauer Hintergrund, hellblaue Consolas-Schrift, gelbe Gruppenüberschriften mit ▶-Pfeil – der U64Manager sieht aus wie er klingt.

Fazit

Der U64Manager ist aus einem einfachen Wunsch entstanden: Die REST API des Ultimate 64 komfortabel nutzen zu können. Herausgekommen ist eine vollständige Fernsteuerungsanwendung mit sieben Tabs, FTP-Datei-Browser, Hex-Viewer mit C64-Zeichensatzdekodierung und OBS-Integration.

Das Projekt hat gezeigt wie gut sich die moderne .NET-Welt (HttpClient, Async/Await, Newtonsoft.Json) mit dem klassischen Windows Forms Ansatz verbinden lässt. Wer selbst einen Ultimate 64 besitzt und mit dem Gedanken spielt, eine eigene Anwendung zu bauen: Die API-Dokumentation findet sich unter 1541u-documentation.readthedocs.io.

Wer den U64Manager einmal in Aktion sehen möchte kann sich mein Demonstrationsvideo auf meinem Youtube-Kanal ansehen: