-
Notifications
You must be signed in to change notification settings - Fork 0
For developers
A little about the technical details about the UAC Launch Control app — if you want to contribute, or build for yourself, read more below.
You can also check out the more detailed explanation about the project and its structure on DeepWiki: https://deepwiki.com/mikkelrask/uaclaunchcontrol
Note: this document gets outdated pretty quickly here in the early development phase, so if you run into any trouble please just let me know, and I'll gladly help out, and update the page accordingly.
- Frontend: React 19 + TypeScript + TailwindCSS v3
- Backend: Express.js API server
- Desktop: Electron (main + renderer processes)
- Build Tool: electron-vite
- File Watching: Chokidar for real-time WAD directory synchronization
- State Management: TanStack Query (React Query)
- UI Components: Radix UI + shadcn/ui
- Routing: wouter (path-only, no query param tracking)
- Icons: Lucide React + Nerd Font (Atkinson Mono Nerd Font)
uaclaunchcontrol/
├── src/
├── main/ # Electron main process
│ ├── index.ts # Window creation, auto-updater, IPC handlers
│ └── server/ # Express API server
│ ├── index.ts # Server setup (port 7666), CORS, logging middleware
│ ├── routes.ts # All REST API endpoints
│ ├── storage.ts # JSON file-based persistence + WAD file watcher
│ └── services/
│ ├── fileService.ts # File system operations
│ └── gameService.ts # Game config management
├── preload/ # Electron preload scripts (contextBridge)
│ ├── index.ts # IPC channels for updates, file path resolution
│ └── index.d.ts # Window type declarations
├── renderer/ # React frontend
│ └── src/
│ ├── api.ts # Fetch-based API client (localhost:7666)
│ ├── App.tsx # Root: router, auto-updater, modals
│ ├── pages/ # GamesPage (/), InstallPage (/install), not-found
│ ├── components/ # Custom components (CatalogManager, GameCard, etc.)
│ │ └── ui/ # shadcn/ui primitives (40+ components)
│ ├── hooks/ # useAutoUpdater, useToast, useMobile
│ ├── lib/ # gameService, fileService, queryClient, utils
│ ├── icons/ # DoomVersionIcon + PNG assets
│ └── assets/ # Fonts, images, logos
└── shared/ # Shared TypeScript types
└── schema.ts # All interfaces: IMod, IModFile, IDoomVersion, etc.
-
Electron Main Process starts and initializes the Express API server on port
7666 -
Express Server manages JSON file storage at
~/.config/uac/:-
settings.json— App settings (paths, preferences) -
doomVersions.json— Configured Doom versions/WADs -
modFileCatalogue.json— Catalog of available mod files -
mods/— Individual mod configurations as JSON files
-
- Chokidar watches the WAD files directory for changes and syncs doom versions automatically
- Renderer Process (React app) communicates with the API server via HTTP fetch
-
Media Proxy: Images served via both
/api/media?path=and/images/:fileNameto bypass Electron'sfile://security restrictions. Screenshots stored in a dedicated images directory. -
Auto-Update: Uses
electron-updaterwith GitHub releases. IPC channels bridge main/renderer for update lifecycle. Linux falls back to GitHub API check for system-installed versions.
- Node.js 18+
- npm
- GZDoom (or similar source port) installed on your system
# Clone the repository
git clone https://github.com/mikkelrask/uaclaunchcontrol.git
cd uaclaunchcontrol
# Install dependencies
npm install# Run in development mode (with hot reload)
npm run devThis will:
- Start the Vite dev server for the renderer (with proxy to Express on port 7666)
- Start the Electron app with the Express API server
- Start the Chokidar WAD watcher for real-time sync
# TypeScript type checking
npm run typecheck
# Lint and format
npm run lint
npm run format
# Build for your current platform
npm run buildnpm run build:win # Windows
npm run build:mac # macOS
npm run build:linux # Linux
npm run build:unpack # Build + unpacked directory (for testing)Built applications will be in the dist/ directory.
Done via the application settings via the cog icon in the top right corner, but everything is also stored in plain text JSON files, so you can edit them manually if you want to. All paths support tilde (~) expansion.
All application data is stored in ~/.config/uac/:
~/.config/uac/
├── settings.json # App settings (paths, preferences)
├── doomVersions.json # Configured Doom versions/WADs
├── modFileCatalogue.json # Catalog of available mod files
├── mods/ # Individual mod configurations
│ ├── 1.json
│ ├── 2.json
│ └── ...
├── images/ # Screenshots and downloaded images
└── saves/ # Game save files (per-mod, optional)
{
"sourcePortPath": "",
"theme": "dark",
"savegamesPath": "~/.config/uac/saves",
"modsDirectory": "~/.config/uac/mods",
"screenshotsPath": "~/Pictures/UAC Launch Control/screenshots",
"wadFilesDirectory": "~/.config/uac/wads",
"autoUpdateEnabled": true,
"registryLookupEnabled": false
}The Express server exposes the following REST API on localhost:7666:
-
GET /api/mods— List all mods (optional?version=and?search=query params) -
GET /api/mods/:id— Get specific mod with its files -
POST /api/mods— Create new mod -
PUT /api/mods/:id— Update mod -
DELETE /api/mods/:id— Delete mod -
POST /api/mods/:id/launch— Launch a mod (spawns source port)
-
GET /api/versions— List all Doom versions -
PUT /api/versions— Save all Doom versions (full array replace) -
GET /api/versions/:slug— Get Doom version by slug -
PUT /api/versions/:id— Update a single Doom version -
POST /api/wads/import— Import a .wad file with MD5-based renaming
-
GET /api/mod-files/catalog— List catalog files -
POST /api/mod-files/catalog— Add file to catalog -
PUT /api/mod-files/catalog/:id— Update catalog entry -
DELETE /api/mod-files/catalog/:id— Delete catalog entry -
POST /api/mod-files/hash— Compute MD5 hash of a file -
POST /api/mod-files/move— Move file to mod folder with hash-based naming
-
GET /api/settings— Get expanded application settings -
PUT /api/settings— Update settings
-
GET /api/media?path=— Proxy for serving local files safely -
GET /images/:fileName— Serve images from the images directory -
POST /api/screenshots/upload— Upload/copy a screenshot to images dir -
POST /api/mod/download-image— Download an image from a URL -
POST /api/move-file— Move a file from one path to another -
POST /api/dialog/open— Open native file/folder picker (supports tilde)
-
GET /api/migration/check— Check for legacy config from previous versions -
POST /api/migration/execute— Execute legacy migration
Defined in electron.vite.config.ts:
-
@/— Maps tosrc/renderer/src/(e.g.@/components/ui/button) -
@renderer/— Maps tosrc/renderer/src/ -
@shared/— Maps tosrc/shared/(e.g.@shared/schema)
- TailwindCSS v3 with class-based dark mode
- Custom app utility classes:
bg-app-primary,bg-app-secondary,text-app-primary,text-app-muted,border-app,bg-accent-highlight - Font: Aldrich (sans, bundled via
@font-face), with Atkinson Mono Nerd Font as fallback for icon glyphs - UI components in
src/renderer/src/components/ui/(40+ shadcn/ui primitives)
All dialogs follow a consistent pattern modeled on SettingsDialog.tsx:
<Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}>
<DialogContent className="bg-app-primary shadow-2xl border-app max-w-4xl p-0 overflow-hidden flex flex-col">
{/* Full-bleed header bar */}
<div className="flex items-center justify-between p-4 border-b border-app bg-app-secondary">
<div className="flex items-center gap-3">
<div className="p-2 bg-accent-highlight/10 rounded-md">
<Icon className="w-5 h-5 text-accent-highlight" />
</div>
<div>
<DialogTitle className="text-xl font-bold tracking-tight text-app-primary lowercase">
title_text
</DialogTitle>
<DialogDescription className="text-xs font-semibold font-mono text-app-muted uppercase tracking-widest opacity-80">
UAC Launch Control // Description
</DialogDescription>
</div>
</div>
</div>
{/* Body */}
<div className="flex-1 overflow-y-auto p-4">...</div>
{/* Full-bleed footer bar */}
<DialogFooter className="bg-app-secondary border-t border-app p-4 shrink-0">
<Button variant="outline" onClick={onClose}>Cancel</Button>
<Button className="bg-accent-highlight text-white">Confirm</Button>
</DialogFooter>
</DialogContent>
</Dialog>Defined in Header.tsx. All single-letter shortcuts are lowercase and disabled when typing in an input/textarea:
| Key | Action |
|---|---|
/ |
Focus search input |
i |
Go to Install > Configuration tab |
m |
Go to Install > Mod Files tab |
w |
Go to Install > WAD Files tab |
l |
Go to Launch page |
Ctrl+. |
Open Settings |
? |
Show keyboard shortcuts modal |
Global shortcuts use URL query params (/install?tab=files) + custom DOM events (uac:switch-tab) since wouter only tracks pathname.
All interfaces in src/shared/schema.ts use I prefix:
- IMod — Mod/game instance (title, description, files, source port, etc.)
- IModFile — Individual mod file entry (path, hash, type, load order)
- IDoomVersion — Base game/WAD configuration (name, slug, executable, icon)
- IAppSettings — Application settings (paths, theme, toggles, database links)
- IUpdateInfo — Auto-update status (version, release notes, download progress)
- IResponseMessage — Generic API response wrapper
The project uses separate TypeScript configurations:
-
tsconfig.node.json— Main process and server code -
tsconfig.web.json— Renderer process (includessrc/sharedvia composite)
- TypeScript strict mode, files:
.ts/.tsx - No semicolons, single quotes, 100 char print width, no trailing commas (Prettier)
- PascalCase for components, camelCase for hooks (
useprefix), kebab-case for files - PascalCase with
Iprefix for types (IMod,IModFile) - Use
cn()from@/lib/utilsfor conditional class merging - Errors displayed via
useToast()hook
-
Server state: TanStack Query (
useQuery,useMutation) withstaleTime: Infinity -
Local state:
useState - Global UI: React Context (rare)
-
Query keys: Use URL paths as keys:
['/api/mods'],['/api/mod-files/catalog'],['/api/settings']
- VSCode
- Extensions:
NOTE:
The information in these wiki's can get outdated pretty quickly as Launch Control, our leading protocol launcher always is in rapid development - If you find some information that is off or doesn't match what you experience, please don't hesitate reaching out, or reporting an issue, and a UAC Support Contractor will respond promptly and update the information accordingly.
- Sam Grimm, Head of R&D - UAC Phobos