Skip to content

Instantly share code, notes, and snippets.

@MCJack123
Last active September 11, 2021 22:47
Show Gist options
  • Save MCJack123/892b936ef0d18da43ab2764ba97402be to your computer and use it in GitHub Desktop.
Save MCJack123/892b936ef0d18da43ab2764ba97402be to your computer and use it in GitHub Desktop.
Beat Saber 1.0 level format conversion
info.json Info.dat
+ _version = "2.0.0"
songName _songName
songSubName _songSubName
authorName _songAuthorName
+ _levelAuthorName = ""
beatsPerMinute _beatsPerMinute
difficultyLevels[].offset _songTimeOffset
Difficulty.json => _shuffle _shuffle
Difficulty.json => _shufflePeriod _shufflePeriod
previewStartTime _previewStartTime
previewDuration _previewDuration
difficultyLevels[].audioPath _songFilename
coverImagePath _coverImageFilename (must be PNG?)
environmentName _environmentName
oneSaber -
contributors (custom mod) -
customEnvironment (custom mod) -
customEnvironmentHash (custom mod) -
difficultyLevels _difficultyBeatmapSets[]._difficultyBeatmaps
characteristic _difficultyBeatmapSets[]._beatmapCharacteristicName
difficulty _difficulty
difficultyRank _difficultyRank
audioPath Info.dat => _songFilename
offset Info.dat => _songTimeOffset (?)
jsonPath _beatmapFilename (as .dat)
oldOffset -
chromaToggle -
customColors -
difficultyLabel -
Difficulty.json => _noteJumpSpeed _noteJumpMovementSpeed
+ _noteJumpStartBeatOffset = 0
Difficulty.json Difficulty.dat
_version = "1.5.0" _version = "2.0.0"
_beatsPerMinute Info.dat => _beatsPerMinute
_beatsPerBar -
_noteJumpSpeed Info.dat - _noteJumpMovementSpeed
_shuffle Info.dat => _shuffle
_shufflePeriod Info.dat => _shufflePeriod
_events _events
_notes _notes
_obstacles _obstacles
var fs = require("fs");
console.log("");
console.log("-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=");
console.log("This is experimental software that may break things. USE AT YOUR OWN RISK.");
console.log("Please disable ScoreSaber before using this tool.");
console.log("-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=");
console.log("");
setTimeout(function() {
// Parse arguments
if (process.argv.length < 4) {
console.log("Usage: " + process.argv[0] + " " + process.argv[1] + " <old level folder> <new level folder>");
return 1;
}
var oldPath = process.argv[2];
var newPath = process.argv[3];
// Check paths
if (!fs.existsSync(oldPath)) {
console.log("Error: Old path " + oldPath + " does not exist");
return 2;
}
if (!fs.existsSync(newPath)) fs.mkdirSync(newPath);
// Read info.json
var oldInfo = JSON.parse(fs.readFileSync(oldPath + "/info.json", {encoding: "ascii"}));
var newInfo = {}
// Convert info.json to Info.dat
newInfo._version = "2.0.0";
newInfo._songName = oldInfo.songName;
newInfo._songSubName = "";
newInfo._songAuthorName = oldInfo.songSubName;
newInfo._levelAuthorName = oldInfo.authorName;
newInfo._beatsPerMinute = oldInfo.beatsPerMinute;
newInfo._previewStartTime = oldInfo.previewStartTime;
newInfo._previewDuration = oldInfo.previewDuration;
newInfo._coverImageFilename = oldInfo.coverImagePath;
newInfo._environmentName = oldInfo.environmentName;
newInfo._difficultyBeatmapSets = [];
var characteristics = {};
// Convert difficulties
for (var d in oldInfo.difficultyLevels) {
var beatmap = oldInfo.difficultyLevels[d];
var cid = 0;
if (!("characteristic" in beatmap)) beatmap.characteristic = "Standard";
if (!(beatmap.characteristic in characteristics)) {
characteristics[beatmap.characteristic] = newInfo._difficultyBeatmapSets.length;
cid = newInfo._difficultyBeatmapSets.length;
newInfo._difficultyBeatmapSets.push({
_beatmapCharacteristicName: beatmap.characteristic,
_difficultyBeatmaps: []
});
} else cid = characteristics[beatmap.characteristic];
var newBeatmap = {}
newBeatmap._difficulty = beatmap.difficulty;
newBeatmap._difficultyRank = beatmap.difficultyRank;
newBeatmap._beatmapFilename = beatmap.jsonPath.replace(".json", ".dat");
newBeatmap._noteJumpStartBeatOffset = 0;
if (("_songFilename" in newInfo) && newInfo._songFilename != beatmap.audioPath) {
console.log("Error: new level format doesn't support multiple songs");
return 3;
}
newInfo._songFilename = beatmap.audioPath;
if (("_songTimeOffset" in newInfo) && newInfo._songTimeOffset != beatmap.offset) {
console.log("Error: new level format doesn't support multiple song offsets");
return 4;
}
newInfo._songTimeOffset = 0;
// Convert the level file
var oldLevel = JSON.parse(fs.readFileSync(oldPath + "/" + beatmap.jsonPath));
var newLevel = {}
newLevel._version = "2.0.0";
newLevel._events = oldLevel._events;
newLevel._notes = oldLevel._notes;
newLevel._obstacles = oldLevel._obstacles;
newBeatmap._noteJumpMovementSpeed = oldLevel._noteJumpSpeed;
if (("_shuffle" in newInfo) && newInfo._shuffle != oldLevel._shuffle) {
console.log("Error: new level format doesn't support multiple shuffles");
return 5;
}
newInfo._shuffle = oldLevel._shuffle;
if (("_shufflePeriod" in newInfo) && newInfo._shufflePeriod != oldLevel._shufflePeriod) {
console.log("Error: new level format doesn't support multiple shuffle periods");
return 5;
}
newInfo._shufflePeriod = oldLevel._shufflePeriod;
// Write the level file & append the level info
fs.writeFileSync(newPath + "/" + newBeatmap._beatmapFilename, JSON.stringify(newLevel));
newInfo._difficultyBeatmapSets[cid]._difficultyBeatmaps.push(newBeatmap);
}
// Write the new info file & copy resources
fs.writeFileSync(newPath + "/Info.dat", JSON.stringify(newInfo));
fs.copyFileSync(oldPath + "/" + oldInfo.coverImagePath, newPath + "/" + newInfo._coverImageFilename);
fs.copyFileSync(oldPath + "/" + newInfo._songFilename, newPath + "/" + newInfo._songFilename);
console.log("Successfully converted Beat Saber map to " + newPath);
}, 3000);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment