A library to parse items in the simc import format into functional objects.
PLEASE NOTE: This library is still a work in progress and is being created to support another project.
Please raise an issue if something isn't working as you would expect or throws a NotYetImplemented
exception and it may be prioritised.
Requires .NET 10. See the changelog for release notes and upgrade steps between major versions.
The examples below assume these namespaces are imported:
using SimcProfileParser; // SimcGenerationService, AddSimcProfileParser()
using SimcProfileParser.Interfaces; // ISimcGenerationService
using SimcProfileParser.Model.Generated; // SimcItemOptions, SimcSpellOptions and the generated models
using SimcProfileParser.Model.RawData; // ItemQuality, InventoryType enumsA new instance can be manually created.
ISimcGenerationService sgs = new SimcGenerationService();To provide logging to the new instance and its children, supply an ILoggerFactory:
ISimcGenerationService sgs = new SimcGenerationService(myLoggerFactory);To implement this using Dependency Injection register it alongside your other services configuration
using the AddSimcProfileParser() extension method when configuring your DI services:
public void ConfigureServices(IServiceCollection services)
{
services.AddSimcProfileParser();
}Then you request an instance of ISimcGenerationService through DI in your class constructors:
class MyClass
{
private readonly ISimcGenerationService _simcGenerationService;
public MyClass(ISimcGenerationService simcGenerationService)
{
_simcGenerationService = simcGenerationService;
}
}Generating a profile object from a simc import file named import.simc:
ISimcGenerationService sgs = new SimcGenerationService();
// Using async
var profile = await sgs.GenerateProfileAsync(File.ReadAllText("import.simc"));
// Output some details about the profile
Console.WriteLine($"Profile: {profile.ParsedProfile.Name} - Level {profile.ParsedProfile.Level}");
Console.WriteLine($"Items loaded: {profile.GeneratedItems.Count}");
Console.WriteLine($"Talents loaded: {profile.Talents.Count}");
var json = JsonSerializer.Serialize(profile, new JsonSerializerOptions() { WriteIndented = true });
await File.WriteAllTextAsync("profile.json", json);You can also generate a profile object from individual lines of an import file:
ISimcGenerationService sgs = new SimcGenerationService();
var lines = new List<string>()
{
"level=90",
"main_hand=,id=237728,bonus_id=6652/10356/13446/1540/10255"
};
var profile = await sgs.GenerateProfileAsync(lines);
// Access parsed data
Console.WriteLine($"Profile object created for a level {profile.ParsedProfile.Level}");
// Access enriched items with full stats
var firstItem = profile.GeneratedItems.FirstOrDefault();
Console.WriteLine($"Wielding {firstItem.Name} with {firstItem.Mods.Count} stats");
var json = JsonSerializer.Serialize(profile, new JsonSerializerOptions() { WriteIndented = true });
await File.WriteAllTextAsync("profile.json", json);Understanding Profile Output:
ParsedProfile- Raw parsed character data (name, class, spec, level, etc.)GeneratedItems- Fully enriched items with calculated stats, gems, enchants, sockets, and effectsTalents- Talent details including spell IDs, names, and ranks
There are some basic options to manually create an item using ISimcGenerationService.GenerateItemAsync.
ISimcGenerationService sgs = new SimcGenerationService();
var itemOptions = new SimcItemOptions()
{
ItemId = 242392,
Quality = ItemQuality.ITEM_QUALITY_EPIC,
ItemLevel = 730
};
var item = await sgs.GenerateItemAsync(itemOptions);
Console.WriteLine($"Item: {item.Name} (iLevel {item.ItemLevel})");
Console.WriteLine($"Stats: {item.Mods.Count}, Sockets: {item.Sockets.Count}, Effects: {item.Effects.Count}");
var json = JsonSerializer.Serialize(item, new JsonSerializerOptions() { WriteIndented = true });
await File.WriteAllTextAsync("item.json", json);Available item options:
public uint ItemId { get; set; } // Required: The game item ID
public int ItemLevel { get; set; } // Override the base item level (Midnight item level bonuses can still change it)
public IList<int> BonusIds { get; set; } // Bonus IDs that modify stats, sockets, or item level
public IList<int> GemIds { get; set; } // Gem item IDs to socket into the item
public IList<int> CraftedStatIds { get; set; } // Stat IDs for player-crafted items
public ItemQuality Quality { get; set; } // Override item quality
public int DropLevel { get; set; } // Character level when item dropped (affects scaling)
public int ContentTuningId { get; set; } // content_tuning= from a simc profile (affects scaling)
public uint RedirectedBaseStatsItemId { get; set; } // redirected_base_stats= from a simc profile, for catalysed items
public int EnchantId { get; set; } // Permanent enchant (enchant_id=)
public int PlayerLevel { get; set; } // Player level to scale gems and enchants to (default: max level)Item Generation Details:
- Bonus IDs: Modify items by adding stats, sockets, changing item level, or quality
- Gems: Add secondary stats when socketed into items with sockets
- Enchants:
SimcItem.Enchantlists the stats a permanent enchant adds, kept separate from the item's own stats inMods, and the IDs of any spells it applies (procs, equip and use effects) - Drop Level: Affects item level scaling for items with dynamic scaling curves
- Crafted Stats: Specify which secondary stats player-crafted gear should have
- Explicit ItemLevel: If set, overrides any item level modifications from bonus IDs
There are some basic options to manually create a spell using ISimcGenerationService.GenerateSpellAsync.
There are two types of spell scaling:
Item Scaling - For spells that scale with item level (trinkets, enchants, weapon procs):
ISimcGenerationService sgs = new SimcGenerationService();
var spellOptions = new SimcSpellOptions()
{
SpellId = 1238697,
ItemLevel = 730,
ItemQuality = ItemQuality.ITEM_QUALITY_EPIC,
ItemInventoryType = InventoryType.INVTYPE_TRINKET
};
var spell = await sgs.GenerateSpellAsync(spellOptions);
// Calculate scaled effect values
var effect = spell.Effects.FirstOrDefault();
var scaledValue = effect.BaseValue + (effect.Coefficient * effect.ScaleBudget);
Console.WriteLine($"Spell: {spell.Name}, Scaled Value: {scaledValue}");
var json = JsonSerializer.Serialize(spell, new JsonSerializerOptions() { WriteIndented = true });
await File.WriteAllTextAsync("spell.json", json);Player Scaling - For spells that scale with player level (racials, class abilities):
ISimcGenerationService sgs = new SimcGenerationService();
var spellOptions = new SimcSpellOptions()
{
SpellId = 274740,
PlayerLevel = 90
};
var spell = await sgs.GenerateSpellAsync(spellOptions);
// The effect's value at level 90, as simc's allspells.txt shows it
var effect = spell.Effects.FirstOrDefault();
Console.WriteLine($"Spell: {spell.Name}, Scaled Value: {effect.ScaledValue}");
var json = JsonSerializer.Serialize(spell, new JsonSerializerOptions() { WriteIndented = true });
await File.WriteAllTextAsync("spell.json", json);Each spell effect has a ScaleBudget property that should be multiplied with the effect's coefficient to get the final scaled value.
For simple cases, the BaseValue property contains the unscaled value.
For player spells, SimcSpellEffect.ScaledValue is the effect's value at the player level, matching "Scaled Value" in
simc's allspells.txt:
- Effects with a coefficient:
Coefficient×ScaleBudget - Effects with points per level:
BaseValue+PointsPerLevelfor each level from the spell's level up to the player level (capped at the spell's max level, if it has one). For example, effect #4 of the Holy Priest aura (137031) hasBaseValue0 andPointsPerLevel1, so itsScaledValueis 90 at level 90. - Anything else:
BaseValue
Where simc shows a range (effects with a Delta), ScaledValue is the midpoint. For item spells, ScaledValue is
always BaseValue; use Coefficient × ScaleBudget as above.
Spell relationships - Generated spells also list which spells modify each other, matching the "Affected Spells" / "Affecting Spells" output in simc's allspells.txt:
SimcSpell.AffectedBySpellIds- IDs of spells with an effect that modifies this spell (talents, class auras, set bonuses, etc.)SimcSpellEffect.AffectedSpellIds- IDs of spells this effect modifies
Relationships are matched three ways, and each list contains every spell ID once:
- Class family flags: e.g. Add Flat/Percent Modifier effects (subtypes 107/108).
- Spell category: cooldown and charge modifiers. The category is the effect's
MiscValue1. - Spell label: e.g. Apply Flat/Percent Modifier w/ Label effects (subtypes 219/218), which modify every spell carrying a label.
The label is the effect's
MiscValue2(orMiscValue1for other label effects, such as cooldown recharge rate).
Broad labels such as "Class Spells" (16) or "Priest Spells" (18) are matched like any other label, so an effect targeting them lists every spell with that label. simc's dump shows only the label name in that case.
The raw data is also available so you can do your own matching:
SimcSpell.Labels: the spell's label IDsSimcSpellEffect.MiscValue1/MiscValue2: for spell modifiers,MiscValue1is the property modified (e.g. damage, duration, cooldown)
Talent rank values - For talents whose effect values change per rank, each effect lists the values for every rank,
matching the "Talent Entry" line (Effect#1 [op=set, values=(10, 20)]) in simc's allspells.txt:
SimcSpellEffect.RankOperation:Set(the rank value replacesBaseValue) orMultiply(the rank value multipliesBaseValue), ornullif the effect has no per-rank valuesSimcSpellEffect.RankValues: the raw value for each rank, normally index 0 = rank 1
BaseValue is always the unmodified spell data value. For example, Light of the Naaru (196985) has BaseValue 10,
RankOperation Set and RankValues [10, 20], so its effect is 20 at rank 2. Critical Mass (117216) has
RankOperation Multiply and RankValues [1, 2], so its effect is BaseValue × 2 at rank 2.
ISimcGenerationService sgs = new SimcGenerationService();
// Get all available talents for a class/spec
// Example: Holy Priest (classId: 5, specId: 257)
var talents = await sgs.GetAvailableTalentsAsync(classId: 5, specId: 257);
Console.WriteLine($"Found {talents.Count} talents for Holy Priest");Base attributes, racial modifiers, base mana and combat rating conversions come from simc's data, using the same branch and PTR settings as everything else.
ISimcGenerationService sgs = new SimcGenerationService();
// Class base attributes at a level, before racial modifiers. Priest (5) at level 90: 360 / 515 / 4600 / 620
var classStats = await sgs.GetClassBaseStatsAsync(classId: 5, level: 90);
// Racial modifiers, by the same race ID SimcParsedProfile.RaceId returns. Gnome (7): -3 / +1 / -1 / +3
var raceStats = await sgs.GetRaceBaseStatsAsync(raceId: 7);
// Base mana. Priest at level 90: 250,000
var baseMana = await sgs.GetBaseManaAsync(classId: 5, level: 90);
// Rating needed for 1% at a level, e.g. 46 crit rating per 1% crit at level 90
var ratings = await sgs.GetCombatRatingsAsync(level: 90);
Console.WriteLine($"Intellect: {classStats.Intellect + raceStats.Intellect}, crit rating per 1%: {ratings.CritSpell}");GetClassBaseStatsAsyncreturns null for levels simc has no data for, e.g. above the current max level.GetRaceBaseStatsAsyncuses simc's race IDs (util::race_id), which matchSimcParsedProfile.RaceIdbut differ from the game's race IDs for some races, e.g. Dark Iron Dwarf is 12 rather than 34.- Combat ratings are before any class or specialization multiplier, such as a specialization's mastery coefficient.
The library automatically downloads game data from SimulationCraft's GitHub repository. You can control which version of the data to use:
var service = new SimcGenerationService();
// Use PTR (Public Test Realm) data instead of live game data
service.UsePtrData = true;
// Switch between expansions
service.UseBranchName = "midnight"; // Midnight expansion (WoW 12.x) - default
// service.UseBranchName = "thewarwithin"; // The War Within (WoW 11.x)
// service.UseBranchName = "dragonflight"; // Dragonflight (WoW 10.x)
// Check which game version the data is from
var version = await service.GetGameDataVersionAsync();
Console.WriteLine($"Using game data from WoW version: {version}");Note: Changing UsePtrData or UseBranchName will clear all cached data to prevent mixing incompatible game versions.
Game data files are automatically downloaded and cached in your system's temp directory for performance. The library handles:
- Automatic downloading when data is first needed
- ETag-based cache validation to check for updates
- Efficient memory caching of parsed data
Cache location: Path.GetTempPath() + "SimcProfileParserData"
- Item data: Base stats, sockets, item levels, bonus IDs, gems, enchants
- Spell data: Effects, scaling, cooldowns, durations, proc rates (RPPM)
- Talent data: Talent trees, spell IDs, ranks, requirements
- Scaling data: Combat rating multipliers, stamina multipliers, spell scaling by level
- Character data: Class base attributes, racial modifiers, base mana and combat ratings by level
- Some newer bonus ID types may not be fully implemented
- Complex talent prerequisites and choice nodes are not validated
- Covenant/Soulbind data is parsed but may not be enriched (legacy content)
The library throws exceptions for invalid inputs:
ArgumentNullException- When required parameters are null or emptyArgumentOutOfRangeException- When item/spell IDs are not found in the game dataNotImplementedException- When encountering unsupported game features
Always wrap API calls in try-catch blocks when dealing with user input.
For bugs please search issues then create a new issue if needed.
For help using this library, please check the wiki or visit discord.