From 655d3e83cde2528da66a9d316856f90a19e849f4 Mon Sep 17 00:00:00 2001 From: David Alvarez Date: Wed, 27 May 2026 09:19:56 +0200 Subject: [PATCH] fix: full dosbox.conf with usescancodes=true + auto WASD for Doom MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause: Dosbox.conf was missing the [sdl] section with 'usescancodes=true' — without it DOSBox doesn't translate keyboard scan codes and WASD keys don't register as game input. Fix: - Generate a complete dosbox.conf matching js-dos v8 standards (all sections: sdl, dosbox, cpu, mixer, render, midi, sblaster, gus, speaker, joystick, serial, dos, ipx, autoexec) - Key setting: usescancodes=true + mapperfile=mapper-jsdos.map - Uses svga_s3 machine type with SB16 audio (same as dos.zone) Bonus: Auto-detect Doom-engine games (DOOM.WAD / DOOM2.WAD) and inject a DEFAULT.CFG with WASD controls: W=forward, S=backward, A=strafe-left, D=strafe-right Arrows=turn, Ctrl=fire, Space=use, Alt=strafe, Shift=run Mouse enabled for looking Note: Existing uploaded games still have the old dosbox.conf. They need to be re-uploaded to get the fix. --- .../java/com/dostalgia/UploadResource.java | 182 +++++++++++++++++- 1 file changed, 178 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/dostalgia/UploadResource.java b/src/main/java/com/dostalgia/UploadResource.java index 92b6c5f..59fef2b 100644 --- a/src/main/java/com/dostalgia/UploadResource.java +++ b/src/main/java/com/dostalgia/UploadResource.java @@ -83,11 +83,13 @@ public class UploadResource { if ("sockdrive".equals(bundleType)) { // Copy files loose copyDir(extractDir, gameDir); - // Write dosbox.conf + // Auto-inject WASD config for Doom-engine games + injectDoomConfig(extractDir, gameDir); + // Write dosbox.conf (full config, not just autoexec) Path jsdos = gameDir.resolve(".jsdos"); Files.createDirectories(jsdos); String relExe = Path.of(mainExe).getFileName().toString(); - String conf = "[autoexec]\n@echo off\nmount c .\nc:\ncls\n" + relExe + "\n"; + String conf = buildDosboxConf(relExe); Files.writeString(jsdos.resolve("dosbox.conf"), conf); bundleFile = ".jsdos/dosbox.conf"; } else { @@ -207,12 +209,14 @@ public class UploadResource { Path tmpBundle = Files.createTempDirectory("dostalgia-bundle-"); try { copyDir(extractDir, tmpBundle); + // Auto-inject WASD config for Doom-engine games + injectDoomConfig(extractDir, tmpBundle); - // Create .jsdos config + // Create .jsdos config (full config, not just autoexec) Path jsdos = tmpBundle.resolve(".jsdos"); Files.createDirectories(jsdos); String relExe = Path.of(mainExe).getFileName().toString(); - String conf = "[autoexec]\n@echo off\nmount c .\nc:\ncls\n" + relExe + "\n"; + String conf = buildDosboxConf(relExe); Files.writeString(jsdos.resolve("dosbox.conf"), conf); // ZIP it up @@ -235,6 +239,176 @@ public class UploadResource { // ─── File Utilities ────────────────────────────────────────── + /** + * Build a complete dosbox.conf with all sections needed for proper + * keyboard and hardware emulation in js-dos v8. + */ + private String buildDosboxConf(String relExe) { + return "[sdl]\n" + + "autolock=true\n" + + "fullscreen=false\n" + + "fulldouble=false\n" + + "fullresolution=original\n" + + "windowresolution=original\n" + + "output=surface\n" + + "sensitivity=100\n" + + "waitonerror=true\n" + + "priority=higher,normal\n" + + "mapperfile=mapper-jsdos.map\n" + + "usescancodes=true\n" + + "\n" + + "[dosbox]\n" + + "machine=svga_s3\n" + + "memsize=16\n" + + "\n" + + "[cpu]\n" + + "core=auto\n" + + "cputype=auto\n" + + "cycles=auto\n" + + "\n" + + "[mixer]\n" + + "nosound=false\n" + + "rate=44100\n" + + "blocksize=1024\n" + + "prebuffer=20\n" + + "\n" + + "[render]\n" + + "frameskip=0\n" + + "aspect=false\n" + + "scaler=none\n" + + "\n" + + "[midi]\n" + + "mpu401=intelligent\n" + + "mididevice=default\n" + + "midiconfig=\n" + + "\n" + + "[sblaster]\n" + + "sbtype=sb16\n" + + "sbbase=220\n" + + "irq=7\n" + + "dma=1\n" + + "hdma=5\n" + + "sbmixer=true\n" + + "oplmode=auto\n" + + "oplemu=default\n" + + "oplrate=44100\n" + + "\n" + + "[gus]\n" + + "gus=false\n" + + "gusrate=44100\n" + + "gusbase=240\n" + + "gusirq=5\n" + + "gusdma=3\n" + + "ultradir=C:\\ULTRASND\n" + + "\n" + + "[speaker]\n" + + "pcspeaker=true\n" + + "pcrate=44100\n" + + "tandy=auto\n" + + "tandyrate=44100\n" + + "disney=true\n" + + "\n" + + "[joystick]\n" + + "joysticktype=auto\n" + + "timed=true\n" + + "autofire=false\n" + + "swap34=false\n" + + "buttonwrap=false\n" + + "\n" + + "[serial]\n" + + "serial1=dummy\n" + + "serial2=dummy\n" + + "serial3=disabled\n" + + "serial4=disabled\n" + + "\n" + + "[dos]\n" + + "xms=true\n" + + "ems=true\n" + + "umb=true\n" + + "keyboardlayout=auto\n" + + "\n" + + "[ipx]\n" + + "ipx=true\n" + + "\n" + + "[autoexec]\n" + + "@echo off\n" + + "mount c .\n" + + "c:\n" + + "cls\n" + + relExe + "\n"; + } + + /** + * Detect Doom-engine games and inject a DEFAULT.CFG with WASD controls. + * Checks for DOOM.WAD or DOOM2.WAD in the source, then writes the + * config to the destination if it doesn't already have one. + */ + private void injectDoomConfig(Path srcDir, Path dstDir) throws IOException { + boolean hasDoom = false; + try (var stream = Files.list(srcDir)) { + for (Path f : (Iterable) stream::iterator) { + String name = f.getFileName().toString().toUpperCase(); + if (name.equals("DOOM.WAD") || name.equals("DOOM2.WAD")) { + hasDoom = true; + break; + } + } + } + if (!hasDoom) return; + // Don't overwrite an existing config + Path cfg = dstDir.resolve("DEFAULT.CFG"); + if (Files.exists(cfg)) return; + Files.write(cfg, buildDoomDefaultCfg()); + LOG.info("Doom detected — injected DEFAULT.CFG with WASD controls"); + } + + /** + * Build a DEFAULT.CFG (Doom engine config) with WASD controls. + * Only called when a DOOM.WAD or DOOM2.WAD is detected in the upload. + * + * Key bindings: + * W=forward, S=backward, A=strafe left, D=strafe right + * Arrows=turn, Ctrl=fire, Space=use, Alt=strafe, Shift=run + * Mouse enabled for looking + */ + private byte[] buildDoomDefaultCfg() { + String cfg = "" + + "mouse_sensitivity\t\t5\n" + + "sfx_volume\t\t8\n" + + "music_volume\t\t8\n" + + "show_messages\t\t1\n" + + "key_right\t\t77\n" // Right arrow — turn right + + "key_left\t\t75\n" // Left arrow — turn left + + "key_up\t\t17\n" // W — forward + + "key_down\t\t31\n" // S — backward + + "key_strafeleft\t\t30\n" // A — strafe left + + "key_straferight\t\t32\n" // D — strafe right + + "key_fire\t\t29\n" // Ctrl — fire + + "key_use\t\t57\n" // Space — use/open + + "key_strafe\t\t56\n" // Alt — strafe (hold) + + "key_speed\t\t54\n" // Shift — run + + "use_mouse\t\t1\n" + + "mouseb_fire\t\t0\n" + + "mouseb_strafe\t\t1\n" + + "mouseb_forward\t\t2\n" + + "use_joystick\t\t0\n" + + "joyb_fire\t\t0\n" + + "joyb_strafe\t\t1\n" + + "joyb_use\t\t3\n" + + "joyb_speed\t\t2\n" + + "screenblocks\t\t10\n" + + "detaillevel\t\t0\n" + + "showmessages\t\t1\n" + + "comport\t\t1\n" + + "snd_channels\t\t3\n" + + "snd_musicdevice\t\t3\n" + + "snd_sfxdevice\t\t3\n" + + "snd_sbport\t\t544\n" + + "snd_sbirq\t\t7\n" + + "snd_sbdma\t\t1\n"; + return cfg.getBytes(java.nio.charset.StandardCharsets.US_ASCII); + } + private double dirSizeMB(Path dir) throws IOException { long[] total = {0}; Files.walkFileTree(dir, new SimpleFileVisitor<>() {