osu-stable-db 0.1.1 → 0.2.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 +29 -18
- package/dist/index.mjs +1 -1
- package/dist/node.d.mts +17 -2
- package/dist/node.mjs +44 -2
- package/dist/{scores-7qsAMzUc.mjs → scores-C35i3lk0.mjs} +18 -202
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ This library currently supports the latest database structure from version 20250
|
|
|
10
10
|
- collection.db
|
|
11
11
|
- scores.db
|
|
12
12
|
|
|
13
|
-
The core binary and database logic is browser-compatible. A separate Node-only subpath is provided for direct file reads and
|
|
13
|
+
The core binary and database logic is browser-compatible. A separate Node-only subpath is provided for direct file reads, writes, and osu! folder helpers.
|
|
14
14
|
|
|
15
15
|
## Scope
|
|
16
16
|
|
|
@@ -64,30 +64,32 @@ Main entry exports:
|
|
|
64
64
|
|
|
65
65
|
## Node Usage
|
|
66
66
|
|
|
67
|
-
Use the Node subpath when you want direct file reads and writes through node:fs/promises.
|
|
67
|
+
Use the Node subpath when you want to work with a full osu! folder or with direct file reads and writes through node:fs/promises.
|
|
68
68
|
|
|
69
69
|
```ts
|
|
70
70
|
import {
|
|
71
|
-
|
|
72
|
-
writeCollectionDatabaseFile,
|
|
71
|
+
OsuFolder,
|
|
73
72
|
} from 'osu-stable-db/node'
|
|
74
73
|
|
|
75
|
-
const
|
|
74
|
+
const osuFolder = new OsuFolder('C:/Games/osu!')
|
|
75
|
+
const osuDatabase = await osuFolder.readOsuDatabase()
|
|
76
|
+
const scoresDatabase = await osuFolder.readScoresDatabase()
|
|
76
77
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
```
|
|
78
|
+
const newestBeatmap = osuDatabase.beatmaps.at(-1)
|
|
79
|
+
const newestScore = scoresDatabase.beatmaps.at(-1)?.scores.at(-1)
|
|
80
|
+
|
|
81
|
+
if (newestBeatmap !== undefined) {
|
|
82
|
+
const osuFilePath = osuFolder.getOsuFilePath(newestBeatmap)
|
|
83
|
+
console.log(osuFilePath)
|
|
84
|
+
}
|
|
85
85
|
|
|
86
|
-
|
|
86
|
+
if (newestScore !== undefined) {
|
|
87
|
+
const osrFilePath = osuFolder.getOsrFilePath(newestScore)
|
|
88
|
+
console.log(osrFilePath)
|
|
89
|
+
}
|
|
90
|
+
```
|
|
87
91
|
|
|
88
|
-
- readOsuDatabaseFile and
|
|
89
|
-
- readCollectionDatabaseFile and writeCollectionDatabaseFile
|
|
90
|
-
- readScoresDatabaseFile and writeScoresDatabaseFile
|
|
92
|
+
If you only need path-based file IO, the same subpath also exports helpers such as readOsuDatabaseFile, writeCollectionDatabaseFile, and writeScoresDatabaseFile.
|
|
91
93
|
|
|
92
94
|
## Types And Time Values
|
|
93
95
|
|
|
@@ -107,12 +109,21 @@ Committed minimal fixtures live in [tests/files](tests/files).
|
|
|
107
109
|
Large real-world local fixtures can be placed in:
|
|
108
110
|
|
|
109
111
|
- [tests/files/local](tests/files/local)
|
|
112
|
+
- [tests/files/local/osu!](tests/files/local/osu!) via a directory link created by the package script below
|
|
110
113
|
|
|
111
114
|
That directory is git-ignored. When those files are present, the test suite will:
|
|
112
115
|
|
|
113
116
|
- parse them
|
|
114
117
|
- verify byte-for-byte round-trip for osu!.db, collection.db, and scores.db
|
|
115
118
|
|
|
119
|
+
To link your real local osu! folder into the workspace on Windows, run:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
pnpm run local:link -- "C:\\path\\to\\osu!"
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
This creates [tests/files/local/osu!](tests/files/local/osu!) as a directory junction. Tests and local scripts now prefer databases from that linked folder, and fall back to [tests/files/local](tests/files/local) for the previous workflow.
|
|
126
|
+
|
|
116
127
|
You can also generate a local inspection report for a specific beatmap identifier with:
|
|
117
128
|
|
|
118
129
|
```bash
|
|
@@ -134,7 +145,7 @@ pnpm run build
|
|
|
134
145
|
|
|
135
146
|
## Notes On Validation
|
|
136
147
|
|
|
137
|
-
Local validation also passes against private real-world database files in [tests/files/local](tests/files/local):
|
|
148
|
+
Local validation also passes against private real-world database files in [tests/files/local/osu!](tests/files/local/osu!):
|
|
138
149
|
|
|
139
150
|
- osu!.db: 58,295,932 bytes, 72,038 beatmaps
|
|
140
151
|
- collection.db: 195,402 bytes, 11 collections, 5,743 stored beatmap references
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as writeOsuDatabase, c as GameplayModes, d as Mods, f as RankedStatuses, i as readOsuDatabase, l as Grades, n as writeScoresDatabase, o as readCollectionDatabase, p as UserPermissions, s as writeCollectionDatabase, t as readScoresDatabase, u as MINIMUM_SUPPORTED_VERSION } from "./scores-C35i3lk0.mjs";
|
|
2
2
|
export { GameplayModes, Grades, MINIMUM_SUPPORTED_VERSION, Mods, RankedStatuses, UserPermissions, readCollectionDatabase, readOsuDatabase, readScoresDatabase, writeCollectionDatabase, writeOsuDatabase, writeScoresDatabase };
|
package/dist/node.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { b as ScoresDatabase, m as OsuDatabase, n as CollectionDatabase } from "./types-DiZMN3fQ.mjs";
|
|
1
|
+
import { b as ScoresDatabase, m as OsuDatabase, n as CollectionDatabase, t as BeatmapEntry, v as ScoreEntry } from "./types-DiZMN3fQ.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/node.d.ts
|
|
4
4
|
type DatabaseFilePath = string | URL;
|
|
@@ -8,5 +8,20 @@ declare function readCollectionDatabaseFile(path: DatabaseFilePath): Promise<Col
|
|
|
8
8
|
declare function writeCollectionDatabaseFile(path: DatabaseFilePath, database: CollectionDatabase): Promise<void>;
|
|
9
9
|
declare function readScoresDatabaseFile(path: DatabaseFilePath): Promise<ScoresDatabase>;
|
|
10
10
|
declare function writeScoresDatabaseFile(path: DatabaseFilePath, database: ScoresDatabase): Promise<void>;
|
|
11
|
+
declare class OsuFolder {
|
|
12
|
+
folderPath: string;
|
|
13
|
+
constructor(folderPath: string);
|
|
14
|
+
getOsuDatabasePath(): string;
|
|
15
|
+
getCollectionDatabasePath(): string;
|
|
16
|
+
getScoresDatabasePath(): string;
|
|
17
|
+
readOsuDatabase(): Promise<OsuDatabase>;
|
|
18
|
+
writeOsuDatabase(database: OsuDatabase): Promise<void>;
|
|
19
|
+
readCollectionDatabase(): Promise<CollectionDatabase>;
|
|
20
|
+
writeCollectionDatabase(database: CollectionDatabase): Promise<void>;
|
|
21
|
+
readScoresDatabase(): Promise<ScoresDatabase>;
|
|
22
|
+
writeScoresDatabase(database: ScoresDatabase): Promise<void>;
|
|
23
|
+
getOsuFilePath(beatmap: BeatmapEntry): string;
|
|
24
|
+
getOsrFilePath(score: ScoreEntry): string;
|
|
25
|
+
}
|
|
11
26
|
//#endregion
|
|
12
|
-
export { DatabaseFilePath, readCollectionDatabaseFile, readOsuDatabaseFile, readScoresDatabaseFile, writeCollectionDatabaseFile, writeOsuDatabaseFile, writeScoresDatabaseFile };
|
|
27
|
+
export { DatabaseFilePath, OsuFolder, readCollectionDatabaseFile, readOsuDatabaseFile, readScoresDatabaseFile, writeCollectionDatabaseFile, writeOsuDatabaseFile, writeScoresDatabaseFile };
|
package/dist/node.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as writeOsuDatabase, i as readOsuDatabase, n as writeScoresDatabase, o as readCollectionDatabase, r as dateTimeTicksToWindowsFileTimeTicks, s as writeCollectionDatabase, t as readScoresDatabase } from "./scores-C35i3lk0.mjs";
|
|
2
2
|
import { readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
3
4
|
//#region src/node.ts
|
|
4
5
|
async function readOsuDatabaseFile(path) {
|
|
5
6
|
return readOsuDatabase(new Uint8Array(await readFile(path)));
|
|
@@ -19,5 +20,46 @@ async function readScoresDatabaseFile(path) {
|
|
|
19
20
|
async function writeScoresDatabaseFile(path, database) {
|
|
20
21
|
await writeFile(path, writeScoresDatabase(database));
|
|
21
22
|
}
|
|
23
|
+
var OsuFolder = class {
|
|
24
|
+
folderPath;
|
|
25
|
+
constructor(folderPath) {
|
|
26
|
+
this.folderPath = folderPath;
|
|
27
|
+
}
|
|
28
|
+
getOsuDatabasePath() {
|
|
29
|
+
return join(this.folderPath, "osu!.db");
|
|
30
|
+
}
|
|
31
|
+
getCollectionDatabasePath() {
|
|
32
|
+
return join(this.folderPath, "collection.db");
|
|
33
|
+
}
|
|
34
|
+
getScoresDatabasePath() {
|
|
35
|
+
return join(this.folderPath, "scores.db");
|
|
36
|
+
}
|
|
37
|
+
async readOsuDatabase() {
|
|
38
|
+
return readOsuDatabaseFile(this.getOsuDatabasePath());
|
|
39
|
+
}
|
|
40
|
+
async writeOsuDatabase(database) {
|
|
41
|
+
await writeOsuDatabaseFile(this.getOsuDatabasePath(), database);
|
|
42
|
+
}
|
|
43
|
+
async readCollectionDatabase() {
|
|
44
|
+
return readCollectionDatabaseFile(this.getCollectionDatabasePath());
|
|
45
|
+
}
|
|
46
|
+
async writeCollectionDatabase(database) {
|
|
47
|
+
await writeCollectionDatabaseFile(this.getCollectionDatabasePath(), database);
|
|
48
|
+
}
|
|
49
|
+
async readScoresDatabase() {
|
|
50
|
+
return readScoresDatabaseFile(this.getScoresDatabasePath());
|
|
51
|
+
}
|
|
52
|
+
async writeScoresDatabase(database) {
|
|
53
|
+
await writeScoresDatabaseFile(this.getScoresDatabasePath(), database);
|
|
54
|
+
}
|
|
55
|
+
getOsuFilePath(beatmap) {
|
|
56
|
+
if (beatmap.beatmapFolderName === null || beatmap.osuFileName === null) throw new Error("Beatmap entry is missing beatmapFolderName or osuFileName");
|
|
57
|
+
return join(this.folderPath, "Songs", beatmap.beatmapFolderName, beatmap.osuFileName);
|
|
58
|
+
}
|
|
59
|
+
getOsrFilePath(score) {
|
|
60
|
+
if (score.beatmapMd5Hash === null) throw new Error("Score entry is missing beatmapMd5Hash");
|
|
61
|
+
return join(this.folderPath, "Data", "r", `${score.beatmapMd5Hash}-${dateTimeTicksToWindowsFileTimeTicks(score.replayTimestamp)}.osr`);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
22
64
|
//#endregion
|
|
23
|
-
export { readCollectionDatabaseFile, readOsuDatabaseFile, readScoresDatabaseFile, writeCollectionDatabaseFile, writeOsuDatabaseFile, writeScoresDatabaseFile };
|
|
65
|
+
export { OsuFolder, readCollectionDatabaseFile, readOsuDatabaseFile, readScoresDatabaseFile, writeCollectionDatabaseFile, writeOsuDatabaseFile, writeScoresDatabaseFile };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { BinaryReader, BinaryWriter } from "osu-binary";
|
|
1
2
|
//#region src/types.ts
|
|
2
3
|
/**
|
|
3
4
|
* The minimum legacy database version supported by this library.
|
|
@@ -98,108 +99,10 @@ const Mods = {
|
|
|
98
99
|
};
|
|
99
100
|
//#endregion
|
|
100
101
|
//#region src/core/binary.ts
|
|
101
|
-
|
|
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
|
-
}
|
|
102
|
+
var BinaryReader$1 = class extends BinaryReader {
|
|
174
103
|
readDateTimeTicks() {
|
|
175
104
|
return this.readInt64();
|
|
176
105
|
}
|
|
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
106
|
readIntFloatPair() {
|
|
204
107
|
const intMarker = this.readByte();
|
|
205
108
|
if (intMarker !== 8) throw new Error(`Invalid Int-Float pair int marker 0x${intMarker.toString(16)}.`);
|
|
@@ -218,95 +121,11 @@ var BinaryReader = class {
|
|
|
218
121
|
isUninherited: this.readBoolean()
|
|
219
122
|
};
|
|
220
123
|
}
|
|
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
124
|
};
|
|
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
|
-
}
|
|
125
|
+
var BinaryWriter$1 = class extends BinaryWriter {
|
|
282
126
|
writeDateTimeTicks(value) {
|
|
283
127
|
this.writeInt64(value);
|
|
284
128
|
}
|
|
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
129
|
writeIntFloatPair(value) {
|
|
311
130
|
this.writeByte(8);
|
|
312
131
|
this.writeInt32(value.mods);
|
|
@@ -318,29 +137,14 @@ var BinaryWriter = class {
|
|
|
318
137
|
this.writeDouble(value.offsetMs);
|
|
319
138
|
this.writeBoolean(value.isUninherited);
|
|
320
139
|
}
|
|
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
140
|
};
|
|
337
141
|
//#endregion
|
|
338
142
|
//#region src/db/shared.ts
|
|
339
143
|
function createReader(input) {
|
|
340
|
-
return new BinaryReader(input);
|
|
144
|
+
return new BinaryReader$1(input);
|
|
341
145
|
}
|
|
342
146
|
function createWriter() {
|
|
343
|
-
return new BinaryWriter();
|
|
147
|
+
return new BinaryWriter$1();
|
|
344
148
|
}
|
|
345
149
|
function assertSupportedVersion(version) {
|
|
346
150
|
if (version < 20250107) throw new RangeError(`Unsupported legacy database version ${version}. Minimum supported version is ${MINIMUM_SUPPORTED_VERSION}.`);
|
|
@@ -548,8 +352,20 @@ function writeOsuDatabase(database) {
|
|
|
548
352
|
writer.writeInt32(database.userPermissions);
|
|
549
353
|
return writer.toUint8Array();
|
|
550
354
|
}
|
|
355
|
+
//#endregion
|
|
356
|
+
//#region src/core/utils.ts
|
|
357
|
+
const WINDOWS_FILE_TIME_EPOCH_DATE_TIME_TICKS = 504911232000000000n;
|
|
551
358
|
Object.values(Mods).filter((value) => value !== Mods.None);
|
|
552
359
|
/**
|
|
360
|
+
* Converts .NET DateTime ticks to Windows FILETIME ticks.
|
|
361
|
+
*
|
|
362
|
+
* scores.db stores replay timestamps as .NET DateTime ticks, while replay
|
|
363
|
+
* filenames under Data/r use FILETIME ticks starting at 1601-01-01.
|
|
364
|
+
*/
|
|
365
|
+
function dateTimeTicksToWindowsFileTimeTicks(ticks) {
|
|
366
|
+
return ticks - WINDOWS_FILE_TIME_EPOCH_DATE_TIME_TICKS;
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
553
369
|
* Tests whether all bits from a mod mask are present in the flags value.
|
|
554
370
|
*/
|
|
555
371
|
function hasMod(flags, mod) {
|
|
@@ -662,4 +478,4 @@ function writeScoresDatabase(database) {
|
|
|
662
478
|
return writer.toUint8Array();
|
|
663
479
|
}
|
|
664
480
|
//#endregion
|
|
665
|
-
export {
|
|
481
|
+
export { writeOsuDatabase as a, GameplayModes as c, Mods as d, RankedStatuses as f, readOsuDatabase as i, Grades as l, writeScoresDatabase as n, readCollectionDatabase as o, UserPermissions as p, dateTimeTicksToWindowsFileTimeTicks as r, writeCollectionDatabase as s, readScoresDatabase as t, MINIMUM_SUPPORTED_VERSION as u };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "osu-stable-db",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.2.0",
|
|
5
5
|
"description": "TypeScript reader and writer for osu!stable database files.",
|
|
6
6
|
"author": "zzzzv",
|
|
7
7
|
"license": "MIT",
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"scripts": {
|
|
30
30
|
"build": "tsdown",
|
|
31
31
|
"dev": "tsdown --watch",
|
|
32
|
+
"local:link": "node scripts/local-link.mjs",
|
|
32
33
|
"local:inspect": "pnpm run build && node scripts/local-inspect.mjs",
|
|
33
34
|
"local:roundtrip": "pnpm run build && node scripts/local-roundtrip.mjs",
|
|
34
35
|
"test": "vitest",
|
|
@@ -41,5 +42,8 @@
|
|
|
41
42
|
"tsdown": "^0.21.7",
|
|
42
43
|
"typescript": "^6.0.2",
|
|
43
44
|
"vitest": "^4.1.2"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"osu-binary": "^0.1.0"
|
|
44
48
|
}
|
|
45
49
|
}
|