osu-stable-db 0.1.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.
- package/README.md +141 -0
- package/dist/index.d.mts +15 -0
- package/dist/index.mjs +2 -0
- package/dist/node.d.mts +12 -0
- package/dist/node.mjs +23 -0
- package/dist/scores-7qsAMzUc.mjs +665 -0
- package/dist/types-DiZMN3fQ.d.mts +547 -0
- package/package.json +45 -0
|
@@ -0,0 +1,665 @@
|
|
|
1
|
+
//#region src/types.ts
|
|
2
|
+
/**
|
|
3
|
+
* The minimum legacy database version supported by this library.
|
|
4
|
+
*
|
|
5
|
+
* Only the latest stable structure introduced in version 20250107 is modeled.
|
|
6
|
+
*/
|
|
7
|
+
const MINIMUM_SUPPORTED_VERSION = 20250107;
|
|
8
|
+
/**
|
|
9
|
+
* Legacy gameplay mode identifiers used by osu!stable database files.
|
|
10
|
+
*/
|
|
11
|
+
const GameplayModes = {
|
|
12
|
+
Osu: 0,
|
|
13
|
+
Taiko: 1,
|
|
14
|
+
Catch: 2,
|
|
15
|
+
Mania: 3
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Beatmap ranked status values stored in osu!.db.
|
|
19
|
+
*/
|
|
20
|
+
const RankedStatuses = {
|
|
21
|
+
Unknown: 0,
|
|
22
|
+
Unsubmitted: 1,
|
|
23
|
+
Pending: 2,
|
|
24
|
+
Unused: 3,
|
|
25
|
+
Ranked: 4,
|
|
26
|
+
Approved: 5,
|
|
27
|
+
Qualified: 6,
|
|
28
|
+
Loved: 7
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Grade values stored in osu!.db for each ruleset.
|
|
32
|
+
*
|
|
33
|
+
* This matches legacy stable grade byte values used by osu!.db.
|
|
34
|
+
*/
|
|
35
|
+
const Grades = {
|
|
36
|
+
XH: 0,
|
|
37
|
+
SH: 1,
|
|
38
|
+
X: 2,
|
|
39
|
+
S: 3,
|
|
40
|
+
A: 4,
|
|
41
|
+
B: 5,
|
|
42
|
+
C: 6,
|
|
43
|
+
D: 7,
|
|
44
|
+
F: 8,
|
|
45
|
+
N: 9
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* User permission flags stored in osu!.db.
|
|
49
|
+
*/
|
|
50
|
+
const UserPermissions = {
|
|
51
|
+
None: 0,
|
|
52
|
+
Normal: 1,
|
|
53
|
+
Moderator: 2,
|
|
54
|
+
Supporter: 4,
|
|
55
|
+
Friend: 8,
|
|
56
|
+
Peppy: 16,
|
|
57
|
+
WorldCupStaff: 32
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Legacy mod flags used by scores.db, osu!.db star ratings, and replay-derived data.
|
|
61
|
+
*
|
|
62
|
+
* This is a flag enum expressed as a const object. Values are intended to be combined
|
|
63
|
+
* with bitwise operations such as `Hidden | HardRock`.
|
|
64
|
+
*/
|
|
65
|
+
const Mods = {
|
|
66
|
+
None: 0,
|
|
67
|
+
NoFail: 1,
|
|
68
|
+
Easy: 2,
|
|
69
|
+
TouchDevice: 4,
|
|
70
|
+
Hidden: 8,
|
|
71
|
+
HardRock: 16,
|
|
72
|
+
SuddenDeath: 32,
|
|
73
|
+
DoubleTime: 64,
|
|
74
|
+
Relax: 128,
|
|
75
|
+
HalfTime: 256,
|
|
76
|
+
Nightcore: 512,
|
|
77
|
+
Flashlight: 1024,
|
|
78
|
+
Autoplay: 2048,
|
|
79
|
+
SpunOut: 4096,
|
|
80
|
+
Autopilot: 8192,
|
|
81
|
+
Perfect: 16384,
|
|
82
|
+
Key4: 32768,
|
|
83
|
+
Key5: 65536,
|
|
84
|
+
Key6: 131072,
|
|
85
|
+
Key7: 262144,
|
|
86
|
+
Key8: 524288,
|
|
87
|
+
FadeIn: 1048576,
|
|
88
|
+
Random: 2097152,
|
|
89
|
+
Cinema: 4194304,
|
|
90
|
+
TargetPractice: 8388608,
|
|
91
|
+
Key9: 16777216,
|
|
92
|
+
KeyCoop: 33554432,
|
|
93
|
+
Key1: 67108864,
|
|
94
|
+
Key3: 134217728,
|
|
95
|
+
Key2: 268435456,
|
|
96
|
+
ScoreV2: 536870912,
|
|
97
|
+
Mirror: 1073741824
|
|
98
|
+
};
|
|
99
|
+
//#endregion
|
|
100
|
+
//#region src/core/binary.ts
|
|
101
|
+
const textEncoder = new TextEncoder();
|
|
102
|
+
const textDecoder = new TextDecoder("utf-8", { ignoreBOM: true });
|
|
103
|
+
function toUint8Array(input) {
|
|
104
|
+
return input instanceof Uint8Array ? input : new Uint8Array(input);
|
|
105
|
+
}
|
|
106
|
+
var BinaryReader = class {
|
|
107
|
+
bytes;
|
|
108
|
+
view;
|
|
109
|
+
offset = 0;
|
|
110
|
+
constructor(input) {
|
|
111
|
+
this.bytes = toUint8Array(input);
|
|
112
|
+
this.view = new DataView(this.bytes.buffer, this.bytes.byteOffset, this.bytes.byteLength);
|
|
113
|
+
}
|
|
114
|
+
get position() {
|
|
115
|
+
return this.offset;
|
|
116
|
+
}
|
|
117
|
+
get length() {
|
|
118
|
+
return this.bytes.byteLength;
|
|
119
|
+
}
|
|
120
|
+
get remaining() {
|
|
121
|
+
return this.length - this.offset;
|
|
122
|
+
}
|
|
123
|
+
readByte() {
|
|
124
|
+
this.ensureAvailable(1);
|
|
125
|
+
const value = this.view.getUint8(this.offset);
|
|
126
|
+
this.offset += 1;
|
|
127
|
+
return value;
|
|
128
|
+
}
|
|
129
|
+
readBoolean() {
|
|
130
|
+
return this.readByte() !== 0;
|
|
131
|
+
}
|
|
132
|
+
readInt16() {
|
|
133
|
+
this.ensureAvailable(2);
|
|
134
|
+
const value = this.view.getInt16(this.offset, true);
|
|
135
|
+
this.offset += 2;
|
|
136
|
+
return value;
|
|
137
|
+
}
|
|
138
|
+
readUInt16() {
|
|
139
|
+
this.ensureAvailable(2);
|
|
140
|
+
const value = this.view.getUint16(this.offset, true);
|
|
141
|
+
this.offset += 2;
|
|
142
|
+
return value;
|
|
143
|
+
}
|
|
144
|
+
readInt32() {
|
|
145
|
+
this.ensureAvailable(4);
|
|
146
|
+
const value = this.view.getInt32(this.offset, true);
|
|
147
|
+
this.offset += 4;
|
|
148
|
+
return value;
|
|
149
|
+
}
|
|
150
|
+
readUInt32() {
|
|
151
|
+
this.ensureAvailable(4);
|
|
152
|
+
const value = this.view.getUint32(this.offset, true);
|
|
153
|
+
this.offset += 4;
|
|
154
|
+
return value;
|
|
155
|
+
}
|
|
156
|
+
readInt64() {
|
|
157
|
+
this.ensureAvailable(8);
|
|
158
|
+
const value = this.view.getBigInt64(this.offset, true);
|
|
159
|
+
this.offset += 8;
|
|
160
|
+
return value;
|
|
161
|
+
}
|
|
162
|
+
readSingle() {
|
|
163
|
+
this.ensureAvailable(4);
|
|
164
|
+
const value = this.view.getFloat32(this.offset, true);
|
|
165
|
+
this.offset += 4;
|
|
166
|
+
return value;
|
|
167
|
+
}
|
|
168
|
+
readDouble() {
|
|
169
|
+
this.ensureAvailable(8);
|
|
170
|
+
const value = this.view.getFloat64(this.offset, true);
|
|
171
|
+
this.offset += 8;
|
|
172
|
+
return value;
|
|
173
|
+
}
|
|
174
|
+
readDateTimeTicks() {
|
|
175
|
+
return this.readInt64();
|
|
176
|
+
}
|
|
177
|
+
readBytes(length) {
|
|
178
|
+
if (!Number.isInteger(length) || length < 0) throw new RangeError(`Byte length must be a non-negative integer, got ${length}.`);
|
|
179
|
+
this.ensureAvailable(length);
|
|
180
|
+
const value = this.bytes.slice(this.offset, this.offset + length);
|
|
181
|
+
this.offset += length;
|
|
182
|
+
return value;
|
|
183
|
+
}
|
|
184
|
+
readUleb128() {
|
|
185
|
+
let value = 0;
|
|
186
|
+
let shift = 0;
|
|
187
|
+
while (true) {
|
|
188
|
+
const byte = this.readByte();
|
|
189
|
+
value |= (byte & 127) << shift;
|
|
190
|
+
if ((byte & 128) === 0) return value;
|
|
191
|
+
shift += 7;
|
|
192
|
+
if (shift > 35) throw new RangeError("ULEB128 value is too large for a JavaScript number.");
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
readString() {
|
|
196
|
+
const marker = this.readByte();
|
|
197
|
+
if (marker === 0) return null;
|
|
198
|
+
if (marker !== 11) throw new Error(`Invalid string marker 0x${marker.toString(16)}.`);
|
|
199
|
+
const length = this.readUleb128();
|
|
200
|
+
const value = this.readBytes(length);
|
|
201
|
+
return textDecoder.decode(value);
|
|
202
|
+
}
|
|
203
|
+
readIntFloatPair() {
|
|
204
|
+
const intMarker = this.readByte();
|
|
205
|
+
if (intMarker !== 8) throw new Error(`Invalid Int-Float pair int marker 0x${intMarker.toString(16)}.`);
|
|
206
|
+
const mods = this.readInt32();
|
|
207
|
+
const floatMarker = this.readByte();
|
|
208
|
+
if (floatMarker !== 12) throw new Error(`Invalid Int-Float pair float marker 0x${floatMarker.toString(16)}.`);
|
|
209
|
+
return {
|
|
210
|
+
mods,
|
|
211
|
+
starRating: this.readSingle()
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
readTimingPoint() {
|
|
215
|
+
return {
|
|
216
|
+
bpm: this.readDouble(),
|
|
217
|
+
offsetMs: this.readDouble(),
|
|
218
|
+
isUninherited: this.readBoolean()
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
ensureAvailable(byteLength) {
|
|
222
|
+
if (this.offset + byteLength > this.length) throw new RangeError(`Attempted to read ${byteLength} byte(s) at offset ${this.offset}, but only ${this.remaining} remain.`);
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
var BinaryWriter = class {
|
|
226
|
+
buffer;
|
|
227
|
+
view;
|
|
228
|
+
bytes;
|
|
229
|
+
offset = 0;
|
|
230
|
+
constructor(initialCapacity = 64) {
|
|
231
|
+
if (!Number.isInteger(initialCapacity) || initialCapacity <= 0) throw new RangeError(`Initial capacity must be a positive integer, got ${initialCapacity}.`);
|
|
232
|
+
this.buffer = new ArrayBuffer(initialCapacity);
|
|
233
|
+
this.view = new DataView(this.buffer);
|
|
234
|
+
this.bytes = new Uint8Array(this.buffer);
|
|
235
|
+
}
|
|
236
|
+
get position() {
|
|
237
|
+
return this.offset;
|
|
238
|
+
}
|
|
239
|
+
writeByte(value) {
|
|
240
|
+
this.ensureCapacity(1);
|
|
241
|
+
this.view.setUint8(this.offset, value);
|
|
242
|
+
this.offset += 1;
|
|
243
|
+
}
|
|
244
|
+
writeBoolean(value) {
|
|
245
|
+
this.writeByte(value ? 1 : 0);
|
|
246
|
+
}
|
|
247
|
+
writeInt16(value) {
|
|
248
|
+
this.ensureCapacity(2);
|
|
249
|
+
this.view.setInt16(this.offset, value, true);
|
|
250
|
+
this.offset += 2;
|
|
251
|
+
}
|
|
252
|
+
writeUInt16(value) {
|
|
253
|
+
this.ensureCapacity(2);
|
|
254
|
+
this.view.setUint16(this.offset, value, true);
|
|
255
|
+
this.offset += 2;
|
|
256
|
+
}
|
|
257
|
+
writeInt32(value) {
|
|
258
|
+
this.ensureCapacity(4);
|
|
259
|
+
this.view.setInt32(this.offset, value, true);
|
|
260
|
+
this.offset += 4;
|
|
261
|
+
}
|
|
262
|
+
writeUInt32(value) {
|
|
263
|
+
this.ensureCapacity(4);
|
|
264
|
+
this.view.setUint32(this.offset, value, true);
|
|
265
|
+
this.offset += 4;
|
|
266
|
+
}
|
|
267
|
+
writeInt64(value) {
|
|
268
|
+
this.ensureCapacity(8);
|
|
269
|
+
this.view.setBigInt64(this.offset, value, true);
|
|
270
|
+
this.offset += 8;
|
|
271
|
+
}
|
|
272
|
+
writeSingle(value) {
|
|
273
|
+
this.ensureCapacity(4);
|
|
274
|
+
this.view.setFloat32(this.offset, value, true);
|
|
275
|
+
this.offset += 4;
|
|
276
|
+
}
|
|
277
|
+
writeDouble(value) {
|
|
278
|
+
this.ensureCapacity(8);
|
|
279
|
+
this.view.setFloat64(this.offset, value, true);
|
|
280
|
+
this.offset += 8;
|
|
281
|
+
}
|
|
282
|
+
writeDateTimeTicks(value) {
|
|
283
|
+
this.writeInt64(value);
|
|
284
|
+
}
|
|
285
|
+
writeBytes(value) {
|
|
286
|
+
this.ensureCapacity(value.byteLength);
|
|
287
|
+
this.bytes.set(value, this.offset);
|
|
288
|
+
this.offset += value.byteLength;
|
|
289
|
+
}
|
|
290
|
+
writeUleb128(value) {
|
|
291
|
+
if (!Number.isInteger(value) || value < 0) throw new RangeError(`ULEB128 value must be a non-negative integer, got ${value}.`);
|
|
292
|
+
let remaining = value;
|
|
293
|
+
do {
|
|
294
|
+
let byte = remaining & 127;
|
|
295
|
+
remaining >>>= 7;
|
|
296
|
+
if (remaining !== 0) byte |= 128;
|
|
297
|
+
this.writeByte(byte);
|
|
298
|
+
} while (remaining !== 0);
|
|
299
|
+
}
|
|
300
|
+
writeString(value) {
|
|
301
|
+
if (value === null) {
|
|
302
|
+
this.writeByte(0);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
this.writeByte(11);
|
|
306
|
+
const encoded = textEncoder.encode(value);
|
|
307
|
+
this.writeUleb128(encoded.byteLength);
|
|
308
|
+
this.writeBytes(encoded);
|
|
309
|
+
}
|
|
310
|
+
writeIntFloatPair(value) {
|
|
311
|
+
this.writeByte(8);
|
|
312
|
+
this.writeInt32(value.mods);
|
|
313
|
+
this.writeByte(12);
|
|
314
|
+
this.writeSingle(value.starRating);
|
|
315
|
+
}
|
|
316
|
+
writeTimingPoint(value) {
|
|
317
|
+
this.writeDouble(value.bpm);
|
|
318
|
+
this.writeDouble(value.offsetMs);
|
|
319
|
+
this.writeBoolean(value.isUninherited);
|
|
320
|
+
}
|
|
321
|
+
toUint8Array() {
|
|
322
|
+
return this.bytes.slice(0, this.offset);
|
|
323
|
+
}
|
|
324
|
+
ensureCapacity(additionalBytes) {
|
|
325
|
+
const required = this.offset + additionalBytes;
|
|
326
|
+
if (required <= this.buffer.byteLength) return;
|
|
327
|
+
let nextCapacity = this.buffer.byteLength;
|
|
328
|
+
while (nextCapacity < required) nextCapacity *= 2;
|
|
329
|
+
const nextBuffer = new ArrayBuffer(nextCapacity);
|
|
330
|
+
const nextBytes = new Uint8Array(nextBuffer);
|
|
331
|
+
nextBytes.set(this.bytes.subarray(0, this.offset));
|
|
332
|
+
this.buffer = nextBuffer;
|
|
333
|
+
this.bytes = nextBytes;
|
|
334
|
+
this.view = new DataView(nextBuffer);
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
//#endregion
|
|
338
|
+
//#region src/db/shared.ts
|
|
339
|
+
function createReader(input) {
|
|
340
|
+
return new BinaryReader(input);
|
|
341
|
+
}
|
|
342
|
+
function createWriter() {
|
|
343
|
+
return new BinaryWriter();
|
|
344
|
+
}
|
|
345
|
+
function assertSupportedVersion(version) {
|
|
346
|
+
if (version < 20250107) throw new RangeError(`Unsupported legacy database version ${version}. Minimum supported version is ${MINIMUM_SUPPORTED_VERSION}.`);
|
|
347
|
+
}
|
|
348
|
+
//#endregion
|
|
349
|
+
//#region src/db/collection.ts
|
|
350
|
+
function readCollectionDatabase(input) {
|
|
351
|
+
const reader = createReader(input);
|
|
352
|
+
const version = reader.readInt32();
|
|
353
|
+
assertSupportedVersion(version);
|
|
354
|
+
const collectionCount = reader.readInt32();
|
|
355
|
+
const collections = [];
|
|
356
|
+
for (let index = 0; index < collectionCount; index += 1) {
|
|
357
|
+
const name = reader.readString();
|
|
358
|
+
const beatmapCount = reader.readInt32();
|
|
359
|
+
const beatmapMd5Hashes = [];
|
|
360
|
+
for (let beatmapIndex = 0; beatmapIndex < beatmapCount; beatmapIndex += 1) beatmapMd5Hashes.push(reader.readString());
|
|
361
|
+
collections.push({
|
|
362
|
+
name,
|
|
363
|
+
beatmapMd5Hashes
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
return {
|
|
367
|
+
version,
|
|
368
|
+
collections
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
function writeCollectionDatabase(database) {
|
|
372
|
+
assertSupportedVersion(database.version);
|
|
373
|
+
const writer = createWriter();
|
|
374
|
+
writer.writeInt32(database.version);
|
|
375
|
+
writer.writeInt32(database.collections.length);
|
|
376
|
+
for (const collection of database.collections) {
|
|
377
|
+
writer.writeString(collection.name);
|
|
378
|
+
writer.writeInt32(collection.beatmapMd5Hashes.length);
|
|
379
|
+
for (const beatmapMd5Hash of collection.beatmapMd5Hashes) writer.writeString(beatmapMd5Hash);
|
|
380
|
+
}
|
|
381
|
+
return writer.toUint8Array();
|
|
382
|
+
}
|
|
383
|
+
//#endregion
|
|
384
|
+
//#region src/db/osu.ts
|
|
385
|
+
function readIntFloatPairArray(reader) {
|
|
386
|
+
const count = reader.readInt32();
|
|
387
|
+
const values = [];
|
|
388
|
+
for (let index = 0; index < count; index += 1) values.push(reader.readIntFloatPair());
|
|
389
|
+
return values;
|
|
390
|
+
}
|
|
391
|
+
function writeIntFloatPairArray(writer, values) {
|
|
392
|
+
writer.writeInt32(values.length);
|
|
393
|
+
for (const value of values) writer.writeIntFloatPair(value);
|
|
394
|
+
}
|
|
395
|
+
function readTimingPointArray(reader) {
|
|
396
|
+
const count = reader.readInt32();
|
|
397
|
+
const values = [];
|
|
398
|
+
for (let index = 0; index < count; index += 1) values.push(reader.readTimingPoint());
|
|
399
|
+
return values;
|
|
400
|
+
}
|
|
401
|
+
function writeTimingPointArray(writer, values) {
|
|
402
|
+
writer.writeInt32(values.length);
|
|
403
|
+
for (const value of values) writer.writeTimingPoint(value);
|
|
404
|
+
}
|
|
405
|
+
function readBeatmapEntry(reader) {
|
|
406
|
+
return {
|
|
407
|
+
artist: reader.readString(),
|
|
408
|
+
artistUnicode: reader.readString(),
|
|
409
|
+
title: reader.readString(),
|
|
410
|
+
titleUnicode: reader.readString(),
|
|
411
|
+
creator: reader.readString(),
|
|
412
|
+
difficultyName: reader.readString(),
|
|
413
|
+
audioFileName: reader.readString(),
|
|
414
|
+
md5Hash: reader.readString(),
|
|
415
|
+
osuFileName: reader.readString(),
|
|
416
|
+
rankedStatus: reader.readByte(),
|
|
417
|
+
hitCircleCount: reader.readUInt16(),
|
|
418
|
+
sliderCount: reader.readUInt16(),
|
|
419
|
+
spinnerCount: reader.readUInt16(),
|
|
420
|
+
lastModificationTime: reader.readDateTimeTicks(),
|
|
421
|
+
approachRate: reader.readSingle(),
|
|
422
|
+
circleSize: reader.readSingle(),
|
|
423
|
+
hpDrain: reader.readSingle(),
|
|
424
|
+
overallDifficulty: reader.readSingle(),
|
|
425
|
+
sliderVelocity: reader.readDouble(),
|
|
426
|
+
standardStarRatings: readIntFloatPairArray(reader),
|
|
427
|
+
taikoStarRatings: readIntFloatPairArray(reader),
|
|
428
|
+
catchStarRatings: readIntFloatPairArray(reader),
|
|
429
|
+
maniaStarRatings: readIntFloatPairArray(reader),
|
|
430
|
+
drainTimeSeconds: reader.readInt32(),
|
|
431
|
+
totalTimeMs: reader.readInt32(),
|
|
432
|
+
previewOffsetMs: reader.readInt32(),
|
|
433
|
+
timingPoints: readTimingPointArray(reader),
|
|
434
|
+
difficultyId: reader.readInt32(),
|
|
435
|
+
beatmapId: reader.readInt32(),
|
|
436
|
+
threadId: reader.readInt32(),
|
|
437
|
+
standardGrade: reader.readByte(),
|
|
438
|
+
taikoGrade: reader.readByte(),
|
|
439
|
+
catchGrade: reader.readByte(),
|
|
440
|
+
maniaGrade: reader.readByte(),
|
|
441
|
+
localOffset: reader.readInt16(),
|
|
442
|
+
stackLeniency: reader.readSingle(),
|
|
443
|
+
gameplayMode: reader.readByte(),
|
|
444
|
+
source: reader.readString(),
|
|
445
|
+
tags: reader.readString(),
|
|
446
|
+
onlineOffset: reader.readInt16(),
|
|
447
|
+
titleFont: reader.readString(),
|
|
448
|
+
isUnplayed: reader.readBoolean(),
|
|
449
|
+
lastPlayedAt: reader.readDateTimeTicks(),
|
|
450
|
+
isOsz2: reader.readBoolean(),
|
|
451
|
+
beatmapFolderName: reader.readString(),
|
|
452
|
+
lastCheckedAgainstRepositoryAt: reader.readDateTimeTicks(),
|
|
453
|
+
ignoreBeatmapSound: reader.readBoolean(),
|
|
454
|
+
ignoreBeatmapSkin: reader.readBoolean(),
|
|
455
|
+
disableStoryboard: reader.readBoolean(),
|
|
456
|
+
disableVideo: reader.readBoolean(),
|
|
457
|
+
visualOverride: reader.readBoolean(),
|
|
458
|
+
lastModificationTimeUnknown: reader.readInt32(),
|
|
459
|
+
maniaScrollSpeed: reader.readByte()
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
function writeBeatmapEntry(writer, beatmap) {
|
|
463
|
+
writer.writeString(beatmap.artist);
|
|
464
|
+
writer.writeString(beatmap.artistUnicode);
|
|
465
|
+
writer.writeString(beatmap.title);
|
|
466
|
+
writer.writeString(beatmap.titleUnicode);
|
|
467
|
+
writer.writeString(beatmap.creator);
|
|
468
|
+
writer.writeString(beatmap.difficultyName);
|
|
469
|
+
writer.writeString(beatmap.audioFileName);
|
|
470
|
+
writer.writeString(beatmap.md5Hash);
|
|
471
|
+
writer.writeString(beatmap.osuFileName);
|
|
472
|
+
writer.writeByte(beatmap.rankedStatus);
|
|
473
|
+
writer.writeUInt16(beatmap.hitCircleCount);
|
|
474
|
+
writer.writeUInt16(beatmap.sliderCount);
|
|
475
|
+
writer.writeUInt16(beatmap.spinnerCount);
|
|
476
|
+
writer.writeDateTimeTicks(beatmap.lastModificationTime);
|
|
477
|
+
writer.writeSingle(beatmap.approachRate);
|
|
478
|
+
writer.writeSingle(beatmap.circleSize);
|
|
479
|
+
writer.writeSingle(beatmap.hpDrain);
|
|
480
|
+
writer.writeSingle(beatmap.overallDifficulty);
|
|
481
|
+
writer.writeDouble(beatmap.sliderVelocity);
|
|
482
|
+
writeIntFloatPairArray(writer, beatmap.standardStarRatings);
|
|
483
|
+
writeIntFloatPairArray(writer, beatmap.taikoStarRatings);
|
|
484
|
+
writeIntFloatPairArray(writer, beatmap.catchStarRatings);
|
|
485
|
+
writeIntFloatPairArray(writer, beatmap.maniaStarRatings);
|
|
486
|
+
writer.writeInt32(beatmap.drainTimeSeconds);
|
|
487
|
+
writer.writeInt32(beatmap.totalTimeMs);
|
|
488
|
+
writer.writeInt32(beatmap.previewOffsetMs);
|
|
489
|
+
writeTimingPointArray(writer, beatmap.timingPoints);
|
|
490
|
+
writer.writeInt32(beatmap.difficultyId);
|
|
491
|
+
writer.writeInt32(beatmap.beatmapId);
|
|
492
|
+
writer.writeInt32(beatmap.threadId);
|
|
493
|
+
writer.writeByte(beatmap.standardGrade);
|
|
494
|
+
writer.writeByte(beatmap.taikoGrade);
|
|
495
|
+
writer.writeByte(beatmap.catchGrade);
|
|
496
|
+
writer.writeByte(beatmap.maniaGrade);
|
|
497
|
+
writer.writeInt16(beatmap.localOffset);
|
|
498
|
+
writer.writeSingle(beatmap.stackLeniency);
|
|
499
|
+
writer.writeByte(beatmap.gameplayMode);
|
|
500
|
+
writer.writeString(beatmap.source);
|
|
501
|
+
writer.writeString(beatmap.tags);
|
|
502
|
+
writer.writeInt16(beatmap.onlineOffset);
|
|
503
|
+
writer.writeString(beatmap.titleFont);
|
|
504
|
+
writer.writeBoolean(beatmap.isUnplayed);
|
|
505
|
+
writer.writeDateTimeTicks(beatmap.lastPlayedAt);
|
|
506
|
+
writer.writeBoolean(beatmap.isOsz2);
|
|
507
|
+
writer.writeString(beatmap.beatmapFolderName);
|
|
508
|
+
writer.writeDateTimeTicks(beatmap.lastCheckedAgainstRepositoryAt);
|
|
509
|
+
writer.writeBoolean(beatmap.ignoreBeatmapSound);
|
|
510
|
+
writer.writeBoolean(beatmap.ignoreBeatmapSkin);
|
|
511
|
+
writer.writeBoolean(beatmap.disableStoryboard);
|
|
512
|
+
writer.writeBoolean(beatmap.disableVideo);
|
|
513
|
+
writer.writeBoolean(beatmap.visualOverride);
|
|
514
|
+
writer.writeInt32(beatmap.lastModificationTimeUnknown);
|
|
515
|
+
writer.writeByte(beatmap.maniaScrollSpeed);
|
|
516
|
+
}
|
|
517
|
+
function readOsuDatabase(input) {
|
|
518
|
+
const reader = createReader(input);
|
|
519
|
+
const version = reader.readInt32();
|
|
520
|
+
assertSupportedVersion(version);
|
|
521
|
+
const folderCount = reader.readInt32();
|
|
522
|
+
const accountUnlocked = reader.readBoolean();
|
|
523
|
+
const accountUnlockDate = reader.readDateTimeTicks();
|
|
524
|
+
const playerName = reader.readString();
|
|
525
|
+
const beatmapCount = reader.readInt32();
|
|
526
|
+
const beatmaps = [];
|
|
527
|
+
for (let index = 0; index < beatmapCount; index += 1) beatmaps.push(readBeatmapEntry(reader));
|
|
528
|
+
return {
|
|
529
|
+
version,
|
|
530
|
+
folderCount,
|
|
531
|
+
accountUnlocked,
|
|
532
|
+
accountUnlockDate,
|
|
533
|
+
playerName,
|
|
534
|
+
beatmaps,
|
|
535
|
+
userPermissions: reader.readInt32()
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
function writeOsuDatabase(database) {
|
|
539
|
+
assertSupportedVersion(database.version);
|
|
540
|
+
const writer = createWriter();
|
|
541
|
+
writer.writeInt32(database.version);
|
|
542
|
+
writer.writeInt32(database.folderCount);
|
|
543
|
+
writer.writeBoolean(database.accountUnlocked);
|
|
544
|
+
writer.writeDateTimeTicks(database.accountUnlockDate);
|
|
545
|
+
writer.writeString(database.playerName);
|
|
546
|
+
writer.writeInt32(database.beatmaps.length);
|
|
547
|
+
for (const beatmap of database.beatmaps) writeBeatmapEntry(writer, beatmap);
|
|
548
|
+
writer.writeInt32(database.userPermissions);
|
|
549
|
+
return writer.toUint8Array();
|
|
550
|
+
}
|
|
551
|
+
Object.values(Mods).filter((value) => value !== Mods.None);
|
|
552
|
+
/**
|
|
553
|
+
* Tests whether all bits from a mod mask are present in the flags value.
|
|
554
|
+
*/
|
|
555
|
+
function hasMod(flags, mod) {
|
|
556
|
+
return (flags & mod) === mod;
|
|
557
|
+
}
|
|
558
|
+
//#endregion
|
|
559
|
+
//#region src/db/scores.ts
|
|
560
|
+
function readScoreAdditionalModInfo(reader) {
|
|
561
|
+
return { targetPracticeAccuracy: reader.readDouble() };
|
|
562
|
+
}
|
|
563
|
+
function writeScoreAdditionalModInfo(writer, value) {
|
|
564
|
+
writer.writeDouble(value.targetPracticeAccuracy);
|
|
565
|
+
}
|
|
566
|
+
function readScoreEntry(reader) {
|
|
567
|
+
const gameplayMode = reader.readByte();
|
|
568
|
+
const version = reader.readInt32();
|
|
569
|
+
const beatmapMd5Hash = reader.readString();
|
|
570
|
+
const playerName = reader.readString();
|
|
571
|
+
const replayMd5Hash = reader.readString();
|
|
572
|
+
const count300 = reader.readUInt16();
|
|
573
|
+
const count100 = reader.readUInt16();
|
|
574
|
+
const count50 = reader.readUInt16();
|
|
575
|
+
const countGeki = reader.readUInt16();
|
|
576
|
+
const countKatu = reader.readUInt16();
|
|
577
|
+
const countMiss = reader.readUInt16();
|
|
578
|
+
const totalScore = reader.readInt32();
|
|
579
|
+
const maxCombo = reader.readUInt16();
|
|
580
|
+
const perfectCombo = reader.readBoolean();
|
|
581
|
+
const mods = reader.readInt32();
|
|
582
|
+
const score = {
|
|
583
|
+
gameplayMode,
|
|
584
|
+
version,
|
|
585
|
+
beatmapMd5Hash,
|
|
586
|
+
playerName,
|
|
587
|
+
replayMd5Hash,
|
|
588
|
+
count300,
|
|
589
|
+
count100,
|
|
590
|
+
count50,
|
|
591
|
+
countGeki,
|
|
592
|
+
countKatu,
|
|
593
|
+
countMiss,
|
|
594
|
+
totalScore,
|
|
595
|
+
maxCombo,
|
|
596
|
+
perfectCombo,
|
|
597
|
+
mods,
|
|
598
|
+
reservedEmptyString: reader.readString(),
|
|
599
|
+
replayTimestamp: reader.readDateTimeTicks(),
|
|
600
|
+
reservedInt32: reader.readInt32(),
|
|
601
|
+
onlineScoreId: reader.readInt64()
|
|
602
|
+
};
|
|
603
|
+
if (hasMod(mods, Mods.TargetPractice)) score.additionalModInfo = readScoreAdditionalModInfo(reader);
|
|
604
|
+
return score;
|
|
605
|
+
}
|
|
606
|
+
function writeScoreEntry(writer, score) {
|
|
607
|
+
writer.writeByte(score.gameplayMode);
|
|
608
|
+
writer.writeInt32(score.version);
|
|
609
|
+
writer.writeString(score.beatmapMd5Hash);
|
|
610
|
+
writer.writeString(score.playerName);
|
|
611
|
+
writer.writeString(score.replayMd5Hash);
|
|
612
|
+
writer.writeUInt16(score.count300);
|
|
613
|
+
writer.writeUInt16(score.count100);
|
|
614
|
+
writer.writeUInt16(score.count50);
|
|
615
|
+
writer.writeUInt16(score.countGeki);
|
|
616
|
+
writer.writeUInt16(score.countKatu);
|
|
617
|
+
writer.writeUInt16(score.countMiss);
|
|
618
|
+
writer.writeInt32(score.totalScore);
|
|
619
|
+
writer.writeUInt16(score.maxCombo);
|
|
620
|
+
writer.writeBoolean(score.perfectCombo);
|
|
621
|
+
writer.writeInt32(score.mods);
|
|
622
|
+
writer.writeString(score.reservedEmptyString);
|
|
623
|
+
writer.writeDateTimeTicks(score.replayTimestamp);
|
|
624
|
+
writer.writeInt32(score.reservedInt32);
|
|
625
|
+
writer.writeInt64(score.onlineScoreId);
|
|
626
|
+
if (hasMod(score.mods, Mods.TargetPractice)) {
|
|
627
|
+
if (score.additionalModInfo === void 0) throw new Error("Target Practice scores require additionalModInfo to be present.");
|
|
628
|
+
writeScoreAdditionalModInfo(writer, score.additionalModInfo);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
function readScoresDatabase(input) {
|
|
632
|
+
const reader = createReader(input);
|
|
633
|
+
const version = reader.readInt32();
|
|
634
|
+
assertSupportedVersion(version);
|
|
635
|
+
const beatmapCount = reader.readInt32();
|
|
636
|
+
const beatmaps = [];
|
|
637
|
+
for (let beatmapIndex = 0; beatmapIndex < beatmapCount; beatmapIndex += 1) {
|
|
638
|
+
const beatmapMd5Hash = reader.readString();
|
|
639
|
+
const scoreCount = reader.readInt32();
|
|
640
|
+
const scores = [];
|
|
641
|
+
for (let scoreIndex = 0; scoreIndex < scoreCount; scoreIndex += 1) scores.push(readScoreEntry(reader));
|
|
642
|
+
beatmaps.push({
|
|
643
|
+
beatmapMd5Hash,
|
|
644
|
+
scores
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
return {
|
|
648
|
+
version,
|
|
649
|
+
beatmaps
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
function writeScoresDatabase(database) {
|
|
653
|
+
assertSupportedVersion(database.version);
|
|
654
|
+
const writer = createWriter();
|
|
655
|
+
writer.writeInt32(database.version);
|
|
656
|
+
writer.writeInt32(database.beatmaps.length);
|
|
657
|
+
for (const beatmap of database.beatmaps) {
|
|
658
|
+
writer.writeString(beatmap.beatmapMd5Hash);
|
|
659
|
+
writer.writeInt32(beatmap.scores.length);
|
|
660
|
+
for (const score of beatmap.scores) writeScoreEntry(writer, score);
|
|
661
|
+
}
|
|
662
|
+
return writer.toUint8Array();
|
|
663
|
+
}
|
|
664
|
+
//#endregion
|
|
665
|
+
export { readCollectionDatabase as a, Grades as c, RankedStatuses as d, UserPermissions as f, writeOsuDatabase as i, MINIMUM_SUPPORTED_VERSION as l, writeScoresDatabase as n, writeCollectionDatabase as o, readOsuDatabase as r, GameplayModes as s, readScoresDatabase as t, Mods as u };
|