ppy/osu · error · InvalidOperationException

{setInfo.GetDisplayString()} already has a difficulty with t

Error message

{setInfo.GetDisplayString()} already has a difficulty with the name of '{beatmapInfo.DifficultyName}'.

What it means

Thrown by BeatmapManager.Save when the filename computed from beatmap metadata (via createBeatmapFilenameFromMetadata) matches the Path of another difficulty already in the same beatmap set. Since difficulty filenames are derived from the difficulty name, this effectively means two difficulties share the same name (case-insensitive comparison).

Source

Thrown at osu.Game/Beatmaps/BeatmapManager.cs:562

            // Since now this is a locally-modified beatmap, we also set all relevant flags to indicate this.
            beatmapInfo.LastLocalUpdate = DateTimeOffset.Now;
            beatmapInfo.Status = BeatmapOnlineStatus.LocallyModified;

            Realm.Write(r =>
            {
                using var stream = new MemoryStream();
                using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
                    new LegacyBeatmapEncoder(beatmapContent, beatmapSkin, storyboard).Encode(sw);

                stream.Seek(0, SeekOrigin.Begin);

                // AddFile generally handles updating/replacing files, but this is a case where the filename may have also changed so let's delete for simplicity.
                var existingFileInfo = beatmapInfo.Path != null ? setInfo.GetFile(beatmapInfo.Path) : null;
                string targetFilename = createBeatmapFilenameFromMetadata(beatmapInfo);

                // ensure that two difficulties from the set don't point at the same beatmap file.
                if (setInfo.Beatmaps.Any(b => b.ID != beatmapInfo.ID && string.Equals(b.Path, targetFilename, StringComparison.OrdinalIgnoreCase)))
                    throw new InvalidOperationException($"{setInfo.GetDisplayString()} already has a difficulty with the name of '{beatmapInfo.DifficultyName}'.");

                if (existingFileInfo != null)
                    DeleteFile(setInfo, existingFileInfo);

                string oldMd5Hash = beatmapInfo.MD5Hash;

                beatmapInfo.MD5Hash = stream.ComputeMD5Hash();
                beatmapInfo.Hash = stream.ComputeSHA2Hash();

                AddFile(setInfo, stream, createBeatmapFilenameFromMetadata(beatmapInfo));

                updateHashAndMarkDirty(setInfo);

                var liveBeatmapSet = r.Find<BeatmapSetInfo>(setInfo.ID)!;

                setInfo.CopyChangesToRealm(liveBeatmapSet);

                if (transferCollections)

View on GitHub (pinned to d9c73e12ad)

Solutions

  1. Rename the difficulty to a unique name within its beatmap set before saving.
  2. Before calling Save, check whether any sibling difficulty (b.ID != beatmapInfo.ID) already uses the same DifficultyName (case-insensitive).
  3. If the duplicate is a stale entry, remove or rename the conflicting difficulty from the set first.

Example fix

// before
beatmapInfo.DifficultyName = "Insane";
beatmapManager.Save(beatmapInfo, beatmapContent); // throws if another 'Insane' exists

// after
bool nameExists = setInfo.Beatmaps.Any(b => b.ID != beatmapInfo.ID
    && string.Equals(b.DifficultyName, "Insane", StringComparison.OrdinalIgnoreCase));
if (nameExists)
    throw new InvalidOperationException("A difficulty named 'Insane' already exists in this set.");
beatmapInfo.DifficultyName = "Insane";
beatmapManager.Save(beatmapInfo, beatmapContent);
Defensive patterns

Strategy: validation

Validate before calling

// Check for duplicate difficulty names before saving
bool nameCollision = setInfo.Beatmaps.Any(b =>
    b.ID != beatmapInfo.ID &&
    string.Equals(b.DifficultyName, beatmapInfo.DifficultyName, StringComparison.OrdinalIgnoreCase));
if (nameCollision)
    throw new InvalidOperationException($"Difficulty name '{beatmapInfo.DifficultyName}' already exists in this set");

Try / catch

try
{
    beatmapManager.Save(beatmapInfo, beatmapContent);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("already has a difficulty"))
{
    // prompt user to pick a unique difficulty name
}

Prevention

When it happens

Trigger: Renaming a difficulty to the same name as another difficulty in the same beatmap set, then calling Save — createBeatmapFilenameFromMetadata produces an identical filename, and the collision check at BeatmapManager.cs:561 catches it.

Common situations: User edits a difficulty name in the editor to match an existing difficulty in the set (e.g., both named 'Insane'); copying a difficulty and forgetting to rename it; case-variation collisions ('Hard' vs 'hard') since the comparison is OrdinalIgnoreCase.

Related errors


AI-assisted analysis of ppy/osu@d9c73e12ad (2026-08-13). Data as JSON: /api/errors/900dc85f6efdaae5. Report an issue: GitHub.