Skip to content

Commit b2c33aa

Browse files
committed
dsda: initial support for boom demos
parser vastly rewritten to match upstream TODO: fix sync
1 parent d97fe55 commit b2c33aa

File tree

1 file changed

+207
-37
lines changed

1 file changed

+207
-37
lines changed

src/BizHawk.Client.Common/movie/import/DoomLmpImport.cs

Lines changed: 207 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -7,54 +7,217 @@ namespace BizHawk.Client.Common
77
{
88
// LMP file format: https://doomwiki.org/wiki/Demo#Technical_information
99
// In better detail, from archive.org: http://web.archive.org/web/20070630072856/http://demospecs.planetquake.gamespy.com/lmp/lmp.html
10+
// https://www.doomworld.com/forum/topic/120007-specifications-for-source-port-demo-formats
1011
[ImporterFor("Doom", ".lmp")]
1112
internal class DoomLmpImport : MovieImporter
1213
{
14+
private enum DemoVersion : int
15+
{
16+
Skill_1 = 0,
17+
Skill_5 = 4,
18+
Doom_1_4 = 104, // first Doom to write version to demo
19+
Doom_1_666 = 106,
20+
Doom_1_9 = 109, // Doom/Doom2/Ultimate/Final
21+
TASDoom = 110,
22+
DoomClassic = 111, // first longtics support
23+
Boom_2_00 = 200,
24+
Boom_2_01 = 201,
25+
Boom_2_02 = 202,
26+
MBF = 203, // LxDoom/MBF
27+
PrBoom_2_1_0 = 210,
28+
// this matching looks weird but it's how DSDA-Doom parses them
29+
PrBoom_2_2_x = 211,
30+
PrBoom_2_3_x = 212,
31+
PrBoom_2_4_0 = 213,
32+
PrBoomPlus = 214,
33+
MBF21 = 221,
34+
}
35+
1336
protected override void RunImport()
1437
{
1538
var input = SourceFile.OpenRead().ReadAllBytes();
1639
var i = 0;
40+
41+
// version dependent settings
42+
var compLevel = DSDA.CompatibilityLevel.MBF21;
43+
var turningResolution = DSDA.TurningResolution.Shorttics;
44+
var skill = DSDA.SkillLevel.UV;
45+
var episode = 1;
46+
var map = 0;
47+
// v1.2- demos didn't store these (nor DisplayPlayer), they have to be explicitly set
48+
var multiplayerMode = DSDA.MultiplayerMode.Single_Coop;
49+
var monstersRespawn = false;
50+
var fastMonsters = false;
51+
var noMonsters = false;
52+
1753
Result.Movie.HeaderEntries[HeaderKeys.Core] = CoreNames.DSDA;
1854
Result.Movie.SystemID = VSystemID.Raw.Doom;
1955

20-
// Try to decide game version based on signature
21-
var signature = input[i];
22-
DSDA.CompatibilityLevel presumedCompatibilityLevel;
23-
if (signature <= 102)
56+
// Try to decide game version
57+
var version = (DemoVersion)input[i++];
58+
59+
// Handling of unrecognized demo formats
60+
// Versions up to 1.2 use a 7-byte header - first byte is a skill level.
61+
// Versions after 1.2 use a 13-byte header - first byte is a demoversion.
62+
// BOOM's demoversion starts from 200
63+
if (!((version >= DemoVersion.Skill_1 && version <= DemoVersion.Skill_5 ) ||
64+
(version >= DemoVersion.Doom_1_4 && version <= DemoVersion.DoomClassic) ||
65+
(version >= DemoVersion.Boom_2_00 && version <= DemoVersion.PrBoomPlus ) ||
66+
(version == DemoVersion.MBF21)))
2467
{
25-
// there is no signature, the first byte is the skill level, so don't advance
26-
Console.WriteLine("Reading DOOM LMP demo version: <=1.12");
27-
presumedCompatibilityLevel = DSDA.CompatibilityLevel.Doom_12;
68+
Result.Errors.Add($"Unknown demo format: {version}");
69+
return;
2870
}
29-
else
71+
72+
if (version < DemoVersion.Doom_1_4)
73+
{
74+
// there is no version, the first byte is the skill level
75+
skill = (DSDA.SkillLevel)version;
76+
episode = input[i++];
77+
map = input[i++];
78+
compLevel = DSDA.CompatibilityLevel.Doom_12;
79+
Console.WriteLine("Reading DOOM LMP demo version: 1.2-");
80+
}
81+
else if (version < DemoVersion.Boom_2_00)
3082
{
31-
i++;
32-
Console.WriteLine("Reading DOOM LMP demo version: {0}", signature);
33-
presumedCompatibilityLevel = signature < 109
83+
if (version == DemoVersion.TASDoom)
84+
{
85+
compLevel = DSDA.CompatibilityLevel.TasDoom;
86+
}
87+
else if (version >= DemoVersion.DoomClassic)
88+
{
89+
turningResolution = DSDA.TurningResolution.Longtics;
90+
}
91+
92+
skill = (DSDA.SkillLevel) (input[i++] + 1);
93+
episode = input[i++];
94+
map = input[i++];
95+
multiplayerMode = (DSDA.MultiplayerMode) input[i++];
96+
monstersRespawn = input[i++] is not 0;
97+
fastMonsters = input[i++] is not 0;
98+
noMonsters = input[i++] is not 0;
99+
i++; // DisplayPlayer is a non-sync setting so importers can't set it
100+
101+
// DSDA-Doom assumes 1.666 compat for sig < 107 but this should be fine too
102+
compLevel = version < DemoVersion.Doom_1_9
34103
? DSDA.CompatibilityLevel.Doom_1666
35104
: DSDA.CompatibilityLevel.Doom2_19;
105+
Console.WriteLine("Reading DOOM LMP demo version: {0}", version);
106+
}
107+
else // Boom territory
108+
{
109+
i++; // skip to signature's second byte
110+
var portID = input[i++];
111+
i += 4; // skip the rest of the signature
112+
switch (version)
113+
{
114+
case DemoVersion.Boom_2_00:
115+
case DemoVersion.Boom_2_01:
116+
if (input[i++] == 1)
117+
{
118+
compLevel = DSDA.CompatibilityLevel.Boom_Compatibility;
119+
}
120+
else
121+
{
122+
compLevel = DSDA.CompatibilityLevel.Boom_201;
123+
}
124+
break;
125+
case DemoVersion.Boom_2_02:
126+
if (input[i++] == 1)
127+
{
128+
compLevel = DSDA.CompatibilityLevel.Boom_Compatibility;
129+
}
130+
else
131+
{
132+
compLevel = DSDA.CompatibilityLevel.Boom_202;
133+
}
134+
break;
135+
case DemoVersion.MBF:
136+
if (portID == (byte) 'B') // "BOOM"
137+
{
138+
// don't advance!
139+
compLevel = DSDA.CompatibilityLevel.LxDoom;
140+
}
141+
else if (portID == (byte) 'M') // "MBF"
142+
{
143+
compLevel = DSDA.CompatibilityLevel.MBF21;
144+
i++;
145+
}
146+
break;
147+
case DemoVersion.PrBoom_2_1_0:
148+
compLevel = DSDA.CompatibilityLevel.PrBoom_2;
149+
i++;
150+
break;
151+
case DemoVersion.PrBoom_2_2_x:
152+
compLevel = DSDA.CompatibilityLevel.PrBoom_3;
153+
i++;
154+
break;
155+
case DemoVersion.PrBoom_2_3_x:
156+
compLevel = DSDA.CompatibilityLevel.PrBoom_4;
157+
i++;
158+
break;
159+
case DemoVersion.PrBoom_2_4_0:
160+
compLevel = DSDA.CompatibilityLevel.PrBoom_5;
161+
i++;
162+
break;
163+
case DemoVersion.PrBoomPlus:
164+
compLevel = DSDA.CompatibilityLevel.PrBoom_6;
165+
turningResolution = DSDA.TurningResolution.Longtics;
166+
i++;
167+
break;
168+
case DemoVersion.MBF21:
169+
compLevel = DSDA.CompatibilityLevel.MBF21;
170+
turningResolution = DSDA.TurningResolution.Longtics;
171+
i++;
172+
break;
173+
default:
174+
Result.Errors.Add($"Unknown demo format: {version}");
175+
return;
176+
}
177+
178+
skill = (DSDA.SkillLevel) (input[i++] + 1);
179+
episode = input[i++];
180+
map = input[i++];
181+
multiplayerMode = (DSDA.MultiplayerMode) input[i++];
182+
i++; // DisplayPlayer is a non-sync setting so importers can't set it
36183
}
37184

38185
DSDA.DoomSyncSettings syncSettings = new()
39186
{
40187
InputFormat = DoomControllerTypes.Doom,
41-
CompatibilityLevel = presumedCompatibilityLevel,
42-
SkillLevel = (DSDA.SkillLevel) (1 + input[i++]),
43-
InitialEpisode = input[i++],
44-
InitialMap = input[i++],
45-
MultiplayerMode = (DSDA.MultiplayerMode) input[i++],
46-
MonstersRespawn = input[i++] is not 0,
47-
FastMonsters = input[i++] is not 0,
48-
NoMonsters = input[i++] is not 0,
49-
TurningResolution = DSDA.TurningResolution.Shorttics,
188+
CompatibilityLevel = compLevel,
189+
SkillLevel = skill,
190+
InitialEpisode = episode,
191+
InitialMap = map,
192+
MultiplayerMode = multiplayerMode,
193+
MonstersRespawn = monstersRespawn,
194+
FastMonsters = fastMonsters,
195+
NoMonsters = noMonsters,
196+
TurningResolution = turningResolution,
50197
RenderWipescreen = false,
51198
};
52199

53-
_ = input[i++]; // DisplayPlayer is a non-sync setting so importers can't* set it
200+
if (version >= DemoVersion.Boom_2_00)
201+
{
202+
var optionsSize = compLevel == DSDA.CompatibilityLevel.MBF21 ? 21 + 25 : 64;
203+
i += optionsSize;
204+
if (version == DemoVersion.Boom_2_00)
205+
i += 256 - optionsSize;
206+
}
207+
54208
syncSettings.Player1Present = input[i++] is not 0;
55209
syncSettings.Player2Present = input[i++] is not 0;
56210
syncSettings.Player3Present = input[i++] is not 0;
57211
syncSettings.Player4Present = input[i++] is not 0;
212+
213+
if (compLevel >= DSDA.CompatibilityLevel.Boom_Compatibility
214+
&& version >= DemoVersion.Boom_2_00)
215+
{
216+
var FUTURE_MAXPLAYERS = 32;
217+
var g_maxplayers = 4;
218+
i += FUTURE_MAXPLAYERS - g_maxplayers;
219+
}
220+
58221
Result.Movie.SyncSettingsJson = ConfigService.SaveWithType(syncSettings);
59222

60223
var doomController = new DoomControllerDeck(
@@ -63,33 +226,40 @@ protected override void RunImport()
63226
syncSettings.Player2Present,
64227
syncSettings.Player3Present,
65228
syncSettings.Player4Present,
66-
syncSettings.TurningResolution == DSDA.TurningResolution.Longtics);
229+
turningResolution == DSDA.TurningResolution.Longtics);
67230

68231
var controller = new SimpleController(doomController.Definition);
69232
controller.Definition.BuildMnemonicsCache(Result.Movie.SystemID);
70233
Result.Movie.LogKey = Bk2LogEntryGenerator.GenerateLogKey(controller.Definition);
71234

72-
void ParsePlayer(string playerPfx)
235+
void ParsePlayer(int port)
73236
{
74-
controller.AcceptNewAxis(playerPfx + "Run Speed", unchecked((sbyte) input[i++]));
75-
controller.AcceptNewAxis(playerPfx + "Strafing Speed", unchecked((sbyte) input[i++]));
76-
controller.AcceptNewAxis(playerPfx + "Turning Speed", unchecked((sbyte) input[i++]));
77-
78-
var specialValue = input[i++];
79-
controller[playerPfx + "Fire"] = (specialValue & 0b00000001) is not 0;
80-
controller[playerPfx + "Use"] = (specialValue & 0b00000010) is not 0;
81-
bool changeWeapon = (specialValue & 0b00000100) is not 0;
82-
int weapon = changeWeapon ? (((specialValue & 0b00111000) >> 3) + 1) : 0;
83-
controller.AcceptNewAxis(playerPfx + "Weapon Select", weapon);
237+
controller.AcceptNewAxis($"P{port} Run Speed", unchecked((sbyte) input[i++]));
238+
controller.AcceptNewAxis($"P{port} Strafing Speed", unchecked((sbyte) input[i++]));
239+
if (turningResolution == DSDA.TurningResolution.Longtics)
240+
{
241+
// low byte comes first and is stored as an unsigned value
242+
controller.AcceptNewAxis($"P{port} Turning Speed Frac.", unchecked((byte) input[i++]));
243+
}
244+
controller.AcceptNewAxis($"P{port} Turning Speed", unchecked((sbyte) input[i++]));
245+
246+
var buttons = input[i++];
247+
controller[$"P{port} Fire"] = (buttons & 0b00000001) is not 0;
248+
controller[$"P{port} Use"] = (buttons & 0b00000010) is not 0;
249+
var changeWeapon = (buttons & 0b00000100) is not 0;
250+
var weapon = changeWeapon ? (((buttons & 0b00111000) >> 3) + 1) : 0;
251+
controller.AcceptNewAxis($"P{port} Weapon Select", weapon);
84252
}
85253

86254
do
87255
{
88-
if (syncSettings.Player1Present) ParsePlayer("P1 ");
89-
if (syncSettings.Player2Present) ParsePlayer("P2 ");
90-
if (syncSettings.Player3Present) ParsePlayer("P3 ");
91-
if (syncSettings.Player4Present) ParsePlayer("P4 ");
256+
if (syncSettings.Player1Present) ParsePlayer(1);
257+
if (syncSettings.Player2Present) ParsePlayer(2);
258+
if (syncSettings.Player3Present) ParsePlayer(3);
259+
if (syncSettings.Player4Present) ParsePlayer(4);
260+
92261
Result.Movie.AppendFrame(controller);
262+
93263
if (i == input.Length) throw new Exception("Reached end of input movie stream without finalization byte");
94264
}
95265
while (input[i] is not 0x80);

0 commit comments

Comments
 (0)