4 #include <QProcess> |
4 #include <QProcess> |
5 #include "demo.h" |
5 #include "demo.h" |
6 #include "bytestream.h" |
6 #include "bytestream.h" |
7 #include "misc.h" |
7 #include "misc.h" |
8 #include "ui_demoprompt.h" |
8 #include "ui_demoprompt.h" |
9 |
9 #include "prompts.h" |
10 static const uint32 g_demoSignature = makeByteID( 'Z', 'C', 'L', 'D' ); |
10 |
11 |
11 static const uint32 g_demoSignature = makeByteID ('Z', 'C', 'L', 'D'); |
12 // ============================================================================= |
12 |
13 // ----------------------------------------------------------------------------- |
13 // ============================================================================= |
14 static str tr( const char* msg ) { |
14 // ----------------------------------------------------------------------------- |
15 return QObject::tr( msg ); |
15 str uncolorize (const str& in) { |
16 } |
16 str out; |
17 |
17 int skip = 0; |
18 // ============================================================================= |
18 |
19 // ----------------------------------------------------------------------------- |
19 for (const qchar& c : in) { |
20 static void error( str msg ) { |
20 if (skip-- > 0) |
21 QMessageBox::critical( null, "Error", msg ); |
21 continue; |
22 } |
22 |
23 |
23 if (c.toAscii() == '\034') { |
24 // ============================================================================= |
24 skip = 1; |
25 // ----------------------------------------------------------------------------- |
25 continue; |
26 static int getVersionIndex( str ver ) { |
26 } |
27 int i; |
27 |
28 |
28 out += c; |
29 for( i = 0; i < g_zanVersions.size(); ++i ) |
29 } |
30 if( g_zanVersions[i] == ver ) |
30 |
31 return i; |
31 return out; |
32 |
32 } |
33 return -1; |
33 |
34 } |
34 // ============================================================================= |
35 |
35 // ----------------------------------------------------------------------------- |
36 // ============================================================================= |
36 static str tr (const char* msg) { |
37 // ----------------------------------------------------------------------------- |
37 return QObject::tr (msg); |
38 static str findWAD( str name ) { |
38 } |
|
39 |
|
40 // ============================================================================= |
|
41 // ----------------------------------------------------------------------------- |
|
42 static void error (str msg) { |
|
43 QMessageBox::critical (null, "Error", msg); |
|
44 } |
|
45 |
|
46 // ============================================================================= |
|
47 // ----------------------------------------------------------------------------- |
|
48 static bool isKnownVersion (str ver) { |
39 QSettings cfg; |
49 QSettings cfg; |
40 list<var> paths = cfg.value( "wads/paths", list<var>() ).toList(); |
50 list<var> versions = getVersionsList(); |
41 |
51 |
42 if( paths.size() == 0 ) { |
52 for (const var& it : versions) |
43 error( tr( "No WAD paths configured!" )); |
53 if (it.toString() == ver || it.toString() + 'M' == ver) |
44 exit( 9 ); |
54 return true; |
45 } |
55 |
46 |
56 return false; |
47 for( var it : paths ) { |
57 } |
48 str fullpath = fmt( "%1/%2", it.toString(), name ); |
58 |
49 QFile f( fullpath ); |
59 // ============================================================================= |
50 |
60 // ----------------------------------------------------------------------------- |
51 if( f.exists() ) |
61 static str findWAD (str name) { |
|
62 QSettings cfg; |
|
63 list<var> paths = cfg.value ("wads/paths", list<var>()).toList(); |
|
64 |
|
65 if (paths.size() == 0) { |
|
66 error (tr ("No WAD paths configured!")); |
|
67 |
|
68 // Cannot just return an empty string here since that'd trigger |
|
69 // another error prompt - skip ahead and exit. |
|
70 exit (9); |
|
71 } |
|
72 |
|
73 for (const var& it : paths) { |
|
74 str fullpath = fmt ("%1/%2", it.toString(), name); |
|
75 QFile f (fullpath); |
|
76 |
|
77 if (f.exists()) |
52 return fullpath; |
78 return fullpath; |
53 } |
79 } |
54 |
80 |
55 return ""; |
81 return ""; |
56 } |
82 } |
57 |
83 |
58 // ============================================================================= |
84 // ============================================================================= |
59 // ----------------------------------------------------------------------------- |
85 // ----------------------------------------------------------------------------- |
60 int launchDemo( str path ) { |
86 int launchDemo (str path) { |
61 FILE* fp = fopen( path.toStdString().c_str(), "r" ); |
87 FILE* fp = fopen (path.toStdString().c_str(), "r"); |
62 |
88 |
63 if( !fp ) { |
89 if (!fp) { |
64 error( fmt( tr( "Couldn't open '%1' for reading: %2" ), path, strerror( errno ))); |
90 error (fmt (tr ("Couldn't open '%1' for reading: %2"), path, strerror (errno))); |
65 return 1; |
91 return 1; |
66 } |
92 } |
67 |
93 |
68 fseek( fp, 0, SEEK_END ); |
94 fseek (fp, 0, SEEK_END); |
69 const size_t fsize = ftell( fp ); |
95 const size_t fsize = ftell (fp); |
70 rewind( fp ); |
96 rewind (fp); |
71 |
97 |
72 char* buf = new char[fsize]; |
98 char* buf = new char[fsize]; |
73 |
99 |
74 if( fread( buf, 1, fsize, fp ) != fsize ) { |
100 if (fread (buf, 1, fsize, fp) != fsize) { |
75 error( tr( "I/O error" )); |
101 error (tr ("I/O error")); |
76 delete[] buf; |
102 delete[] buf; |
77 return 2; |
103 return 2; |
78 } |
104 } |
79 |
105 |
80 Bytestream s( buf, fsize ); |
106 Bytestream s (buf, fsize); |
81 delete[] buf; |
107 delete[] buf; |
82 |
108 |
83 uint8 offset; |
109 uint8 offset; |
84 uint32 length; |
110 uint32 length; |
85 |
111 |
91 |
117 |
92 // Check signature |
118 // Check signature |
93 { |
119 { |
94 uint32 sig; |
120 uint32 sig; |
95 |
121 |
96 if( !s.readLong( sig ) || sig != g_demoSignature ) { |
122 if (!s.readLong (sig) || sig != g_demoSignature) { |
97 error( fmt( tr( "'%1' is not a Zandronum demo file!" ), path )); |
123 error (fmt (tr ("'%1' is not a Zandronum demo file!"), path)); |
98 return 3; |
124 return 3; |
99 } |
125 } |
100 } |
126 } |
101 |
127 |
102 // Zandronum stores CLD_DEMOLENGTH after the signature. This is also the |
128 // Zandronum stores CLD_DEMOLENGTH after the signature. This is also the |
103 // first demo enumerator, so we can determine the offset (which is variable!) |
129 // first demo enumerator, so we can determine the offset (which is variable!) |
104 // from this byte |
130 // from this byte |
105 s.readByte( offset ); |
131 s.readByte (offset); |
106 s.readLong( length ); |
132 s.readLong (length); |
107 |
133 |
108 uint16 zanversionID, numWads; |
134 uint16 zanversionID, numWads; |
109 uint32 longSink; |
135 uint32 longSink; |
110 str zanversion; |
136 str zanversion; |
111 list<str> wads; |
137 list<str> wads; |
112 bool ready = false; |
138 bool ready = false; |
113 |
139 uint8 buildID; |
114 for( ;; ) { |
140 |
|
141 // Read the demo header and get data |
|
142 for (;;) { |
115 uint8 header; |
143 uint8 header; |
116 if( !s.readByte( header )) |
144 |
|
145 if (!s.readByte (header)) |
117 break; |
146 break; |
118 |
147 |
119 if( header == DemoBodyStart + offset ) { |
148 if (header == DemoBodyStart + offset) { |
120 ready = true; |
149 ready = true; |
121 break; |
150 break; |
122 } elif( header == DemoVersion + offset ) { |
151 } elif (header == DemoVersion + offset) { |
123 s.readShort( zanversionID ); |
152 s.readShort (zanversionID); |
124 s.readString( zanversion ); |
153 s.readString (zanversion); |
125 s.readLong( longSink ); // rng seed - we don't need it |
154 |
126 } elif( header == DemoUserInfo + offset ) { |
155 if (zanversion.left (4) != "1.1-" && zanversion.left (6) != "1.1.1-") |
127 s.readString( userinfo.netname ); |
156 s.readByte (buildID); |
128 s.readByte( userinfo.gender ); |
157 else |
129 s.readLong( userinfo.color ); |
158 buildID = 1; |
130 s.readLong( userinfo.aimdist ); |
159 |
131 s.readString( userinfo.skin ); |
160 s.readLong (longSink); // rng seed - we don't need it |
132 s.readLong( userinfo.railcolor ); |
161 } elif (header == DemoUserInfo + offset) { |
133 s.readByte( userinfo.handicap ); |
162 s.readString (userinfo.netname); |
134 s.readByte( userinfo.unlagged ); |
163 s.readByte (userinfo.gender); |
135 s.readByte( userinfo.respawnOnFire ); |
164 s.readLong (userinfo.color); |
136 s.readByte( userinfo.ticsPerUpdate ); |
165 s.readLong (userinfo.aimdist); |
137 s.readByte( userinfo.connectionType ); |
166 s.readString (userinfo.skin); |
138 s.readString( userinfo.className ); |
167 s.readLong (userinfo.railcolor); |
139 } elif( header == DemoWads + offset ) { |
168 s.readByte (userinfo.handicap); |
140 s.readShort( numWads ); |
169 s.readByte (userinfo.unlagged); |
141 |
170 s.readByte (userinfo.respawnOnFire); |
142 for( uint8 i = 0; i < numWads; ++i ) { |
171 s.readByte (userinfo.ticsPerUpdate); |
|
172 s.readByte (userinfo.connectionType); |
|
173 s.readString (userinfo.className); |
|
174 } elif (header == DemoWads + offset) { |
|
175 str sink; |
|
176 s.readShort (numWads); |
|
177 |
|
178 for (uint8 i = 0; i < numWads; ++i) { |
143 str wad; |
179 str wad; |
144 s.readString( wad ); |
180 s.readString (wad); |
145 wads << wad; |
181 wads << wad; |
146 } |
182 } |
147 |
183 |
148 // The demo has two checksum strings. We're not interested |
184 // The demo has two checksum strings. We're not interested |
149 // in them though. |
185 // in them though. |
150 str sink; |
186 for (int i = 0; i < 2; ++i) |
151 for( int i = 0; i < 2; ++i ) |
187 s.readString (sink); |
152 s.readString( sink ); |
|
153 } else { |
188 } else { |
154 error( fmt( tr( "Unknown header %1!\n" ), (int) header )); |
189 error (fmt (tr ("Unknown header %1!\n"), (int) header)); |
155 return 4; |
190 return 3; |
156 } |
191 } |
157 } |
192 } |
158 |
193 |
159 if( !ready ) { |
194 if (!ready) { |
160 error( fmt( tr( "Incomplete demo header in '%s'!" ), path )); |
195 error (fmt (tr ("Incomplete demo header in '%s'!"), path)); |
161 return 5; |
196 return 3; |
162 } |
197 } |
163 |
198 |
164 int i = getVersionIndex( zanversion ); |
199 if (!isKnownVersion (zanversion)) { |
165 if( i == -1 ) { |
200 UnknownVersionPrompt* prompt = new UnknownVersionPrompt (path, zanversion, (buildID == 1)); |
166 error( fmt( tr( "Unknown Zandronum version %1!\n" ), zanversion )); |
201 if (!prompt->exec()) |
167 return 6; |
202 return 6; |
168 } |
203 |
169 |
204 if (!isKnownVersion (zanversion)) { |
|
205 error (tr ("Failure in configuration! This shouldn't happen.")); |
|
206 return 6; |
|
207 } |
|
208 } |
|
209 |
170 QSettings cfg; |
210 QSettings cfg; |
171 str binarypath = cfg.value( binaryConfigName( zanversion )).toString(); |
211 str binarypath = cfg.value (binaryConfigName (zanversion)).toString(); |
172 |
212 |
173 if( binarypath.isEmpty() ) { |
213 if (binarypath.isEmpty()) { |
174 error( fmt( tr( "No binary path specified for Zandronum version %1!\n" ), zanversion )); |
214 error (fmt (tr ("No binary path specified for Zandronum version %1!"), zanversion)); |
175 return 7; |
215 return 7; |
176 } |
216 } |
177 |
217 |
178 str iwad, iwadpath; |
218 str iwadpath; |
179 list<str> pwads, pwadpaths; |
219 list<str> pwadpaths; |
180 |
220 |
181 // Find the WADs |
221 // Find the WADs |
182 for( const str& wad : wads ) { |
222 for (const str& wad : wads) { |
183 str path = findWAD( wad ); |
223 str path = findWAD (wad); |
184 |
224 |
185 // IWAD names can appear in uppercase too. Linux is case-sensitive, so.. |
225 // IWAD names can appear in uppercase too. Linux is case-sensitive, so.. |
186 if( &wad == &wads[0] && path.isEmpty() ) |
226 if (&wad == &wads[0] && path.isEmpty()) |
187 path = findWAD( wad.toUpper() ); |
227 path = findWAD (wad.toUpper()); |
188 |
228 |
189 if( path.isEmpty() ) { |
229 if (path.isEmpty()) { |
190 error( fmt( tr( "Couldn't find %1!\n" ), wad )); |
230 error (fmt (tr ("Couldn't find %1!"), wad)); |
191 return 8; |
231 return 8; |
192 } |
232 } |
193 |
233 |
194 if( &wad == &wads[0] ) { |
234 if (&wad == &wads[0]) |
195 iwadpath = path; |
235 iwadpath = path; |
196 iwad = wad; |
236 else |
197 } else { |
|
198 pwadpaths << path; |
237 pwadpaths << path; |
199 pwads << wad; |
238 } |
200 } |
239 |
201 } |
240 if (!cfg.value ("nodemoprompt", false).toBool()) { |
202 |
|
203 if( !cfg.value( "nodemoprompt", false ).toBool() ) { |
|
204 str pwadtext; |
241 str pwadtext; |
205 for( const str& pwad : pwads ) { |
242 |
206 if( !pwadtext.isEmpty() ) |
243 for (const str& pwad : wads) { |
|
244 if (&pwad == &wads[0]) |
|
245 continue; // skip the IWAD |
|
246 |
|
247 if (!pwadtext.isEmpty()) |
207 pwadtext += "<br />"; |
248 pwadtext += "<br />"; |
208 |
249 |
209 pwadtext += pwad; |
250 pwadtext += pwad; |
210 } |
251 } |
211 |
252 |
212 QDialog* dlg = new QDialog; |
253 QDialog* dlg = new QDialog; |
213 Ui_DemoPrompt ui; |
254 Ui_DemoPrompt ui; |
214 ui.setupUi( dlg ); |
255 ui.setupUi (dlg); |
215 ui.demoNameLabel->setText( basename( path )); |
256 ui.demoNameLabel->setText (basename (path)); |
216 ui.demoRecorder->setText( userinfo.netname ); |
257 ui.demoRecorder->setText (uncolorize (userinfo.netname)); |
217 ui.versionLabel->setText( zanversion ); |
258 ui.versionLabel->setText (zanversion); |
218 ui.iwadLabel->setText( wads[0] ); |
259 ui.iwadLabel->setText (wads[0]); |
219 ui.pwadsLabel->setText( pwadtext ); |
260 ui.pwadsLabel->setText (pwadtext); |
220 |
261 dlg->setWindowTitle (fmt (APPNAME " %1", versionString())); |
221 if( !dlg->exec() ) |
262 |
|
263 if (!dlg->exec()) |
222 return 1; |
264 return 1; |
223 } |
265 } |
224 |
266 |
225 print( "binary: %1\n", binarypath ); |
267 QStringList cmdlineList ( { |
226 print( "iwad: %1\npwads: %2\n", iwadpath, pwadpaths ); |
|
227 |
|
228 QStringList cmdlineList ({ |
|
229 "-playdemo", path, |
268 "-playdemo", path, |
230 "-iwad", iwadpath, |
269 "-iwad", iwadpath, |
231 }); |
270 }); |
232 |
271 |
233 if( pwadpaths.size() > 0 ) { |
272 if (pwadpaths.size() > 0) { |
234 cmdlineList << "-file"; |
273 cmdlineList << "-file"; |
235 cmdlineList << pwadpaths; |
274 cmdlineList << pwadpaths; |
236 } |
275 } |
237 |
276 |
238 print( "commandline: %1 %2\n", binarypath, cmdlineList.join( " " )); |
277 print ("Executing: %1 %2\n", binarypath, cmdlineList.join (" ")); |
239 QProcess* proc = new QProcess; |
278 QProcess* proc = new QProcess; |
240 proc->start( binarypath, cmdlineList ); |
279 proc->start (binarypath, cmdlineList); |
241 proc->waitForFinished( -1 ); |
280 proc->waitForFinished (-1); |
242 return 0; |
281 return 0; |
243 } |
282 } |