@@ -7,54 +7,217 @@ namespace BizHawk.Client.Common
7
7
{
8
8
// LMP file format: https://doomwiki.org/wiki/Demo#Technical_information
9
9
// 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
10
11
[ ImporterFor ( "Doom" , ".lmp" ) ]
11
12
internal class DoomLmpImport : MovieImporter
12
13
{
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
+
13
36
protected override void RunImport ( )
14
37
{
15
38
var input = SourceFile . OpenRead ( ) . ReadAllBytes ( ) ;
16
39
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
+
17
53
Result . Movie . HeaderEntries [ HeaderKeys . Core ] = CoreNames . DSDA ;
18
54
Result . Movie . SystemID = VSystemID . Raw . Doom ;
19
55
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 ) ) )
24
67
{
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 ;
28
70
}
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 )
30
82
{
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
34
103
? DSDA . CompatibilityLevel . Doom_1666
35
104
: 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
36
183
}
37
184
38
185
DSDA . DoomSyncSettings syncSettings = new ( )
39
186
{
40
187
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 ,
50
197
RenderWipescreen = false ,
51
198
} ;
52
199
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
+
54
208
syncSettings . Player1Present = input [ i ++ ] is not 0 ;
55
209
syncSettings . Player2Present = input [ i ++ ] is not 0 ;
56
210
syncSettings . Player3Present = input [ i ++ ] is not 0 ;
57
211
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
+
58
221
Result . Movie . SyncSettingsJson = ConfigService . SaveWithType ( syncSettings ) ;
59
222
60
223
var doomController = new DoomControllerDeck (
@@ -63,33 +226,40 @@ protected override void RunImport()
63
226
syncSettings . Player2Present ,
64
227
syncSettings . Player3Present ,
65
228
syncSettings . Player4Present ,
66
- syncSettings . TurningResolution == DSDA . TurningResolution . Longtics ) ;
229
+ turningResolution == DSDA . TurningResolution . Longtics ) ;
67
230
68
231
var controller = new SimpleController ( doomController . Definition ) ;
69
232
controller . Definition . BuildMnemonicsCache ( Result . Movie . SystemID ) ;
70
233
Result . Movie . LogKey = Bk2LogEntryGenerator . GenerateLogKey ( controller . Definition ) ;
71
234
72
- void ParsePlayer ( string playerPfx )
235
+ void ParsePlayer ( int port )
73
236
{
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 ) ;
84
252
}
85
253
86
254
do
87
255
{
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
+
92
261
Result . Movie . AppendFrame ( controller ) ;
262
+
93
263
if ( i == input . Length ) throw new Exception ( "Reached end of input movie stream without finalization byte" ) ;
94
264
}
95
265
while ( input [ i ] is not 0x80 ) ;
0 commit comments