Added checksum and version checking

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
2026-03-15 21:46:33 +01:00
parent 460552378a
commit 59fc530a1a
6 changed files with 131 additions and 29 deletions

View File

@@ -1,6 +1,8 @@
import 'dart:convert';
import 'dart:typed_data';
import 'package:crypto/crypto.dart' show md5;
import 'package:wolf_3d_data/src/data_version.dart';
import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
abstract class WLParser {
@@ -21,42 +23,72 @@ abstract class WLParser {
/// Asynchronously discovers the game version and loads all necessary files.
/// Provide a [fileFetcher] callback (e.g., Flutter's rootBundle.load) that
/// takes a filename and returns its ByteData.
/// Asynchronously discovers the game version and loads all necessary files.
/// Asynchronously discovers the game version and loads all necessary files.
static Future<WolfensteinData> loadAsync(
Future<ByteData> Function(String filename) fileFetcher,
) async {
GameVersion? detectedVersion;
ByteData? vswap;
// 1. Probe the data source to figure out which version we have
// 1. Probe the data source for VSWAP to determine the GameVersion
for (final version in GameVersion.values) {
try {
vswap = await fileFetcher('VSWAP.${version.fileExtension}');
detectedVersion = version;
break; // We found the version!
} catch (_) {
// File wasn't found, try the next version extension
}
break;
} catch (_) {}
}
if (detectedVersion == null || vswap == null) {
throw Exception(
'Could not locate a valid VSWAP file for any game version.',
);
throw Exception('Could not locate a valid VSWAP file.');
}
final ext = detectedVersion.fileExtension;
// 2. Now that we know the version, confidently load the rest of the files
// 2. Determine DataIdentity (Checksum) immediately
final vswapBytes = vswap.buffer.asUint8List(
vswap.offsetInBytes,
vswap.lengthInBytes,
);
final vswapHash = md5.convert(vswapBytes).toString();
final dataIdentity = DataVersion.fromChecksum(vswapHash);
// 3. Load other required files
// Special Case: v1.0 Retail uses MAPTEMP instead of GAMEMAPS
ByteData gameMapsData;
if (dataIdentity == DataVersion.version10Retail) {
try {
gameMapsData = await fileFetcher('MAPTEMP.$ext');
} catch (_) {
// Fallback in case v1.0 files were renamed to standard convention
gameMapsData = await fileFetcher('GAMEMAPS.$ext');
}
} else {
gameMapsData = await fileFetcher('GAMEMAPS.$ext');
}
final rawFiles = {
'MAPHEAD.$ext': await fileFetcher('MAPHEAD.$ext'),
'VGADICT.$ext': await fileFetcher('VGADICT.$ext'),
'VGAHEAD.$ext': await fileFetcher('VGAHEAD.$ext'),
'VGAGRAPH.$ext': await fileFetcher('VGAGRAPH.$ext'),
'AUDIOHED.$ext': await fileFetcher('AUDIOHED.$ext'),
'AUDIOT.$ext': await fileFetcher('AUDIOT.$ext'),
};
// 4. Final call to load with the required dataIdentity
return load(
version: detectedVersion,
dataIdentity: dataIdentity, // Now correctly passed
vswap: vswap,
mapHead: await fileFetcher('MAPHEAD.$ext'),
gameMaps: await fileFetcher('GAMEMAPS.$ext'),
vgaDict: await fileFetcher('VGADICT.$ext'),
vgaHead: await fileFetcher('VGAHEAD.$ext'),
vgaGraph: await fileFetcher('VGAGRAPH.$ext'),
audioHed: await fileFetcher('AUDIOHED.$ext'),
audioT: await fileFetcher('AUDIOT.$ext'),
mapHead: rawFiles['MAPHEAD.$ext']!,
gameMaps: gameMapsData,
vgaDict: rawFiles['VGADICT.$ext']!,
vgaHead: rawFiles['VGAHEAD.$ext']!,
vgaGraph: rawFiles['VGAGRAPH.$ext']!,
audioHed: rawFiles['AUDIOHED.$ext']!,
audioT: rawFiles['AUDIOT.$ext']!,
);
}
@@ -73,9 +105,15 @@ abstract class WLParser {
required ByteData vgaGraph,
required ByteData audioHed,
required ByteData audioT,
required DataVersion dataIdentity,
}) {
final isShareware = version == GameVersion.shareware;
// v1.0/1.1 used different HUD strings and had different secret wall bugs
final isLegacy =
dataIdentity == DataVersion.version10Retail ||
dataIdentity == DataVersion.version11Retail;
final audio = parseAudio(audioHed, audioT, version);
return WolfensteinData(