rhythia-api 181.0.0 → 183.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/.prettierrc.json +6 -6
  2. package/api/addCollectionMap.ts +82 -82
  3. package/api/approveMap.ts +78 -78
  4. package/api/chartPublicStats.ts +32 -32
  5. package/api/createBeatmap.ts +168 -156
  6. package/api/createBeatmapPage.ts +64 -64
  7. package/api/createClan.ts +81 -81
  8. package/api/createCollection.ts +58 -58
  9. package/api/deleteBeatmapPage.ts +77 -72
  10. package/api/deleteCollection.ts +59 -59
  11. package/api/deleteCollectionMap.ts +71 -71
  12. package/api/editAboutMe.ts +91 -91
  13. package/api/editClan.ts +90 -90
  14. package/api/editCollection.ts +77 -75
  15. package/api/editProfile.ts +123 -123
  16. package/api/getAvatarUploadUrl.ts +85 -85
  17. package/api/getBadgedUsers.ts +56 -56
  18. package/api/getBeatmapComments.ts +57 -57
  19. package/api/getBeatmapPage.ts +106 -106
  20. package/api/getBeatmapPageById.ts +99 -99
  21. package/api/getBeatmapStarRating.ts +53 -53
  22. package/api/getBeatmaps.ts +159 -159
  23. package/api/getClan.ts +77 -77
  24. package/api/getCollection.ts +130 -128
  25. package/api/getCollections.ts +128 -126
  26. package/api/getLeaderboard.ts +136 -136
  27. package/api/getMapUploadUrl.ts +93 -93
  28. package/api/getPassToken.ts +55 -55
  29. package/api/getProfile.ts +146 -146
  30. package/api/getPublicStats.ts +180 -180
  31. package/api/getRawStarRating.ts +57 -57
  32. package/api/getScore.ts +85 -85
  33. package/api/getTimestamp.ts +23 -23
  34. package/api/getUserScores.ts +175 -175
  35. package/api/nominateMap.ts +82 -69
  36. package/api/postBeatmapComment.ts +59 -59
  37. package/api/rankMapsArchive.ts +64 -64
  38. package/api/searchUsers.ts +56 -56
  39. package/api/setPasskey.ts +59 -59
  40. package/api/submitScore.ts +433 -433
  41. package/api/updateBeatmapPage.ts +229 -229
  42. package/handleApi.ts +20 -20
  43. package/index.html +2 -2
  44. package/index.ts +865 -862
  45. package/package.json +2 -2
  46. package/types/database.ts +42 -0
  47. package/utils/getUserBySession.ts +48 -48
  48. package/utils/requestUtils.ts +87 -87
  49. package/utils/security.ts +20 -20
  50. package/utils/star-calc/index.ts +72 -72
  51. package/utils/star-calc/osuUtils.ts +53 -53
  52. package/utils/star-calc/sspmParser.ts +398 -398
  53. package/utils/star-calc/sspmv1Parser.ts +165 -165
  54. package/utils/supabase.ts +13 -13
  55. package/utils/test +4 -4
  56. package/utils/validateToken.ts +7 -7
  57. package/vercel.json +12 -12
  58. package/package-lock.json +0 -8913
@@ -1,398 +1,398 @@
1
- type DataTypeID =
2
- | 0x00
3
- | 0x01
4
- | 0x02
5
- | 0x03
6
- | 0x04
7
- | 0x05
8
- | 0x06
9
- | 0x07
10
- | 0x08
11
- | 0x09
12
- | 0x0a
13
- | 0x0b
14
- | 0x0c;
15
-
16
- interface Header {
17
- signature: Buffer;
18
- version: number;
19
- reserved: Buffer;
20
- }
21
-
22
- interface StaticMetadata {
23
- sha1: Buffer;
24
- lastMarkerPos: number;
25
- noteCount: number;
26
- markerCount: number;
27
- difficulty: number;
28
- rating: number;
29
- hasAudio: boolean;
30
- hasCover: boolean;
31
- requiresMod: boolean;
32
- }
33
-
34
- interface Pointers {
35
- customDataOffset: number;
36
- customDataLength: number;
37
- audioOffset: number;
38
- audioLength: number;
39
- coverOffset: number;
40
- coverLength: number;
41
- markerDefinitionsOffset: number;
42
- markerDefinitionsLength: number;
43
- markerOffset: number;
44
- markerLength: number;
45
- }
46
-
47
- interface Strings {
48
- mapID: string;
49
- mapName: string;
50
- songName: string;
51
- mappers: string[];
52
- }
53
-
54
- interface CustomField {
55
- id: string;
56
- type: DataTypeID;
57
- arrayType?: DataTypeID;
58
- value: any;
59
- }
60
-
61
- interface CustomData {
62
- fields: CustomField[];
63
- }
64
-
65
- interface MarkerDefinition {
66
- id: string;
67
- values: DataTypeID[];
68
- }
69
-
70
- interface Marker {
71
- position: number;
72
- type: number;
73
- data: Buffer;
74
- }
75
-
76
- export class SSPMParser {
77
- private buffer: Buffer;
78
- private offset: number = 0;
79
-
80
- constructor(buffer: Buffer) {
81
- this.buffer = buffer;
82
- }
83
-
84
- private checkBounds(length: number): void {
85
- if (this.offset + length > this.buffer.length) {
86
- throw new RangeError(
87
- `Attempt to read beyond buffer length: Offset=${this.offset}, Length=${length}, Buffer Length=${this.buffer.length}`
88
- );
89
- }
90
- }
91
-
92
- private log(message: string): void {
93
- // console.log(`[Offset: ${this.offset}] ${message}`);
94
- }
95
-
96
- private readUInt16(): number {
97
- this.checkBounds(2);
98
- const value = this.buffer.readUInt16LE(this.offset);
99
- this.log(`Read UInt16: ${value}`);
100
- this.offset += 2;
101
- return value;
102
- }
103
-
104
- private readUInt32(): number {
105
- this.checkBounds(4);
106
- const value = this.buffer.readUInt32LE(this.offset);
107
- this.log(`Read UInt32: ${value}`);
108
- this.offset += 4;
109
- return value;
110
- }
111
-
112
- private readUInt64(): number {
113
- this.checkBounds(8);
114
- const low = this.buffer.readUInt32LE(this.offset);
115
- const high = this.buffer.readUInt32LE(this.offset + 4);
116
- this.offset += 8;
117
- const value = low + high * 2 ** 32;
118
- this.log(`Read UInt64: ${value} (Low: ${low}, High: ${high})`);
119
- return value;
120
- }
121
-
122
- private readBytes(length: number): Buffer {
123
- this.checkBounds(length);
124
- const value = this.buffer.slice(this.offset, this.offset + length);
125
- this.log(`Read ${length} bytes`);
126
- this.offset += length;
127
- return value;
128
- }
129
-
130
- private readString(): string {
131
- const length = this.readUInt16();
132
- this.checkBounds(length);
133
- const value = this.readBytes(length).toString("utf-8");
134
- this.log(`Read String of length ${length}: ${value}`);
135
- return value;
136
- }
137
-
138
- private readStringList(count: number): string[] {
139
- const list: string[] = [];
140
- for (let i = 0; i < count; i++) {
141
- list.push(this.readString());
142
- }
143
- return list;
144
- }
145
-
146
- private readMarkerField(typeID: DataTypeID): any {
147
- switch (typeID) {
148
- case 0x01: // 1 byte integer
149
- this.checkBounds(1);
150
- const int8 = this.buffer.readInt8(this.offset++);
151
- this.log(`Read Int8: ${int8}`);
152
- return int8;
153
- case 0x02: // 2 byte uint
154
- return this.readUInt16();
155
- case 0x03: // 4 byte uint
156
- return this.readUInt32();
157
- case 0x04: // 8 byte uint
158
- return this.readUInt64();
159
- case 0x05: // 4 byte float
160
- this.checkBounds(4);
161
- const floatVal32 = this.buffer.readFloatLE(this.offset);
162
- this.log(`Read Float32: ${floatVal32}`);
163
- this.offset += 4;
164
- return floatVal32;
165
- case 0x06: // 8 byte float
166
- this.checkBounds(8);
167
- const floatVal64 = this.buffer.readDoubleLE(this.offset);
168
- this.log(`Read Float64: ${floatVal64}`);
169
- this.offset += 8;
170
- return floatVal64;
171
- case 0x07: // position type
172
- const isQuantum = this.buffer.readUInt8(this.offset++);
173
- let posData;
174
- if (isQuantum === 0x00) {
175
- this.checkBounds(2);
176
- const posX = this.buffer.readUInt8(this.offset++);
177
- const posY = this.buffer.readUInt8(this.offset++);
178
- this.log(`Read Position Int: x=${posX}, y=${posY}`);
179
- posData = { x: posX, y: posY, type: "int" };
180
- } else {
181
- this.checkBounds(8);
182
- const posX = this.buffer.readFloatLE(this.offset);
183
- this.offset += 4;
184
- const posY = this.buffer.readFloatLE(this.offset);
185
- this.offset += 4;
186
- this.log(`Read Position Quantum: x=${posX}, y=${posY}`);
187
- posData = { x: posX, y: posY, type: "quantum" };
188
- }
189
- return posData;
190
- case 0x08: // buffer
191
- case 0x09: // string
192
- const length16 = this.readUInt16();
193
- this.checkBounds(length16);
194
- const value = this.readBytes(length16).toString("utf-8");
195
- this.log(`Read Buffer/String of length ${length16}`);
196
- return value;
197
- case 0x0a: // long buffer
198
- case 0x0b: // long string
199
- const length32 = this.readUInt32();
200
- this.log(`Reading Buffer/String of length ${length32}`);
201
- this.checkBounds(length32);
202
- const longValue = this.readBytes(length32).toString("utf-8");
203
- this.log(`Read Long Buffer/String of length ${length32}`);
204
- return longValue;
205
- case 0x00: // end type, should not appear here
206
- default:
207
- throw new Error("Unexpected DataTypeID in marker definition.");
208
- }
209
- }
210
-
211
- private readMarkerData(typeIDs: DataTypeID[]): any {
212
- const dataObject: any = {};
213
- for (const [index, typeID] of typeIDs.entries()) {
214
- dataObject[`field${index}`] = this.readMarkerField(typeID);
215
- }
216
- return dataObject;
217
- }
218
-
219
- parse(): {
220
- header: Header;
221
- metadata: StaticMetadata;
222
- pointers: Pointers;
223
- strings: Strings;
224
- customData: CustomData;
225
- audio?: Buffer;
226
- cover?: Buffer;
227
- markerDefinitions: MarkerDefinition[];
228
- markers: Marker[];
229
- } {
230
- // Header
231
- const header: Header = {
232
- signature: this.readBytes(4),
233
- version: this.readUInt16(),
234
- reserved: this.readBytes(4),
235
- };
236
- if (header.version == 1) {
237
- throw "Can't parse";
238
- }
239
-
240
- // Static Metadata
241
- const metadata: StaticMetadata = {
242
- sha1: this.readBytes(20),
243
- lastMarkerPos: this.readUInt32(),
244
- noteCount: this.readUInt32(),
245
- markerCount: this.readUInt32(),
246
- difficulty: this.buffer.readUInt8(this.offset++),
247
- rating: this.readUInt16(),
248
- hasAudio: this.buffer.readUInt8(this.offset++) === 1,
249
- hasCover: this.buffer.readUInt8(this.offset++) === 1,
250
- requiresMod: this.buffer.readUInt8(this.offset++) === 1,
251
- };
252
-
253
- // Pointers
254
- const pointers: Pointers = {
255
- customDataOffset: this.readUInt64(),
256
- customDataLength: this.readUInt64(),
257
- audioOffset: this.readUInt64(),
258
- audioLength: this.readUInt64(),
259
- coverOffset: this.readUInt64(),
260
- coverLength: this.readUInt64(),
261
- markerDefinitionsOffset: this.readUInt64(),
262
- markerDefinitionsLength: this.readUInt64(),
263
- markerOffset: this.readUInt64(),
264
- markerLength: this.readUInt64(),
265
- };
266
-
267
- // Log derived pointer values
268
- this.log(`customDataOffset: ${pointers.customDataOffset}`);
269
- this.log(`customDataLength: ${pointers.customDataLength}`);
270
- this.log(`audioOffset: ${pointers.audioOffset}`);
271
- this.log(`audioLength: ${pointers.audioLength}`);
272
- this.log(`coverOffset: ${pointers.coverOffset}`);
273
- this.log(`coverLength: ${pointers.coverLength}`);
274
- this.log(`markerDefinitionsOffset: ${pointers.markerDefinitionsOffset}`);
275
- this.log(`markerDefinitionsLength: ${pointers.markerDefinitionsLength}`);
276
- this.log(`markerOffset: ${pointers.markerOffset}`);
277
- this.log(`markerLength: ${pointers.markerLength}`);
278
-
279
- // Strings
280
- const strings: Strings = {
281
- mapID: this.readString(),
282
- mapName: this.readString(),
283
- songName: this.readString(),
284
- mappers: [],
285
- // mappers: this.readStringList(this.readUInt16()),
286
- };
287
-
288
- let customData: CustomData = { fields: [] };
289
- try {
290
- if (pointers.customDataOffset && pointers.customDataLength) {
291
- this.log(
292
- `Reading Custom Data, Offset: ${pointers.customDataOffset}, Length: ${pointers.customDataLength}`
293
- );
294
- this.offset = Number(pointers.customDataOffset);
295
- const fieldCount = this.readUInt16();
296
- this.log(`Fields: ${fieldCount.toString()}`);
297
- for (let i = 0; i < fieldCount; i++) {
298
- const id = this.readString();
299
- const type = this.buffer.readUInt8(this.offset++) as DataTypeID;
300
- let arrayType: DataTypeID | undefined;
301
- if (type === 0x0c) {
302
- arrayType = this.buffer.readUInt8(this.offset++) as DataTypeID;
303
- }
304
- const length = this.readUInt32();
305
- // this.checkBounds(length);
306
- const value = this.readBytes(length);
307
- customData.fields.push({ id, type, arrayType, value });
308
- }
309
- }
310
- } catch (error) {}
311
-
312
- let audio: Buffer | undefined;
313
- if (
314
- metadata.hasAudio &&
315
- pointers.audioOffset != 0 &&
316
- pointers.audioLength != 0
317
- ) {
318
- this.log(
319
- `Reading Audio Data, Offset: ${pointers.audioOffset}, Length: ${pointers.audioLength}`
320
- );
321
- this.offset = Number(pointers.audioOffset);
322
- this.checkBounds(Number(pointers.audioLength));
323
- audio = this.readBytes(Number(pointers.audioLength));
324
- }
325
-
326
- let cover: Buffer | undefined;
327
- if (
328
- metadata.hasCover &&
329
- pointers.coverOffset != 0 &&
330
- pointers.coverLength != 0
331
- ) {
332
- this.log(
333
- `Reading Cover Data, Offset: ${pointers.coverOffset}, Length: ${pointers.coverLength}`
334
- );
335
- this.offset = Number(pointers.coverOffset);
336
- this.checkBounds(Number(pointers.coverLength));
337
- cover = this.readBytes(Number(pointers.coverLength));
338
- }
339
-
340
- // Marker Definitions
341
- this.log(
342
- `Reading Marker Definitions, Offset: ${pointers.markerDefinitionsOffset}, Length: ${pointers.markerDefinitionsLength}`
343
- );
344
- this.offset = Number(pointers.markerDefinitionsOffset);
345
- const markerDefCount = this.buffer.readUInt8(this.offset++);
346
- const markerDefinitions: MarkerDefinition[] = [];
347
- for (let i = 0; i < markerDefCount; i++) {
348
- const id = this.readString();
349
- const valueCount = this.buffer.readUInt8(this.offset++);
350
- const values: DataTypeID[] = [];
351
- for (let j = 0; j < valueCount; j++) {
352
- values.push(this.buffer.readUInt8(this.offset++) as DataTypeID);
353
- }
354
- markerDefinitions.push({ id, values });
355
- }
356
-
357
- // Markers
358
- this.log(
359
- `Reading Markers, Offset: ${pointers.markerOffset}, Length: ${pointers.markerLength}`
360
- );
361
- this.offset = Number(pointers.markerOffset);
362
- const endOffset = this.offset + Number(pointers.markerLength);
363
-
364
- const markers: Marker[] = [];
365
-
366
- while (this.offset < endOffset) {
367
- const position = this.readUInt32();
368
- const type = this.buffer.readUInt8(this.offset++);
369
- const def = markerDefinitions.find((d) => d.id === "ssp_note"); // Adjust as needed for other marker types
370
- const data = def ? this.readMarkerData(def.values) : {};
371
- markers.push({ position, type, data });
372
- }
373
-
374
- return {
375
- header,
376
- metadata,
377
- pointers,
378
- strings,
379
- customData,
380
- audio,
381
- cover,
382
- markerDefinitions,
383
- markers,
384
- };
385
- }
386
- }
387
-
388
- export type SSPMParsedMap = {
389
- header: Header;
390
- metadata: StaticMetadata;
391
- pointers: Pointers;
392
- strings: Strings;
393
- customData: CustomData;
394
- audio?: Buffer;
395
- cover?: Buffer;
396
- markerDefinitions: MarkerDefinition[];
397
- markers: Marker[];
398
- };
1
+ type DataTypeID =
2
+ | 0x00
3
+ | 0x01
4
+ | 0x02
5
+ | 0x03
6
+ | 0x04
7
+ | 0x05
8
+ | 0x06
9
+ | 0x07
10
+ | 0x08
11
+ | 0x09
12
+ | 0x0a
13
+ | 0x0b
14
+ | 0x0c;
15
+
16
+ interface Header {
17
+ signature: Buffer;
18
+ version: number;
19
+ reserved: Buffer;
20
+ }
21
+
22
+ interface StaticMetadata {
23
+ sha1: Buffer;
24
+ lastMarkerPos: number;
25
+ noteCount: number;
26
+ markerCount: number;
27
+ difficulty: number;
28
+ rating: number;
29
+ hasAudio: boolean;
30
+ hasCover: boolean;
31
+ requiresMod: boolean;
32
+ }
33
+
34
+ interface Pointers {
35
+ customDataOffset: number;
36
+ customDataLength: number;
37
+ audioOffset: number;
38
+ audioLength: number;
39
+ coverOffset: number;
40
+ coverLength: number;
41
+ markerDefinitionsOffset: number;
42
+ markerDefinitionsLength: number;
43
+ markerOffset: number;
44
+ markerLength: number;
45
+ }
46
+
47
+ interface Strings {
48
+ mapID: string;
49
+ mapName: string;
50
+ songName: string;
51
+ mappers: string[];
52
+ }
53
+
54
+ interface CustomField {
55
+ id: string;
56
+ type: DataTypeID;
57
+ arrayType?: DataTypeID;
58
+ value: any;
59
+ }
60
+
61
+ interface CustomData {
62
+ fields: CustomField[];
63
+ }
64
+
65
+ interface MarkerDefinition {
66
+ id: string;
67
+ values: DataTypeID[];
68
+ }
69
+
70
+ interface Marker {
71
+ position: number;
72
+ type: number;
73
+ data: Buffer;
74
+ }
75
+
76
+ export class SSPMParser {
77
+ private buffer: Buffer;
78
+ private offset: number = 0;
79
+
80
+ constructor(buffer: Buffer) {
81
+ this.buffer = buffer;
82
+ }
83
+
84
+ private checkBounds(length: number): void {
85
+ if (this.offset + length > this.buffer.length) {
86
+ throw new RangeError(
87
+ `Attempt to read beyond buffer length: Offset=${this.offset}, Length=${length}, Buffer Length=${this.buffer.length}`
88
+ );
89
+ }
90
+ }
91
+
92
+ private log(message: string): void {
93
+ // console.log(`[Offset: ${this.offset}] ${message}`);
94
+ }
95
+
96
+ private readUInt16(): number {
97
+ this.checkBounds(2);
98
+ const value = this.buffer.readUInt16LE(this.offset);
99
+ this.log(`Read UInt16: ${value}`);
100
+ this.offset += 2;
101
+ return value;
102
+ }
103
+
104
+ private readUInt32(): number {
105
+ this.checkBounds(4);
106
+ const value = this.buffer.readUInt32LE(this.offset);
107
+ this.log(`Read UInt32: ${value}`);
108
+ this.offset += 4;
109
+ return value;
110
+ }
111
+
112
+ private readUInt64(): number {
113
+ this.checkBounds(8);
114
+ const low = this.buffer.readUInt32LE(this.offset);
115
+ const high = this.buffer.readUInt32LE(this.offset + 4);
116
+ this.offset += 8;
117
+ const value = low + high * 2 ** 32;
118
+ this.log(`Read UInt64: ${value} (Low: ${low}, High: ${high})`);
119
+ return value;
120
+ }
121
+
122
+ private readBytes(length: number): Buffer {
123
+ this.checkBounds(length);
124
+ const value = this.buffer.slice(this.offset, this.offset + length);
125
+ this.log(`Read ${length} bytes`);
126
+ this.offset += length;
127
+ return value;
128
+ }
129
+
130
+ private readString(): string {
131
+ const length = this.readUInt16();
132
+ this.checkBounds(length);
133
+ const value = this.readBytes(length).toString("utf-8");
134
+ this.log(`Read String of length ${length}: ${value}`);
135
+ return value;
136
+ }
137
+
138
+ private readStringList(count: number): string[] {
139
+ console.log("[DANGER] Reading String List");
140
+ const list: string[] = [];
141
+ for (let i = 0; i < count; i++) {
142
+ list.push(this.readString());
143
+ }
144
+ return list;
145
+ }
146
+
147
+ private readMarkerField(typeID: DataTypeID): any {
148
+ switch (typeID) {
149
+ case 0x01: // 1 byte integer
150
+ this.checkBounds(1);
151
+ const int8 = this.buffer.readInt8(this.offset++);
152
+ this.log(`Read Int8: ${int8}`);
153
+ return int8;
154
+ case 0x02: // 2 byte uint
155
+ return this.readUInt16();
156
+ case 0x03: // 4 byte uint
157
+ return this.readUInt32();
158
+ case 0x04: // 8 byte uint
159
+ return this.readUInt64();
160
+ case 0x05: // 4 byte float
161
+ this.checkBounds(4);
162
+ const floatVal32 = this.buffer.readFloatLE(this.offset);
163
+ this.log(`Read Float32: ${floatVal32}`);
164
+ this.offset += 4;
165
+ return floatVal32;
166
+ case 0x06: // 8 byte float
167
+ this.checkBounds(8);
168
+ const floatVal64 = this.buffer.readDoubleLE(this.offset);
169
+ this.log(`Read Float64: ${floatVal64}`);
170
+ this.offset += 8;
171
+ return floatVal64;
172
+ case 0x07: // position type
173
+ const isQuantum = this.buffer.readUInt8(this.offset++);
174
+ let posData;
175
+ if (isQuantum === 0x00) {
176
+ this.checkBounds(2);
177
+ const posX = this.buffer.readUInt8(this.offset++);
178
+ const posY = this.buffer.readUInt8(this.offset++);
179
+ this.log(`Read Position Int: x=${posX}, y=${posY}`);
180
+ posData = { x: posX, y: posY, type: "int" };
181
+ } else {
182
+ this.checkBounds(8);
183
+ const posX = this.buffer.readFloatLE(this.offset);
184
+ this.offset += 4;
185
+ const posY = this.buffer.readFloatLE(this.offset);
186
+ this.offset += 4;
187
+ this.log(`Read Position Quantum: x=${posX}, y=${posY}`);
188
+ posData = { x: posX, y: posY, type: "quantum" };
189
+ }
190
+ return posData;
191
+ case 0x08: // buffer
192
+ case 0x09: // string
193
+ const length16 = this.readUInt16();
194
+ this.checkBounds(length16);
195
+ const value = this.readBytes(length16).toString("utf-8");
196
+ this.log(`Read Buffer/String of length ${length16}`);
197
+ return value;
198
+ case 0x0a: // long buffer
199
+ case 0x0b: // long string
200
+ const length32 = this.readUInt32();
201
+ this.log(`Reading Buffer/String of length ${length32}`);
202
+ this.checkBounds(length32);
203
+ const longValue = this.readBytes(length32).toString("utf-8");
204
+ this.log(`Read Long Buffer/String of length ${length32}`);
205
+ return longValue;
206
+ case 0x00: // end type, should not appear here
207
+ default:
208
+ throw new Error("Unexpected DataTypeID in marker definition.");
209
+ }
210
+ }
211
+
212
+ private readMarkerData(typeIDs: DataTypeID[]): any {
213
+ const dataObject: any = {};
214
+ for (const [index, typeID] of typeIDs.entries()) {
215
+ dataObject[`field${index}`] = this.readMarkerField(typeID);
216
+ }
217
+ return dataObject;
218
+ }
219
+
220
+ parse(): {
221
+ header: Header;
222
+ metadata: StaticMetadata;
223
+ pointers: Pointers;
224
+ strings: Strings;
225
+ customData: CustomData;
226
+ audio?: Buffer;
227
+ cover?: Buffer;
228
+ markerDefinitions: MarkerDefinition[];
229
+ markers: Marker[];
230
+ } {
231
+ // Header
232
+ const header: Header = {
233
+ signature: this.readBytes(4),
234
+ version: this.readUInt16(),
235
+ reserved: this.readBytes(4),
236
+ };
237
+ if (header.version == 1) {
238
+ throw "Can't parse";
239
+ }
240
+
241
+ // Static Metadata
242
+ const metadata: StaticMetadata = {
243
+ sha1: this.readBytes(20),
244
+ lastMarkerPos: this.readUInt32(),
245
+ noteCount: this.readUInt32(),
246
+ markerCount: this.readUInt32(),
247
+ difficulty: this.buffer.readUInt8(this.offset++),
248
+ rating: this.readUInt16(),
249
+ hasAudio: this.buffer.readUInt8(this.offset++) === 1,
250
+ hasCover: this.buffer.readUInt8(this.offset++) === 1,
251
+ requiresMod: this.buffer.readUInt8(this.offset++) === 1,
252
+ };
253
+
254
+ // Pointers
255
+ const pointers: Pointers = {
256
+ customDataOffset: this.readUInt64(),
257
+ customDataLength: this.readUInt64(),
258
+ audioOffset: this.readUInt64(),
259
+ audioLength: this.readUInt64(),
260
+ coverOffset: this.readUInt64(),
261
+ coverLength: this.readUInt64(),
262
+ markerDefinitionsOffset: this.readUInt64(),
263
+ markerDefinitionsLength: this.readUInt64(),
264
+ markerOffset: this.readUInt64(),
265
+ markerLength: this.readUInt64(),
266
+ };
267
+
268
+ // Log derived pointer values
269
+ this.log(`customDataOffset: ${pointers.customDataOffset}`);
270
+ this.log(`customDataLength: ${pointers.customDataLength}`);
271
+ this.log(`audioOffset: ${pointers.audioOffset}`);
272
+ this.log(`audioLength: ${pointers.audioLength}`);
273
+ this.log(`coverOffset: ${pointers.coverOffset}`);
274
+ this.log(`coverLength: ${pointers.coverLength}`);
275
+ this.log(`markerDefinitionsOffset: ${pointers.markerDefinitionsOffset}`);
276
+ this.log(`markerDefinitionsLength: ${pointers.markerDefinitionsLength}`);
277
+ this.log(`markerOffset: ${pointers.markerOffset}`);
278
+ this.log(`markerLength: ${pointers.markerLength}`);
279
+
280
+ // Strings
281
+ const strings: Strings = {
282
+ mapID: this.readString(),
283
+ mapName: this.readString(),
284
+ songName: this.readString(),
285
+ mappers: this.readStringList(this.readUInt16()),
286
+ };
287
+
288
+ let customData: CustomData = { fields: [] };
289
+ try {
290
+ if (pointers.customDataOffset && pointers.customDataLength) {
291
+ this.log(
292
+ `Reading Custom Data, Offset: ${pointers.customDataOffset}, Length: ${pointers.customDataLength}`
293
+ );
294
+ this.offset = Number(pointers.customDataOffset);
295
+ const fieldCount = this.readUInt16();
296
+ this.log(`Fields: ${fieldCount.toString()}`);
297
+ for (let i = 0; i < fieldCount; i++) {
298
+ const id = this.readString();
299
+ const type = this.buffer.readUInt8(this.offset++) as DataTypeID;
300
+ let arrayType: DataTypeID | undefined;
301
+ if (type === 0x0c) {
302
+ arrayType = this.buffer.readUInt8(this.offset++) as DataTypeID;
303
+ }
304
+ const length = this.readUInt32();
305
+ // this.checkBounds(length);
306
+ const value = this.readBytes(length);
307
+ customData.fields.push({ id, type, arrayType, value });
308
+ }
309
+ }
310
+ } catch (error) {}
311
+
312
+ let audio: Buffer | undefined;
313
+ if (
314
+ metadata.hasAudio &&
315
+ pointers.audioOffset != 0 &&
316
+ pointers.audioLength != 0
317
+ ) {
318
+ this.log(
319
+ `Reading Audio Data, Offset: ${pointers.audioOffset}, Length: ${pointers.audioLength}`
320
+ );
321
+ this.offset = Number(pointers.audioOffset);
322
+ this.checkBounds(Number(pointers.audioLength));
323
+ audio = this.readBytes(Number(pointers.audioLength));
324
+ }
325
+
326
+ let cover: Buffer | undefined;
327
+ if (
328
+ metadata.hasCover &&
329
+ pointers.coverOffset != 0 &&
330
+ pointers.coverLength != 0
331
+ ) {
332
+ this.log(
333
+ `Reading Cover Data, Offset: ${pointers.coverOffset}, Length: ${pointers.coverLength}`
334
+ );
335
+ this.offset = Number(pointers.coverOffset);
336
+ this.checkBounds(Number(pointers.coverLength));
337
+ cover = this.readBytes(Number(pointers.coverLength));
338
+ }
339
+
340
+ // Marker Definitions
341
+ this.log(
342
+ `Reading Marker Definitions, Offset: ${pointers.markerDefinitionsOffset}, Length: ${pointers.markerDefinitionsLength}`
343
+ );
344
+ this.offset = Number(pointers.markerDefinitionsOffset);
345
+ const markerDefCount = this.buffer.readUInt8(this.offset++);
346
+ const markerDefinitions: MarkerDefinition[] = [];
347
+ for (let i = 0; i < markerDefCount; i++) {
348
+ const id = this.readString();
349
+ const valueCount = this.buffer.readUInt8(this.offset++);
350
+ const values: DataTypeID[] = [];
351
+ for (let j = 0; j < valueCount; j++) {
352
+ values.push(this.buffer.readUInt8(this.offset++) as DataTypeID);
353
+ }
354
+ markerDefinitions.push({ id, values });
355
+ }
356
+
357
+ // Markers
358
+ this.log(
359
+ `Reading Markers, Offset: ${pointers.markerOffset}, Length: ${pointers.markerLength}`
360
+ );
361
+ this.offset = Number(pointers.markerOffset);
362
+ const endOffset = this.offset + Number(pointers.markerLength);
363
+
364
+ const markers: Marker[] = [];
365
+
366
+ while (this.offset < endOffset) {
367
+ const position = this.readUInt32();
368
+ const type = this.buffer.readUInt8(this.offset++);
369
+ const def = markerDefinitions.find((d) => d.id === "ssp_note"); // Adjust as needed for other marker types
370
+ const data = def ? this.readMarkerData(def.values) : {};
371
+ markers.push({ position, type, data });
372
+ }
373
+
374
+ return {
375
+ header,
376
+ metadata,
377
+ pointers,
378
+ strings,
379
+ customData,
380
+ audio,
381
+ cover,
382
+ markerDefinitions,
383
+ markers,
384
+ };
385
+ }
386
+ }
387
+
388
+ export type SSPMParsedMap = {
389
+ header: Header;
390
+ metadata: StaticMetadata;
391
+ pointers: Pointers;
392
+ strings: Strings;
393
+ customData: CustomData;
394
+ audio?: Buffer;
395
+ cover?: Buffer;
396
+ markerDefinitions: MarkerDefinition[];
397
+ markers: Marker[];
398
+ };