osu-stable-db 0.1.2 → 0.2.1
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 +26 -31
- package/dist/index.mjs +1 -1
- package/dist/node.d.mts +20 -2
- package/dist/node.mjs +53 -2
- package/dist/{scores-D_f7sIP0.mjs → scores-C35i3lk0.mjs} +13 -1
- package/package.json +4 -4
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
|
|
|
@@ -55,39 +55,35 @@ const collectionDatabase: CollectionDatabase = {
|
|
|
55
55
|
const collectionBytes = writeCollectionDatabase(collectionDatabase)
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
-
Main entry exports:
|
|
59
|
-
|
|
60
|
-
- All public types and constants
|
|
61
|
-
- readOsuDatabase and writeOsuDatabase
|
|
62
|
-
- readCollectionDatabase and writeCollectionDatabase
|
|
63
|
-
- readScoresDatabase and writeScoresDatabase
|
|
64
|
-
|
|
65
58
|
## Node Usage
|
|
66
59
|
|
|
67
|
-
Use the Node subpath when you want direct file reads and writes through node:fs/promises.
|
|
60
|
+
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
61
|
|
|
69
62
|
```ts
|
|
70
63
|
import {
|
|
71
|
-
|
|
72
|
-
writeCollectionDatabaseFile,
|
|
64
|
+
OsuFolder,
|
|
73
65
|
} from 'osu-stable-db/node'
|
|
74
66
|
|
|
75
|
-
const
|
|
67
|
+
const osuFolder = new OsuFolder('C:/osu!')
|
|
76
68
|
|
|
77
|
-
await
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
)
|
|
84
|
-
|
|
69
|
+
const osuDatabase = await osuFolder.readOsuDatabase()
|
|
70
|
+
const scoresDatabase = await osuFolder.readScoresDatabase()
|
|
71
|
+
|
|
72
|
+
const newestBeatmap = osuDatabase.beatmaps.at(-1)
|
|
73
|
+
const newestScore = scoresDatabase.beatmaps.at(-1)?.scores.at(-1)
|
|
74
|
+
|
|
75
|
+
if (newestBeatmap !== undefined) {
|
|
76
|
+
const osuFilePath = osuFolder.getOsuFilePath(newestBeatmap)
|
|
77
|
+
console.log(osuFilePath)
|
|
78
|
+
}
|
|
85
79
|
|
|
86
|
-
|
|
80
|
+
if (newestScore !== undefined) {
|
|
81
|
+
const osrFilePath = osuFolder.getOsrFilePath(newestScore)
|
|
82
|
+
console.log(osrFilePath)
|
|
83
|
+
}
|
|
84
|
+
```
|
|
87
85
|
|
|
88
|
-
- readOsuDatabaseFile and
|
|
89
|
-
- readCollectionDatabaseFile and writeCollectionDatabaseFile
|
|
90
|
-
- readScoresDatabaseFile and writeScoresDatabaseFile
|
|
86
|
+
If you only need path-based file IO, the same subpath also exports helpers such as readOsuDatabaseFile, writeCollectionDatabaseFile, and writeScoresDatabaseFile.
|
|
91
87
|
|
|
92
88
|
## Types And Time Values
|
|
93
89
|
|
|
@@ -104,14 +100,13 @@ This project is 100% AI-generated.
|
|
|
104
100
|
|
|
105
101
|
Committed minimal fixtures live in [tests/files](tests/files).
|
|
106
102
|
|
|
107
|
-
|
|
103
|
+
To run tests and local scripts against your real osu! installation, set this in [.env](.env):
|
|
108
104
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
105
|
+
```dotenv
|
|
106
|
+
OSU_STABLE_DIR=C:/osu!
|
|
107
|
+
```
|
|
112
108
|
|
|
113
|
-
-
|
|
114
|
-
- verify byte-for-byte round-trip for osu!.db, collection.db, and scores.db
|
|
109
|
+
When OSU_STABLE_DIR is set, local validation reads your real database files and verifies byte-for-byte round-trip for osu!.db, collection.db, and scores.db.
|
|
115
110
|
|
|
116
111
|
You can also generate a local inspection report for a specific beatmap identifier with:
|
|
117
112
|
|
|
@@ -134,7 +129,7 @@ pnpm run build
|
|
|
134
129
|
|
|
135
130
|
## Notes On Validation
|
|
136
131
|
|
|
137
|
-
Local validation also passes against private real-world database files
|
|
132
|
+
Local validation also passes against private real-world database files referenced by OSU_STABLE_DIR:
|
|
138
133
|
|
|
139
134
|
- osu!.db: 58,295,932 bytes, 72,038 beatmaps
|
|
140
135
|
- 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,12 +1,30 @@
|
|
|
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;
|
|
5
|
+
declare const OSU_STABLE_DIR_ENV_VAR = "OSU_STABLE_DIR";
|
|
6
|
+
declare function getConfiguredOsuFolderPath(): string | null;
|
|
7
|
+
declare function getConfiguredOsuFolder(): OsuFolder | null;
|
|
5
8
|
declare function readOsuDatabaseFile(path: DatabaseFilePath): Promise<OsuDatabase>;
|
|
6
9
|
declare function writeOsuDatabaseFile(path: DatabaseFilePath, database: OsuDatabase): Promise<void>;
|
|
7
10
|
declare function readCollectionDatabaseFile(path: DatabaseFilePath): Promise<CollectionDatabase>;
|
|
8
11
|
declare function writeCollectionDatabaseFile(path: DatabaseFilePath, database: CollectionDatabase): Promise<void>;
|
|
9
12
|
declare function readScoresDatabaseFile(path: DatabaseFilePath): Promise<ScoresDatabase>;
|
|
10
13
|
declare function writeScoresDatabaseFile(path: DatabaseFilePath, database: ScoresDatabase): Promise<void>;
|
|
14
|
+
declare class OsuFolder {
|
|
15
|
+
folderPath: string;
|
|
16
|
+
constructor(folderPath: string);
|
|
17
|
+
getOsuDatabasePath(): string;
|
|
18
|
+
getCollectionDatabasePath(): string;
|
|
19
|
+
getScoresDatabasePath(): string;
|
|
20
|
+
readOsuDatabase(): Promise<OsuDatabase>;
|
|
21
|
+
writeOsuDatabase(database: OsuDatabase): Promise<void>;
|
|
22
|
+
readCollectionDatabase(): Promise<CollectionDatabase>;
|
|
23
|
+
writeCollectionDatabase(database: CollectionDatabase): Promise<void>;
|
|
24
|
+
readScoresDatabase(): Promise<ScoresDatabase>;
|
|
25
|
+
writeScoresDatabase(database: ScoresDatabase): Promise<void>;
|
|
26
|
+
getOsuFilePath(beatmap: BeatmapEntry): string;
|
|
27
|
+
getOsrFilePath(score: ScoreEntry): string;
|
|
28
|
+
}
|
|
11
29
|
//#endregion
|
|
12
|
-
export { DatabaseFilePath, readCollectionDatabaseFile, readOsuDatabaseFile, readScoresDatabaseFile, writeCollectionDatabaseFile, writeOsuDatabaseFile, writeScoresDatabaseFile };
|
|
30
|
+
export { DatabaseFilePath, OSU_STABLE_DIR_ENV_VAR, OsuFolder, getConfiguredOsuFolder, getConfiguredOsuFolderPath, readCollectionDatabaseFile, readOsuDatabaseFile, readScoresDatabaseFile, writeCollectionDatabaseFile, writeOsuDatabaseFile, writeScoresDatabaseFile };
|
package/dist/node.mjs
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
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
|
|
5
|
+
const OSU_STABLE_DIR_ENV_VAR = "OSU_STABLE_DIR";
|
|
6
|
+
function getConfiguredOsuFolderPath() {
|
|
7
|
+
const folderPath = process.env[OSU_STABLE_DIR_ENV_VAR]?.trim();
|
|
8
|
+
return folderPath === void 0 || folderPath.length === 0 ? null : folderPath;
|
|
9
|
+
}
|
|
10
|
+
function getConfiguredOsuFolder() {
|
|
11
|
+
const folderPath = getConfiguredOsuFolderPath();
|
|
12
|
+
return folderPath === null ? null : new OsuFolder(folderPath);
|
|
13
|
+
}
|
|
4
14
|
async function readOsuDatabaseFile(path) {
|
|
5
15
|
return readOsuDatabase(new Uint8Array(await readFile(path)));
|
|
6
16
|
}
|
|
@@ -19,5 +29,46 @@ async function readScoresDatabaseFile(path) {
|
|
|
19
29
|
async function writeScoresDatabaseFile(path, database) {
|
|
20
30
|
await writeFile(path, writeScoresDatabase(database));
|
|
21
31
|
}
|
|
32
|
+
var OsuFolder = class {
|
|
33
|
+
folderPath;
|
|
34
|
+
constructor(folderPath) {
|
|
35
|
+
this.folderPath = folderPath;
|
|
36
|
+
}
|
|
37
|
+
getOsuDatabasePath() {
|
|
38
|
+
return join(this.folderPath, "osu!.db");
|
|
39
|
+
}
|
|
40
|
+
getCollectionDatabasePath() {
|
|
41
|
+
return join(this.folderPath, "collection.db");
|
|
42
|
+
}
|
|
43
|
+
getScoresDatabasePath() {
|
|
44
|
+
return join(this.folderPath, "scores.db");
|
|
45
|
+
}
|
|
46
|
+
async readOsuDatabase() {
|
|
47
|
+
return readOsuDatabaseFile(this.getOsuDatabasePath());
|
|
48
|
+
}
|
|
49
|
+
async writeOsuDatabase(database) {
|
|
50
|
+
await writeOsuDatabaseFile(this.getOsuDatabasePath(), database);
|
|
51
|
+
}
|
|
52
|
+
async readCollectionDatabase() {
|
|
53
|
+
return readCollectionDatabaseFile(this.getCollectionDatabasePath());
|
|
54
|
+
}
|
|
55
|
+
async writeCollectionDatabase(database) {
|
|
56
|
+
await writeCollectionDatabaseFile(this.getCollectionDatabasePath(), database);
|
|
57
|
+
}
|
|
58
|
+
async readScoresDatabase() {
|
|
59
|
+
return readScoresDatabaseFile(this.getScoresDatabasePath());
|
|
60
|
+
}
|
|
61
|
+
async writeScoresDatabase(database) {
|
|
62
|
+
await writeScoresDatabaseFile(this.getScoresDatabasePath(), database);
|
|
63
|
+
}
|
|
64
|
+
getOsuFilePath(beatmap) {
|
|
65
|
+
if (beatmap.beatmapFolderName === null || beatmap.osuFileName === null) throw new Error("Beatmap entry is missing beatmapFolderName or osuFileName");
|
|
66
|
+
return join(this.folderPath, "Songs", beatmap.beatmapFolderName, beatmap.osuFileName);
|
|
67
|
+
}
|
|
68
|
+
getOsrFilePath(score) {
|
|
69
|
+
if (score.beatmapMd5Hash === null) throw new Error("Score entry is missing beatmapMd5Hash");
|
|
70
|
+
return join(this.folderPath, "Data", "r", `${score.beatmapMd5Hash}-${dateTimeTicksToWindowsFileTimeTicks(score.replayTimestamp)}.osr`);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
22
73
|
//#endregion
|
|
23
|
-
export { readCollectionDatabaseFile, readOsuDatabaseFile, readScoresDatabaseFile, writeCollectionDatabaseFile, writeOsuDatabaseFile, writeScoresDatabaseFile };
|
|
74
|
+
export { OSU_STABLE_DIR_ENV_VAR, OsuFolder, getConfiguredOsuFolder, getConfiguredOsuFolderPath, readCollectionDatabaseFile, readOsuDatabaseFile, readScoresDatabaseFile, writeCollectionDatabaseFile, writeOsuDatabaseFile, writeScoresDatabaseFile };
|
|
@@ -352,8 +352,20 @@ function writeOsuDatabase(database) {
|
|
|
352
352
|
writer.writeInt32(database.userPermissions);
|
|
353
353
|
return writer.toUint8Array();
|
|
354
354
|
}
|
|
355
|
+
//#endregion
|
|
356
|
+
//#region src/core/utils.ts
|
|
357
|
+
const WINDOWS_FILE_TIME_EPOCH_DATE_TIME_TICKS = 504911232000000000n;
|
|
355
358
|
Object.values(Mods).filter((value) => value !== Mods.None);
|
|
356
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
|
+
/**
|
|
357
369
|
* Tests whether all bits from a mod mask are present in the flags value.
|
|
358
370
|
*/
|
|
359
371
|
function hasMod(flags, mod) {
|
|
@@ -466,4 +478,4 @@ function writeScoresDatabase(database) {
|
|
|
466
478
|
return writer.toUint8Array();
|
|
467
479
|
}
|
|
468
480
|
//#endregion
|
|
469
|
-
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.1
|
|
4
|
+
"version": "0.2.1",
|
|
5
5
|
"description": "TypeScript reader and writer for osu!stable database files.",
|
|
6
6
|
"author": "zzzzv",
|
|
7
7
|
"license": "MIT",
|
|
@@ -29,9 +29,9 @@
|
|
|
29
29
|
"scripts": {
|
|
30
30
|
"build": "tsdown",
|
|
31
31
|
"dev": "tsdown --watch",
|
|
32
|
-
"local:inspect": "pnpm run build && node scripts/local-inspect.mjs",
|
|
33
|
-
"local:roundtrip": "pnpm run build && node scripts/local-roundtrip.mjs",
|
|
34
|
-
"test": "vitest",
|
|
32
|
+
"local:inspect": "pnpm run build && node --env-file=.env scripts/local-inspect.mjs",
|
|
33
|
+
"local:roundtrip": "pnpm run build && node --env-file=.env scripts/local-roundtrip.mjs",
|
|
34
|
+
"test": "node --env-file=.env ./node_modules/vitest/vitest.mjs",
|
|
35
35
|
"typecheck": "tsc --noEmit",
|
|
36
36
|
"prepublishOnly": "pnpm run build"
|
|
37
37
|
},
|