Ostatnio aktywny 1 month ago

NX10/20 Parser of StepFever

racerxdl's Avatar Lucas Teske zrewidował ten Gist 10 years ago. Przejdź do rewizji

1 file changed, 463 insertions

nxdata.py(stworzono plik)

@@ -0,0 +1,463 @@
1 + #!/usr/bin/env python
2 + from stepfever import *
3 + import time
4 + import struct
5 +
6 + SUPRESS_PRINT = False
7 +
8 + class NoteType:
9 + Null = 0
10 + Tap = 1
11 + HoldHead = 2
12 + HoldBody = 3
13 + HoldTail = 4
14 + RollHead = 5
15 + Item = 6
16 + Fake = 7
17 + HoldHeadFake = 8
18 + HoldBodyFake = 9
19 + HoldTailFake = 10
20 + RollHeadFake = 11
21 + ItemFake = 12
22 +
23 + class NX10:
24 +
25 + Null = 0x00
26 + Fake = 0x73 # 0b00100011
27 +
28 + Tap = 0xB3 # 0b01000011
29 + HoldHead = 0xB4 # 0b01010111
30 + HoldBody = 0xB6 # 0b01011011
31 + HoldTail = 0xB7 # 0b01011111
32 +
33 + HoldHeadFake = 0x74 # 0b00110111
34 + HoldBodyFake = 0x76 # 0b00111011
35 + HoldTailFake = 0x77 # 0b00111111
36 + Item = 0xF1 # 0b01100001
37 +
38 +
39 + class NX20:
40 +
41 + Null = 0x00
42 +
43 + Effect = 0x41 # 0b01000001
44 + DivBrain = 0x42 # 0b01000010
45 + Fake = 0x23 # 0b00100011
46 + Tap = 0x43 # 0b01000011
47 + HoldHeadFake = 0x37 # 0b00110111
48 + HoldHead = 0x57 # 0b01010111
49 + HoldBodyFake = 0x3b # 0b00111011
50 + HoldBody = 0x5B # 0b01011011
51 + HoldTailFake = 0x3f # 0b00111111
52 + HoldTail = 0x5F # 0b01011111
53 + FakeItem = 0x21 # 0b00100001
54 + Item = 0x61 # 0b01100001
55 + Row = 0x80 # 0b10000000
56 +
57 + MetaMissionLevel = 1000
58 + MetaChartLevel = 1001
59 +
60 + NoteSeedAction = 0
61 + NoteSeedShield = 1
62 + NoteSeedChange = 2
63 + NoteSeedAcceleration = 3
64 + NoteSeedFlash = 4
65 + NoteSeedMineTap = 5
66 + NoteSeedMineHold = 6
67 + NoteSeedAttack = 7
68 + NoteSeedDrain = 8
69 + NoteSeedHeart = 9
70 + NoteSeedSpeed2 = 10
71 + NoteSeedRandom = 11
72 + NoteSeedSpeed3 = 12
73 + NoteSeedSpeed4 = 13
74 + NoteSeedSpeed8 = 14
75 + NoteSeedSpeed1 = 15
76 + NoteSeedPotion = 16
77 + NoteSeedRotate0 = 17
78 + NoteSeedRotate90 = 18
79 + NoteSeedRotate180 = 19
80 + NoteSeedRotate270 = 20
81 + NoteSeedSpeed_ = 21
82 + NoteSeedBomb = 22
83 + NoteSeedHyperPotion = 23
84 +
85 + NOTE_NX202SFC = {
86 + NX20.Null : NoteType.Null,
87 + NX20.Fake : NoteType.Fake,
88 + NX20.Tap : NoteType.Tap,
89 + NX20.HoldHead : NoteType.HoldHead,
90 + NX20.HoldHeadFake : NoteType.HoldHeadFake,
91 + NX20.HoldBody : NoteType.HoldBody,
92 + NX20.HoldBodyFake : NoteType.HoldBodyFake,
93 + NX20.HoldTail : NoteType.HoldTail,
94 + NX20.FakeItem : NoteType.ItemFake,
95 + NX20.Item : NoteType.Item
96 + }
97 +
98 + NOTE_NX102SFC = {
99 + NX10.Null : NoteType.Null,
100 + NX10.Fake : NoteType.Fake,
101 + NX10.Tap : NoteType.Tap,
102 + NX10.HoldHead : NoteType.HoldHead,
103 + NX10.HoldHeadFake : NoteType.HoldHeadFake,
104 + NX10.HoldBody : NoteType.HoldBody,
105 + NX10.HoldBodyFake : NoteType.HoldBodyFake,
106 + NX10.HoldTail : NoteType.HoldTail,
107 + NX10.Item : NoteType.Item
108 + }
109 +
110 + MODE_NX202SFC = {
111 + 5 : ChartType.PUMP_SINGLE,
112 + 10 : ChartType.PUMP_DOUBLE
113 + }
114 +
115 + def ParseStep(stepdata):
116 + if not stepdata[0] in NOTE_NX202SFC or stepdata[0] == NX20.Effect:
117 + return {"type":NoteType.Null,"seed": 0,"skin":0,"trigger":0}
118 + else:
119 + return {"type":NOTE_NX202SFC[stepdata[0]],"seed": stepdata[1],"skin":stepdata[2],"trigger":0}
120 +
121 +
122 + def ParseStepNX10(stepdata):
123 + if not stepdata[0] in NOTE_NX102SFC:
124 + return {"type":NoteType.Null,"seed": 0,"skin":0,"trigger":0}
125 + else:
126 + return {"type":NOTE_NX102SFC[stepdata[0]],"seed": stepdata[1],"skin":stepdata[2],"trigger":0}
127 +
128 + def GetChartData(file):
129 + out = {"level":-1,"type":ChartType.PUMP_SINGLE,"color" : (255,255,255),"added":time.time(), "title":"Converted by Tool","author":"nx2sfm", "file" : file}
130 + f = open(file,"rb")
131 + head = f.read(20)
132 + out["nxtype"] = head[:4]
133 + if head[:4] == "NX20":
134 + startcolumn = struct.unpack("I", head[4:8])[0]
135 + numcolumns = struct.unpack("I", head[8:12])[0]
136 + lightmap = struct.unpack("I", head[12:16])[0]
137 + metablocks = struct.unpack("I", head[16:20])[0]
138 + out["type"] = MODE_NX202SFC[numcolumns]
139 + for i in range(metablocks):
140 + data = f.read(8)
141 + idx = struct.unpack("I", data[:4])[0]
142 + value = struct.unpack("I", data[4:8])[0]
143 + if idx == NX20.MetaChartLevel:
144 + out["level"] = value
145 + break
146 + elif head[:4] == "NX10":
147 + nxversion = struct.unpack("4s", head[0:4])[0]
148 + startcolumn = struct.unpack("I", head[4:8])[0]
149 + numcolumns = struct.unpack("I", head[8:12])[0]
150 + splits = struct.unpack("I", head[12:16])[0]
151 + lightmap = startcolumn > 9
152 + out["type"] = MODE_NX202SFC[numcolumns]
153 + else:
154 + print "NXData::GetChartData() - Invalid file magic: %s" %head[:4]
155 + f.close()
156 + return out
157 +
158 + def ParseChartNX20(chart):
159 + print "Parsing NX20 - %s" %chart["file"]
160 + f = open(chart["file"], "rb")
161 + mode = chart["type"]
162 + startbpm = -1
163 + bpmchanges = []
164 + rushchanges = []
165 + sfchanges = []
166 + sschanges = []
167 + effects = []
168 + tcchanges = []
169 + warps = []
170 + bgchanges = []
171 + rows = []
172 + lastbpm = -1
173 +
174 + beat = 0
175 + time = 0
176 + head = f.read(20)
177 + nxversion = struct.unpack("4s", head[0:4])[0]
178 + startcolumn = struct.unpack("I", head[4:8])[0]
179 + numcolumns = struct.unpack("I", head[8:12])[0]
180 + lightmap = struct.unpack("I", head[12:16])[0]
181 + metablocks = struct.unpack("I", head[16:20])[0]
182 +
183 + # Lets just skip the metadatablocks for now:
184 + junk = f.read(8*metablocks)
185 + numsplits = struct.unpack("I", f.read(4))[0]
186 + lastsf = None
187 + lastss = None
188 +
189 + if not SUPRESS_PRINT:
190 + print "Number of splits: %s" %numsplits
191 + for s in range(numsplits):
192 + systemselected = struct.unpack("I", f.read(4))[0]
193 + metablocks = struct.unpack("I", f.read(4))[0]
194 +
195 + # We will just skip the metadatablocks from split:
196 + junk = f.read(8*metablocks)
197 +
198 + numblocks = struct.unpack("I", f.read(4))[0]
199 + if not SUPRESS_PRINT:
200 + print " System Selected: %s" %systemselected
201 + print " Number of blocks: %s" %numblocks
202 + # Currently, I dont know how System Selected works, but when System Selected != 0, the machine seens to pick a random block.
203 + # This converter is just for test, so we will pick the random block here on convert
204 +
205 + SelectedBlock = 0 if systemselected == 0 else ((int)(random()*numblocks))
206 + if not SUPRESS_PRINT:
207 + print " Block Picked: %s" %SelectedBlock
208 +
209 + for blk in range(0,numblocks):
210 + steptime = struct.unpack("f", f.read(4))[0]
211 + bpm = struct.unpack("f", f.read(4))[0]
212 + mysteryblock = struct.unpack("f", f.read(4))[0]
213 + delay = struct.unpack("f", f.read(4))[0]
214 + speed = struct.unpack("f", f.read(4))[0]
215 + beatsplit = struct.unpack("B", f.read(1))[0]
216 + beatmeasure = struct.unpack("B", f.read(1))[0]
217 + smoothspeed = struct.unpack("B", f.read(1))[0]
218 + unknownflag = struct.unpack("B", f.read(1))[0]
219 + divisionconds = struct.unpack("I", f.read(4))[0]
220 + freeze = (speed<0);
221 + bps = bpm / 60.0
222 +
223 + startbpm = bpm if startbpm == -1 else startbpm
224 +
225 + # We will skip division conditions too
226 + junk = f.read(8*divisionconds)
227 +
228 + numrows = struct.unpack("I", f.read(4))[0]
229 + if SelectedBlock != blk:
230 + if not SUPRESS_PRINT:
231 + print " Skipping not selected block %s" %blk
232 + for n in range(numrows):
233 + rowt = struct.unpack("BBBB", f.read(4))[0]
234 + if rowt[0] != NX20.Row:
235 + junk = f.read(4*numcolumns)
236 +
237 +
238 + else:
239 + speed = abs(speed)
240 + if not SUPRESS_PRINT:
241 + print " CurrentTime: %s" %time
242 + print " CurrentBeat: %s" %beat
243 + print " Block(%s)" % blk
244 + print " StepTime: %s" %steptime
245 + print " BPM: %s" %bpm
246 + print " Mystery Block: %s" %mysteryblock
247 + print " Delay: %s" %delay
248 + print " Freeze: %s"%freeze
249 + print " Speed: %s"%speed
250 + print " Beat Split: %s"%beatsplit
251 + print " Beat Measure: %s"%beatmeasure
252 + print " Smooth Speed: %s"%smoothspeed
253 + print " Unknown Flag: %s"%unknownflag
254 + print " Division Conditions: %s" %divisionconds
255 + print " Number of Rows: %s" %numrows
256 +
257 + # BPM Changes
258 + if lastbpm != bpm:
259 + bpmchanges.append({"time": steptime/1000, "bpm" : bpm})
260 + lastbpm = bpm
261 + delay = delay / 1000
262 +
263 + # Delays
264 + beat += delay * bps
265 + time += delay
266 +
267 + # ScrollFactor
268 + sf = mysteryblock * beatsplit
269 + lastsf = {"beat" : beat, "factor" : sf, "smooth" : smoothspeed>0, "smoothtime" : steptime}
270 + if lastsf != None:
271 + lastsf["smoothtime"] = (steptime - lastsf["smoothtime"]) / 1000
272 + sfchanges.append(lastsf)
273 + if not SUPRESS_PRINT:
274 + print " Adding Scrollfactor Change - Factor: %s Smooth: %s Beat: %s SmoothTime: %s" %(lastsf["factor"],lastsf["smooth"],lastsf["beat"],lastsf["smoothtime"])
275 +
276 + lastsf = {"beat" : beat, "factor" : sf, "smooth" : smoothspeed>0, "smoothtime" : steptime}
277 +
278 + if lastss != None:
279 + lastss["smoothtime"] = (steptime - lastss["smoothtime"]) / 1000
280 + sschanges.append(lastss)
281 + if not SUPRESS_PRINT:
282 + print " Adding ScrollSpeed Change - Factor: %s Smooth: %s Beat: %s SmoothTime: %s" %(lastss["factor"],lastss["smooth"],lastss["beat"],lastss["smoothtime"])
283 +
284 + lastss = {"beat" : beat, "factor" : speed, "smooth" : smoothspeed>0, "smoothtime" : steptime}
285 +
286 + if s == numsplits-1:
287 + if not SUPRESS_PRINT:
288 + print " Adding last ss"
289 + sschanges.append(lastss)
290 +
291 + # Check for warps
292 + if round(steptime*1000) != round(time*1000*1000):
293 + # Lets do an warp!
294 + WarpBeat = ((steptime/1000) - time ) * bps + beat;
295 + if not SUPRESS_PRINT:
296 + print " Time Warp from %s ms to %s ms from beat %s to beat %s" %(steptime,time*1000,WarpBeat,beat)
297 + #warps.append({"beatFrom":beat,"beatTo":WarpBeat})
298 + warps.append({"time":(steptime/1000),"beatTo":beat})
299 +
300 + # Process Rows
301 + for n in range(numrows):
302 + notes = []
303 + rowt = struct.unpack("BBBB",f.read(4))
304 + if rowt[0] != NX20.Row: # If its not an empty row
305 + notedata = ParseStep(rowt)
306 + notedata["beat"] = beat
307 + notes.append(notedata)
308 + for k in range(numcolumns-1):
309 + rowt = struct.unpack("BBBB",f.read(4))
310 + notedata = ParseStep(rowt)
311 + notedata["beat"] = beat
312 + notes.append(notedata)
313 + if len(notes) > 0:
314 + rows.append({"beat":beat, "notes":notes})
315 + beat += 1.0 / beatsplit
316 + time += 1.0 / (beatsplit * bps)
317 + f.close()
318 + data = GenSFC(chart["id"], chart["level"], chart["type"], startbpm, chart["title"], chart["author"], bpmchanges, warps, rushchanges, sfchanges, sschanges, tcchanges, effects, bgchanges, rows)
319 + return data
320 +
321 + def ParseChartNX10(chart):
322 + print "Parsing NX10 %s" %chart["file"]
323 + f = open(chart["file"], "rb")
324 + mode = chart["type"]
325 +
326 + startbpm = -1
327 + bpmchanges = []
328 + rushchanges = []
329 + sfchanges = []
330 + sschanges = []
331 + effects = []
332 + tcchanges = []
333 + warps = []
334 + bgchanges = []
335 + rows = []
336 + lastbpm = -1
337 + beat = 0
338 + time = 0
339 +
340 +
341 + head = f.read(16)
342 + nxversion = struct.unpack("4s", head[0:4])[0]
343 + startcolumn = struct.unpack("I", head[4:8])[0]
344 + numcolumns = struct.unpack("I", head[8:12])[0]
345 + numsplits = struct.unpack("I", head[12:16])[0]
346 + lightmap = startcolumn > 9
347 +
348 + lastsf = None
349 + lastss = None
350 +
351 + if not SUPRESS_PRINT:
352 + print "Format: %s - Start Column %d - Num Columns: %d - Splits: %d" %(nxversion,startcolumn,numcolumns,numsplits)
353 + splitsd = f.read(4*numsplits)
354 + splitsd = struct.unpack("%sI"%numsplits,splitsd)
355 +
356 + for s in range(numsplits):
357 + file_pos = splitsd[s]
358 + f.seek(file_pos)
359 + blocks = struct.unpack("I", f.read(4))[0]
360 + if not SUPRESS_PRINT:
361 + print "\tNumber of blocks %s" %blocks
362 + blocksd = struct.unpack("%sI"%blocks, f.read(4*blocks))
363 + for z in range(blocks):
364 + f.seek(blocksd[z])
365 + steptime = struct.unpack("f", f.read(4))[0]
366 + bpm = struct.unpack("f", f.read(4))[0]
367 + mysteryblock = struct.unpack("f", f.read(4))[0]
368 + delay = struct.unpack("f", f.read(4))[0]
369 + speed = struct.unpack("f", f.read(4))[0]
370 + divisionconds = struct.unpack("I", f.read(4))[0]
371 + beatsplit = struct.unpack("H", f.read(2))[0]
372 + beatmeasure = struct.unpack("B", f.read(1))[0]
373 + smoothspeed = struct.unpack("B", f.read(1))[0]
374 + numrows = struct.unpack("I", f.read(4))[0]
375 + freeze = (speed<0);
376 + bps = bpm / 60.0
377 + startbpm = bpm if startbpm == -1 else startbpm
378 + if not SUPRESS_PRINT:
379 + print "\t Block(%s)" % z
380 + print "\t StepTime: %s" %steptime
381 + print "\t BPM: %s" %bpm
382 + print "\t Mystery Block: %s" %mysteryblock
383 + print "\t Delay: %s" %delay
384 + print "\t Freeze: %s" %freeze
385 + print "\t Speed: %s" %speed
386 + print "\t Beat Split: %s" %beatsplit
387 + print "\t Beat Measure: %s" %beatmeasure
388 + print "\t Smooth Speed: %s" %smoothspeed
389 + print "\t Number of Rows: %s" %numrows
390 +
391 + if divisionconds > 0:
392 + # Skip that conditions
393 + divd = f.read(4*10*2)
394 +
395 + if lightmap:
396 + if numrows > 0:
397 + lmpd = f.read(4*numrows)
398 + else:
399 + speed = abs(speed)
400 + # BPM Changes
401 + if lastbpm != bpm:
402 + bpmchanges.append({"time": steptime/1000, "bpm" : bpm})
403 + lastbpm = bpm
404 + # Delays
405 + if time == 0:
406 + time = steptime
407 + beat = steptime * bps
408 + else:
409 + beat += delay * bps
410 + time += delay
411 +
412 + # ScrollFactor
413 + sf = mysteryblock * beatsplit
414 + lastsf = {"beat" : beat, "factor" : sf, "smooth" : smoothspeed>0, "smoothtime" : steptime}
415 + if lastsf != None:
416 + lastsf["smoothtime"] = (steptime - lastsf["smoothtime"]) / 1000
417 + sfchanges.append(lastsf)
418 + if not SUPRESS_PRINT:
419 + print " Adding Scrollfactor Change - Factor: %s Smooth: %s Beat: %s SmoothTime: %s" %(lastsf["factor"],lastsf["smooth"],lastsf["beat"],lastsf["smoothtime"])
420 +
421 + lastsf = {"beat" : beat, "factor" : sf, "smooth" : smoothspeed>0, "smoothtime" : steptime}
422 +
423 + if lastss != None:
424 + lastss["smoothtime"] = (steptime - lastss["smoothtime"]) / 1000
425 + sschanges.append(lastss)
426 + if not SUPRESS_PRINT:
427 + print " Adding ScrollSpeed Change - Factor: %s Smooth: %s Beat: %s SmoothTime: %s" %(lastss["factor"],lastss["smooth"],lastss["beat"],lastss["smoothtime"])
428 +
429 + lastss = {"beat" : beat, "factor" : speed, "smooth" : smoothspeed>0, "smoothtime" : steptime}
430 +
431 + if s == numsplits-1:
432 + if not SUPRESS_PRINT:
433 + print " Adding last ss"
434 + sschanges.append(lastss)
435 +
436 + # Check for warps
437 + if round(steptime/1000) != round(time):
438 + # Lets do an warp!
439 + WarpBeat = ((steptime/1000) - time ) * bps + beat;
440 + if not SUPRESS_PRINT:
441 + print " Time Warp from %s ms to %s ms from beat %s to beat %s" %(steptime/1000,time,WarpBeat,beat)
442 + #warps.append({"beatFrom":beat,"beatTo":WarpBeat})
443 + warps.append({"time":(steptime/1000),"beatTo":beat})
444 +
445 + if numrows > 0:
446 + rowd = struct.unpack("%sI"%numrows, f.read(4*numrows))
447 + for r in range(numrows):
448 + notes = []
449 + if rowd[r] > 0:
450 + f.seek(rowd[r])
451 + for p in range(numcolumns):
452 + data = struct.unpack("2B", f.read(2))
453 + #{"type":NOTE_NX102SFC[stepdata[0]],"seed": stepdata[1],"skin":stepdata[2],"trigger":0}
454 + notedata = ParseStepNX10([data[0], data[1] & 0xFC, data[1] & 0x03])
455 + notedata["beat"] = beat
456 + notes.append(notedata)
457 + rows.append({"beat":beat, "notes":notes})
458 + if bps != 0:
459 + beat += 1.0 / beatsplit
460 + time += 1.0 / (beatsplit * bps)
461 + f.close()
462 + data = GenSFC(chart["id"], chart["level"], chart["type"], startbpm, chart["title"], chart["author"], bpmchanges, warps, rushchanges, sfchanges, sschanges, tcchanges, effects, bgchanges, rows)
463 + return data
Nowsze Starsze