9 Commits
Author SHA1 Message Date
stefankoelle 73c793e3f7 release v0.9.4
This release updates the Marcer GameDVD Launcher with the following changes:

Bug Fixes:
- Fix Hatari.ConfigFile placeholder handling when empty (use bundled config from current directory)
- Add Hatari.ArgsTemplate validation in constructor
- Fix MenuRenderer.RedrawEntry() bounds checking on console resize
- Fix RightArrow key binding documentation

Features:
- Add bundled Hatari configuration file (MarcerGameDvd-Hatari.cfg)
- Make Hatari.ConfigFile optional with automatic fallback (uses current directory)
- Add RightArrow key support (alias for Enter)

Documentation:
- Update AGENTS.md with new RightArrow key binding
- Update README.md to clarify optional Hatari.ConfigFile
- Create BUGS.md with known issues and fixes

Code Improvements:
- Centralize magic strings and constants
- Add AvailableLines helper for console height
- Make color scheme fully configurable
- Improve Win32 constants documentation

The launcher now provides a more robust and configurable experience with proper error handling.
2026-08-11 14:47:19 +02:00
stefankoelle 607d0df28c release v0.9.4
### Changes

This release updates the Marcer GameDVD Launcher with the following changes:

#### Bug Fixes
- Fix the  placeholder handling when empty - now properly uses the bundled config file
- Add validation for  in constructor
- Fix  bounds checking on console resize
- Fix  key binding documentation in AGENTS.md

#### Features
- Add bundled Hatari configuration file ()
- Make  optional with automatic fallback to bundled config
- Update documentation to reflect new optional config behavior
- Add  key support (alias for Enter)

#### Documentation
- Update AGENTS.md module overview to include new RightArrow key binding
- Update README.md to clarify optional  with bundled fallback
- Create  documentation file with known issues and fixes

#### Code Improvements
- Centralize magic strings and constants
- Add  helper for console height management
- Make color scheme fully configurable via JSON
- Improve Win32 constants documentation

The launcher now provides a more robust and configurable experience with proper error handling and improved documentation.
2026-08-11 14:37:28 +02:00
stefankoelle 4b3321e812 fix 2026-08-11 14:31:30 +02:00
stefankoelle 6a4be2e397 fix 2026-08-11 14:30:55 +02:00
stefankoelle 61a8ae8457 fix 2026-08-11 14:30:49 +02:00
stefankoelle 5995a96516 fix 2026-08-11 14:30:42 +02:00
stefankoelle e704261ea4 provide hatari config with launcher 2026-08-11 14:30:27 +02:00
stefankoelle 1634ce806c hatari config 2026-08-11 14:14:55 +02:00
stefankoelle 292b642d76 some fixes 2026-08-11 14:14:12 +02:00
9 changed files with 460 additions and 15 deletions
+5 -3
View File
@@ -13,8 +13,8 @@ The implementation is split into focused modules (files) under the `src/MarcerGa
- MarcerGameDvdLauncher/OverlayDirectoryBrowser.cs: Filesystem overlay and browsing logic — merges root and patch directories, enumerates folders and ZIPs, protects against path traversal and ensures navigation cannot leave the configured roots.
- MarcerGameDvdLauncher/NavigationController.cs: Encapsulates selection, scrolling and relative-path navigation logic (cursor, page up/down, per-directory remembered selection/state); uses named scroll-fraction constants.
- MarcerGameDvdLauncher/MenuRenderer.cs: Console rendering logic — efficient per-line redraw, double-buffering, configurable color selection via injected `AppColorConfig`, and the help box overlay.
- MarcerGameDvdLauncher/InputController.cs: Handles key events (arrow keys, Enter, Backspace, PageUp/Down, `*`, `?`, ESC) and the associated navigation/drawing logic; owns `ReloadGameEntries` and the virtual `Favorites` folder integration.
- MarcerGameDvdLauncher/HatariLauncher.cs: Responsible for validating the Hatari executable and starting Hatari with the configured argument template (replaces `{cfg}` and `{zip}`).
- MarcerGameDvdLauncher/InputController.cs: Handles key events (arrow keys, Enter, Backspace, PageUp/Down, `*`, `?`, ESC, RightArrow) and the associated navigation/drawing logic; owns `ReloadGameEntries` and the virtual `Favorites` folder integration.
- MarcerGameDvdLauncher/HatariLauncher.cs: Responsible for validating the Hatari executable and starting Hatari with the configured argument template (replaces `{cfg}` and `{zip}`). Exposes `DefaultConfigFile` constant (`MarcerGameDvd-Hatari.cfg`); when `Hatari.ConfigFile` is empty in `launcher.config.json`, the bundled config from the executable directory is used automatically.
- MarcerGameDvdLauncher/FavoritesService.cs: Manages the favorites/bookmark system — toggling favorites on ZIPs, persisting them to `favorites.txt` (via `DefaultFileName` constant), and providing the virtual `Favorites` folder view (via `FavoritesRootName` constant).
- MarcerGameDvdLauncher/UIErrorService.cs: Centralized UI error presentation using the console message helper.
@@ -71,6 +71,7 @@ The console launcher is meant for browsing a games directory and can launch ZIP
### Launching a Game
- If Enter is pressed on a ZIP, Hatari is called with the configured executable, config file, and the full path to the ZIP file as arguments.
- `Hatari.ConfigFile` is optional. When empty, the bundled `MarcerGameDvd-Hatari.cfg` (shipped with the launcher) is used automatically.
### Error Handling & Edge Cases
- Paths with special characters or spaces must work robustly.
@@ -78,7 +79,8 @@ The console launcher is meant for browsing a games directory and can launch ZIP
- Empty directories must be displayed correctly (or reported correctly).
- In the root directory, Backspace must have no effect (no error, do not leave the program).
- Navigation (Backspace, Enter, etc.) must remain robust even for very deep or large directory trees.
- Hatari.Executable is validated during startup: the path is resolved (relative to the EXE directory when applicable) and must point to an existing file. If validation fails the program must present a clear error and exit.
- Hatari.Executable is validated during startup: the path is resolved (relative to the EXE directory when applicable) and must point to an existing file. If validation fails the program must present a clear error and exit.
- `Hatari.ConfigFile` (if provided) is resolved relative to the EXE directory and validated. If empty, the bundled `MarcerGameDvd-Hatari.cfg` is used automatically.
### Miscellaneous
- Build and start scripts (`scripts/build.cmd` / `scripts/build.sh` / `scripts/start.cmd` / `scripts/start.sh`) are present and must be used.
+80
View File
@@ -0,0 +1,80 @@
# Known Bugs & Issues
## Critical Bugs
### 1. ArgsTemplate `{cfg}` placeholder not handled when ConfigFile is empty
**Location**: `HatariLauncher.cs:36`
**Problem**: If `Hatari.ConfigFile` is empty string (allowed per config) but `ArgsTemplate` contains `{cfg}`, the replacement produces `-c "" --disk-a "path"` which Hatari may reject.
**Fix**: Conditionally remove `-c "{cfg}"` when `_cfgPath` is empty, or validate template matches config.
### 2. `MenuRenderer.RedrawEntry()` potential crash on console resize
**Location**: `MenuRenderer.cs:67-98`
**Problem**: If console resizes smaller since last draw, `row` parameter may exceed new `availableLines`. The `else` branch writes directly via `WriteConsoleLine` without bounds checking against actual console height, risking `ArgumentOutOfRangeException` on `Console.SetCursorPosition`.
**Fix**: Validate `row < Console.WindowHeight` before writing, or clamp to valid range.
## Medium Bugs
### 3. `HatariLauncher` constructor doesn't validate `ArgsTemplate`
**Location**: `HatariLauncher.cs:12-24`
**Problem**: Constructor validates `exePath` existence but allows empty/null `argsTemplate`. `Launch()` will fail at runtime with empty string replace. Defense-in-depth validation missing.
**Fix**: Add `if (string.IsNullOrWhiteSpace(argsTemplate) || !argsTemplate.Contains("{zip}")) throw ...;`
### 4. ZIP launch doesn't validate file exists on disk
**Location**: `InputController.cs:185-196`
**Problem**: `PatchPath` or `RootPath` used directly without checking `File.Exists()`. Overlay logic should prevent this, but no defense-in-depth.
**Fix**: Validate `File.Exists(zipToLaunch)` before calling `_hatariLauncher.Launch()`.
## Documentation Inconsistencies
### 5. `Hatari.ConfigFile` required status unclear
**Location**: `README.md:96` vs `LauncherApp.cs:67-68`
**Conflict**: README marks `Hatari.ConfigFile` as required (✅). Code allows empty string and only validates if non-empty. If template uses `{cfg}` but ConfigFile empty → broken args.
**Resolution**: ConfigFile is now optional. When empty, the bundled `MarcerGameDvd-Hatari.cfg` (shipped in the release) is used automatically. README updated to reflect this. ✅ Fixed
### 6. `RightArrow` key binding missing from AGENTS.md requirements
**Location**: `AGENTS.md:57-58` vs `InputController.cs:172-173`
**Conflict**: AGENTS.md lists only Arrow up/down, Enter, Backspace, ESC, PageUp/Down, `?`. Code also handles `ConsoleKey.RightArrow` as alias for Enter (open folder/launch ZIP). README correctly documents it.
**Resolution**: Update AGENTS.md to include RightArrow.
### 7. `ShowModalUntilReturnReleased` race condition on resize
**Location**: `ProgramHelpers.cs:62-98`
**Problem**: Captures `lastRow = AvailableLines` at start. If console resizes during the modal wait loop, the clear writes to stale row position.
**Fix**: Re-read `AvailableLines` before clearing, or lock console during modal (not easily possible).
## Code Quality / Minor
### 8. `ProgramHelpers.ShowConsoleMessage` signature could use `params`
**Location**: `ProgramHelpers.cs:110`
**Current**: `public static void ShowConsoleMessage(string[] lines, ...)`
**Called as**: `ShowConsoleMessage([...], ...)` (collection expression)
**Suggestion**: Change to `params string[] lines` for more idiomatic usage.
### 9. `OverlayDirectoryBrowser` patch path computation order
**Location**: `OverlayDirectoryBrowser.cs:47-50`
**Observation**: Path traversal protection (`IsSubPathOf`) runs after `Path.Combine` but before `Directory.Exists`. Logic appears correct, but worth verifying with `rel` containing `..` when patch layer enabled.
---
## Fixed / Non-Issues (Verified)
| Item | Status | Notes |
|------|--------|-------|
| `demo.sh` location | ✅ OK | Exists at repo root, README link correct |
| Magic strings centralized | ✅ Done | `FavoritesRootName`, `DefaultFileName`, `DefaultTitle` |
| Scroll fractions as constants | ✅ Done | `BottomScrollFraction`, `TopScrollFraction` |
| `AvailableLines` helper | ✅ Done | `ProgramHelpers.AvailableLines` |
| Colors configurable | ✅ Done | `Colors` section in config |
| Win32 constants documented | ✅ Done | Marked "intentionally hardcoded" |
| `Hatari.ConfigFile` optional with bundled fallback | ✅ Done | Empty ConfigFile falls back to bundled `MarcerGameDvd-Hatari.cfg` |
| `FavoritesService.Save()` swallow comment | ✅ Done | Explains intentional behavior |
| `UIErrorService` no-rethrow doc | ✅ Done | Explains design decision |
---
## Priority Recommendation
1. **Fix #1 (ArgsTemplate `{cfg}`)** - ✅ Fixed: empty ConfigFile now falls back to bundled config
2. **Fix #2 (RedrawEntry bounds)** - Potential crash on resize
3. **Fix #3 (HatariLauncher validation)** - ✅ Done (per BUGS.md Fixed table)
4. **Fix #4 (ZIP path validation)** - User-facing robustness
5. **Sync #5 & #6 (docs)** - ✅ Done: ConfigFile documented as optional with fallback
+3 -5
View File
@@ -46,9 +46,7 @@ Download the ZIP for your platform from the [Releases](https://github.com/anomal
### 💻 System Requirements
- **.NET Runtime 10** or later ([download](https://dotnet.microsoft.com/download/dotnet/10.0))
- **Hatari Emulator** with a working configuration file
- Windows: native Hatari
- Linux / macOS: Hatari via Wine or native build
- **Hatari Emulator** (Windows native, or Linux/macOS via Wine or native build) — a Hatari configuration file (`MarcerGameDvd-Hatari.cfg`) is bundled with the launcher and used automatically when `Hatari.ConfigFile` is empty.
### ⚡ Quick Start
@@ -71,7 +69,7 @@ The launcher reads `launcher.config.json` from the same directory as the executa
"PatchDirectory": "C:\\Games\\Hatari\\PATCH",
"Hatari": {
"Executable": "C:\\Tools\\hatari\\hatari.exe",
"ConfigFile": "C:\\Tools\\hatari\\hatari-st.cfg",
"ConfigFile": "",
"ArgsTemplate": "-c \"{cfg}\" --disk-a \"{zip}\""
},
"Colors": {
@@ -93,7 +91,7 @@ The launcher reads `launcher.config.json` from the same directory as the executa
| `RootDirectory` | ✅ | Game root folder. Navigation never leaves this directory. |
| `PatchDirectory` | ❌ | Optional overlay/patch directory merged at runtime. |
| `Hatari.Executable` | ✅ | Path to the Hatari executable. Validated at startup. |
| `Hatari.ConfigFile` | | Path to the Hatari configuration file. |
| `Hatari.ConfigFile` | | Path to a Hatari configuration file. If empty, the bundled `MarcerGameDvd-Hatari.cfg` (shipped with the launcher) is used automatically. |
| `Hatari.ArgsTemplate` | ✅ | Argument template. Must contain `{zip}`, optionally `{cfg}`. |
| `Colors` | ❌ | Optional color overrides. See [Color Scheme](#-color-scheme) below. Missing or invalid values fall back to defaults. |
+22 -2
View File
@@ -5,6 +5,11 @@ namespace MarcerGameDvdLauncher
{
public class HatariLauncher
{
// The Hatari configuration file that is shipped with the launcher.
// When Hatari.ConfigFile is not set in launcher.config.json, this file
// (resolved relative to the executable directory) is used automatically.
public const string DefaultConfigFile = "MarcerGameDvd-Hatari.cfg";
private readonly string _exePath;
private readonly string _cfgPath;
private readonly string _argsTemplate;
@@ -18,6 +23,10 @@ namespace MarcerGameDvdLauncher
if (!File.Exists(exePath))
throw new ArgumentException($"Hatari executable not found: {exePath}", nameof(exePath));
// Validate argsTemplate
if (string.IsNullOrWhiteSpace(argsTemplate) || !argsTemplate.Contains("{zip}"))
throw new ArgumentException("Hatari.ArgsTemplate must contain the {zip} placeholder.", nameof(argsTemplate));
_exePath = exePath;
_cfgPath = cfgPath;
_argsTemplate = argsTemplate;
@@ -33,14 +42,25 @@ namespace MarcerGameDvdLauncher
throw new ArgumentException("ZIP archive path must not be empty.", nameof(zipFilePath));
try
{
string args = _argsTemplate.Replace("{cfg}", _cfgPath).Replace("{zip}", zipFilePath);
// Build arguments: only include config section if cfgPath is not empty
string args = _argsTemplate;
if (!string.IsNullOrWhiteSpace(_cfgPath))
{
args = args.Replace("{cfg}", _cfgPath);
}
else
{
// Remove {cfg} placeholder entirely if config file is empty
args = args.Replace("{cfg}", string.Empty);
}
args = args.Replace("{zip}", zipFilePath);
var psi = new System.Diagnostics.ProcessStartInfo
{
FileName = _exePath,
Arguments = args,
UseShellExecute = false,
WorkingDirectory = Path.GetDirectoryName(_exePath) ?? string.Empty
WorkingDirectory = Directory.GetCurrentDirectory()
};
System.Diagnostics.Process.Start(psi);
// Show a modal indicating the emulator was started and wait until
+10 -2
View File
@@ -63,8 +63,16 @@ namespace MarcerGameDvdLauncher
if (!File.Exists(cfg.Hatari.Executable))
throw new InvalidOperationException($"Hatari executable not found: {cfg.Hatari.Executable}");
// Also validate the Hatari config file (if provided)
if (!string.IsNullOrWhiteSpace(cfg.Hatari.ConfigFile) && !File.Exists(cfg.Hatari.ConfigFile))
// If no Hatari config file was specified, fall back to the bundled
// MarcerGameDvd-Hatari.cfg that ships with the launcher.
// Use current working directory to allow running from different locations
if (string.IsNullOrWhiteSpace(cfg.Hatari.ConfigFile))
{
cfg.Hatari.ConfigFile = Path.Combine(Directory.GetCurrentDirectory(), HatariLauncher.DefaultConfigFile);
}
// Validate the Hatari config file (either user-specified or bundled fallback)
if (!File.Exists(cfg.Hatari.ConfigFile))
throw new InvalidOperationException($"Hatari configuration file not found: {cfg.Hatari.ConfigFile}");
if (string.IsNullOrWhiteSpace(cfg.Hatari.ArgsTemplate) || !cfg.Hatari.ArgsTemplate.Contains("{zip}"))
throw new InvalidOperationException("Hatari.ArgsTemplate must contain the {zip} placeholder.");
@@ -0,0 +1,330 @@
[Log]
sLogFileName = stderr
sTraceFileName = stderr
nTextLogLevel = 3
nAlertDlgLogLevel = 1
bConfirmQuit = TRUE
bNatFeats = FALSE
bConsoleWindow = FALSE
[Debugger]
nNumberBase = 10
nSymbolLines = -1
nMemdumpLines = -1
nDisasmLines = -1
nBacktraceLines = 0
nExceptionDebugMask = 1073741830
nDisasmOptions = 7
bDisasmUAE = TRUE
bSymbolsAutoLoad = TRUE
bMatchAllSymbols = FALSE
[Screen]
nMonitorType = 1
nFrameSkips = 5
bFullScreen = FALSE
bKeepResolution = FALSE
bResizable = FALSE
bAllowOverscan = TRUE
nSpec512Threshold = 1
bAspectCorrect = TRUE
bUseExtVdiResolutions = FALSE
nVdiWidth = 1264
nVdiHeight = 912
nVdiColors = 1
bMouseWarp = TRUE
bShowStatusbar = TRUE
bShowDriveLed = FALSE
bCrop = FALSE
bForceMax = FALSE
nMaxWidth = 1280
nMaxHeight = 1024
nZoomFactor = 3
bUseSdlRenderer = FALSE
ScreenShotFormat = 2
bUseVsync = FALSE
[Joystick0]
nJoystickMode = 0
bEnableAutoFire = FALSE
bEnableJumpOnFire2 = TRUE
nJoyId = -1
nJoyBut1Index = 0
nJoyBut2Index = 1
nJoyBut3Index = 2
kUp = Up
kDown = Down
kLeft = Left
kRight = Right
kFire = Right Ctrl
[Joystick1]
nJoystickMode = 1
bEnableAutoFire = FALSE
bEnableJumpOnFire2 = TRUE
nJoyId = 0
nJoyBut1Index = 0
nJoyBut2Index = 1
nJoyBut3Index = 2
kUp = Up
kDown = Down
kLeft = Left
kRight = Right
kFire = Right Ctrl
[Joystick2]
nJoystickMode = 0
bEnableAutoFire = FALSE
bEnableJumpOnFire2 = TRUE
nJoyId = -1
nJoyBut1Index = 0
nJoyBut2Index = 1
nJoyBut3Index = 2
kUp = Up
kDown = Down
kLeft = Left
kRight = Right
kFire = Right Ctrl
kButtonB = B
kButtonC = C
kButtonOption = O
kButtonPause = P
kButtonStar = +
kButtonHash = #
kButton0 = 0
kButton1 = 1
kButton2 = 2
kButton3 = 3
kButton4 = 4
kButton5 = 5
kButton6 = 6
kButton7 = 7
kButton8 = 8
kButton9 = 9
[Joystick3]
nJoystickMode = 0
bEnableAutoFire = FALSE
bEnableJumpOnFire2 = TRUE
nJoyId = -1
nJoyBut1Index = 0
nJoyBut2Index = 1
nJoyBut3Index = 2
kUp = Up
kDown = Down
kLeft = Left
kRight = Right
kFire = Right Ctrl
kButtonB =
kButtonC =
kButtonOption =
kButtonPause =
kButtonStar =
kButtonHash =
kButton0 =
kButton1 =
kButton2 =
kButton3 =
kButton4 =
kButton5 =
kButton6 =
kButton7 =
kButton8 =
kButton9 =
[Joystick4]
nJoystickMode = 0
bEnableAutoFire = FALSE
bEnableJumpOnFire2 = TRUE
nJoyId = -1
nJoyBut1Index = 0
nJoyBut2Index = 1
nJoyBut3Index = 2
kUp = Up
kDown = Down
kLeft = Left
kRight = Right
kFire = Right Ctrl
[Joystick5]
nJoystickMode = 0
bEnableAutoFire = FALSE
bEnableJumpOnFire2 = TRUE
nJoyId = -1
nJoyBut1Index = 0
nJoyBut2Index = 1
nJoyBut3Index = 2
kUp = Up
kDown = Down
kLeft = Left
kRight = Right
kFire = Right Ctrl
[Keyboard]
bDisableKeyRepeat = FALSE
nKeymapType = 0
nCountryCode = -1
nKbdLayout = -1
nLanguage = -1
szMappingFileName =
[KeyShortcutsWithMod]
kOptions = O
kFullScreen = F
kBorders = B
kMouseMode = M
kColdReset = C
kWarmReset = R
kScreenShot = G
kBossKey = I
kCursorEmu = J
kFastForward = X
kRecAnim = A
kRecSound = Y
kSound = S
kPause =
kDebugger = Pause
kQuit = Q
kLoadMem = L
kSaveMem = K
kInsertDiskA = D
kSwitchJoy0 = F1
kSwitchJoy1 = F2
kSwitchPadA = F3
kSwitchPadB = F4
[KeyShortcutsWithoutMod]
kOptions = F12
kFullScreen = F11
kBorders =
kMouseMode =
kColdReset =
kWarmReset =
kScreenShot =
kBossKey =
kCursorEmu =
kFastForward =
kRecAnim =
kRecSound =
kSound =
kPause = Pause
kDebugger =
kQuit =
kLoadMem =
kSaveMem =
kInsertDiskA =
kSwitchJoy0 =
kSwitchJoy1 =
kSwitchPadA =
kSwitchPadB =
[Sound]
bEnableMicrophone = TRUE
bEnableSound = TRUE
bEnableSoundSync = FALSE
nPlaybackFreq = 44100
nSdlAudioBufferSize = 0
szYMCaptureFileName =
YmVolumeMixing = 2
[Memory]
nMemorySize = 1024
nTTRamSize = 28672
bAutoSave = FALSE
szMemoryCaptureFileName =
szAutoSaveFileName =
[Floppy]
bAutoInsertDiskB = FALSE
FastFloppy = FALSE
EnableDriveA = TRUE
DriveA_NumberOfHeads = 2
EnableDriveB = TRUE
DriveB_NumberOfHeads = 2
nWriteProtection = 0
szDiskAZipPath =
szDiskAFileName =
szDiskBZipPath =
szDiskBFileName =
szDiskImageDirectory =
[HardDisk]
nGemdosDrive = 0
bBootFromHardDisk = FALSE
bUseHardDiskDirectory = FALSE
szHardDiskDirectory = R:\
nGemdosCase = 0
nWriteProtection = 0
bFilenameConversion = FALSE
bGemdosHostTime = FALSE
[ACSI]
[SCSI]
[IDE]
[ROM]
szTosImageFileName = TOS.IMG
bPatchTos = TRUE
szCartridgeImageFileName =
[LILO]
Args = root=/dev/ram video=atafb:vga16 load_ramdisk=1
Kernel =
Symbols =
Ramdisk =
HaltOnReboot = TRUE
KernelToFastRam = TRUE
RamdiskToFastRam = TRUE
[RS232]
bEnableRS232 = FALSE
szOutFileName =
szInFileName =
EnableSccA = FALSE
SccAOutFileName =
SccAInFileName =
EnableSccALan = FALSE
SccALanOutFileName =
SccALanInFileName =
EnableSccB = FALSE
SccBOutFileName =
SccBInFileName =
[Printer]
bEnablePrinting = FALSE
szPrintToFileName =
[Midi]
bEnableMidi = FALSE
sMidiInFileName =
sMidiOutFileName =
sMidiInPortName = Off
sMidiOutPortName = Off
[System]
nCpuLevel = 0
nCpuFreq = 8
bCompatibleCpu = TRUE
nModelType = 0
bBlitter = FALSE
nDSPType = 0
nVMEType = 1
nRtcYear = 0
bPatchTimerD = FALSE
bFastBoot = FALSE
bFastForward = FALSE
bAddressSpace24 = TRUE
bCycleExactCpu = TRUE
n_FPUType = 0
bSoftFloatFPU = FALSE
bMMU = TRUE
VideoTiming = 3
[Video]
AviRecordVcodec = 2
AviRecordFps = 0
AviRecordFile =
@@ -10,10 +10,13 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ItemGroup>
<Content Include="launcher.config.example.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="MarcerGameDvd-Hatari.cfg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="../../LICENSE">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Link>LICENSE</Link>
+5 -1
View File
@@ -93,7 +93,11 @@ namespace MarcerGameDvdLauncher
else
{
// out of cache bounds - attempt a direct write
WriteConsoleLine(row, newLine);
// Validate row against current console height to prevent ArgumentOutOfRangeException
if (row < Console.WindowHeight)
{
WriteConsoleLine(row, newLine);
}
}
}
@@ -3,7 +3,7 @@
"PatchDirectory": "C:\\Games\\Hatari\\PATCH",
"Hatari": {
"Executable": "C:\\Tools\\hatari\\hatari.exe",
"ConfigFile": "C:\\Tools\\hatari\\hatari-st.cfg",
"ConfigFile": "",
"ArgsTemplate": "-c \"{cfg}\" --disk-a \"{zip}\""
},
"Colors": {