Compare commits

...

7 Commits

Author SHA1 Message Date
Robert Paciorek
12bb663076 fix null exception in LoginParent 2025-06-27 22:37:58 +02:00
Robert Paciorek
a3db9c2bcf update dotnet version in Dockerfile 2025-06-27 22:37:58 +02:00
Spirtix
2aac24c37e fix indent 2025-06-27 22:37:58 +02:00
Spirtix
06daf21d8c update dependencies 2025-06-27 22:37:58 +02:00
Spirtix
dc303d871e mission deep copy
.net 8 deprecated BinaryFormatter serialization
apart from that proper deep copies are ~10x faster
2025-06-27 22:37:58 +02:00
Spirtix
3ffced2b60 password rehashing
asp net identity v3 uses a new hashing algorithm (hmac-sha256)
2025-06-27 22:37:58 +02:00
Spirtix
640d7ba664 switch to .net 9 2025-06-27 22:37:58 +02:00
34 changed files with 631 additions and 337 deletions

View File

@ -49,10 +49,19 @@ public class AuthenticationController : Controller {
user = ctx.Users.FirstOrDefault(e => e.Username == data.UserName);
}
if (user is null || new PasswordHasher<object>().VerifyHashedPassword(null, user.Password, data.Password) != PasswordVerificationResult.Success) {
if (user is null) {
return Ok(new ParentLoginInfo { Status = MembershipUserStatus.InvalidUserName });
}
PasswordVerificationResult result = new PasswordHasher<object>().VerifyHashedPassword(null, user.Password, data.Password);
if (result == PasswordVerificationResult.Failed) {
return Ok(new ParentLoginInfo { Status = MembershipUserStatus.InvalidPassword });
}
if (result == PasswordVerificationResult.SuccessRehashNeeded) {
user.Password = new PasswordHasher<object>().HashPassword(null, data.Password);
}
// Create session
Session session = new Session {
User = user,

View File

@ -1,5 +1,5 @@
# Use the official .NET SDK image for building the application
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
WORKDIR /src
# Copy the source code
@ -13,7 +13,7 @@ RUN mv src/mods /app && ln -s /app/mods src/
RUN mv src/assets /app && ln -s /app/assets src/
# Create clean run environment (without source and sdk)
# FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
# FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
# WORKDIR /app
# COPY --from=build /app .

View File

@ -4,8 +4,27 @@ namespace sodoff.Schema;
[XmlRoot(ElementName = "AR", Namespace = "")]
[Serializable]
public class AchievementReward
{
public class AchievementReward {
public AchievementReward() { }
public AchievementReward(AchievementReward other) {
if (other.UserItem != null)
UserItem = new UserItemData(other.UserItem);
Amount = other.Amount;
PointTypeID = other.PointTypeID;
ItemID = other.ItemID;
EntityID = other.EntityID;
EntityTypeID = other.EntityTypeID;
RewardID = other.RewardID;
AchievementID = other.AchievementID;
AllowMultiple = other.AllowMultiple;
MinAmount = other.MinAmount;
MaxAmount = other.MaxAmount;
Date = other.Date;
CommonInventoryID = other.CommonInventoryID;
}
[XmlElement(ElementName = "ui", IsNullable = true)]
public UserItemData UserItem { get; set; }

View File

@ -5,8 +5,15 @@ namespace sodoff.Schema;
[XmlRoot(ElementName = "BP", Namespace = "", IsNullable = true)]
[Serializable]
public class BluePrint
{
public class BluePrint {
public BluePrint() { }
public BluePrint(BluePrint other) {
Deductibles = other.Deductibles.Select(d => new BluePrintDeductibleConfig(d)).ToList();
Ingredients = other.Ingredients.Select(i => new BluePrintSpecification(i)).ToList();
Outputs = other.Outputs.Select(o => new BluePrintSpecification(o)).ToList();
}
[XmlElement(ElementName = "BPDC", IsNullable = true)]
public List<BluePrintDeductibleConfig> Deductibles { get; set; }

View File

@ -4,8 +4,16 @@ namespace sodoff.Schema;
[XmlRoot(ElementName = "BPDC", Namespace = "", IsNullable = true)]
[Serializable]
public class BluePrintDeductibleConfig
{
public class BluePrintDeductibleConfig {
public BluePrintDeductibleConfig() {}
public BluePrintDeductibleConfig(BluePrintDeductibleConfig other) {
BluePrintItemID = other.BluePrintItemID;
DeductibleType = other.DeductibleType;
ItemID = other.ItemID;
Quantity = other.Quantity;
}
[XmlElement(ElementName = "BPIID", IsNullable = false)]
public int BluePrintItemID { get; set; }

View File

@ -2,8 +2,20 @@ using System.Xml.Serialization;
namespace sodoff.Schema;
public class BluePrintSpecification
{
public class BluePrintSpecification {
public BluePrintSpecification() { }
public BluePrintSpecification(BluePrintSpecification other) {
BluePrintSpecID = other.BluePrintSpecID;
BluePrintItemID = other.BluePrintItemID;
ItemID = other.ItemID;
CategoryID = other.CategoryID;
ItemRarity = other.ItemRarity;
Tier = other.Tier;
Quantity = other.Quantity;
SpecificationType = other.SpecificationType;
}
[XmlElement(ElementName = "BPSID", IsNullable = false)]
public int BluePrintSpecID { get; set; }

View File

@ -3,8 +3,13 @@ using System.Xml.Serialization;
namespace sodoff.Schema;
[Serializable]
public class CompletionAction
{
public class CompletionAction {
public CompletionAction() { }
public CompletionAction(CompletionAction other) {
Transition = other.Transition;
}
[XmlElement(ElementName = "Transition")]
public StateTransition Transition;
}

View File

@ -4,8 +4,14 @@ namespace sodoff.Schema;
[XmlRoot(ElementName = "AT", Namespace = "")]
[Serializable]
public class ItemAttribute
{
public class ItemAttribute {
public ItemAttribute() { }
public ItemAttribute(ItemAttribute other) {
Key = other.Key;
Value = other.Value;
}
[XmlElement(ElementName = "k")]
public string Key;

View File

@ -4,8 +4,14 @@ namespace sodoff.Schema;
[XmlRoot(ElementName = "Availability", Namespace = "")]
[Serializable]
public class ItemAvailability
{
public class ItemAvailability {
public ItemAvailability() { }
public ItemAvailability(ItemAvailability other) {
StartDate = other.StartDate;
EndDate = other.EndDate;
}
[XmlElement(ElementName = "sdate", IsNullable = true)]
public DateTime? StartDate;

View File

@ -3,8 +3,45 @@ using System.Xml.Serialization;
namespace sodoff.Schema;
[XmlRoot(ElementName = "I", Namespace = "", IsNullable = true)]
public class ItemData
{
public class ItemData {
public ItemData() { }
public ItemData(ItemData other) {
ItemStates = other.ItemStates.Select(s => new ItemState(s)).ToList();
ItemRarity = other.ItemRarity;
PossibleStatsMap = new ItemPossibleStatsMap(other.PossibleStatsMap);
ItemStatsMap = new ItemStatsMap(other.ItemStatsMap);
ItemSaleConfigs = other.ItemSaleConfigs.Select(cfg => new ItemSaleConfig(cfg)).ToArray();
BluePrint = new BluePrint(other.BluePrint);
AssetName = other.AssetName;
Attribute = other.Attribute.Select(attr => new ItemAttribute(attr)).ToArray();
Category = other.Category.Select(cat => new ItemDataCategory(cat)).ToArray();
Cost = other.Cost;
CashCost = other.CashCost;
CreativePoints = other.CreativePoints;
Description = other.Description;
IconName = other.IconName;
InventoryMax = other.InventoryMax;
ItemID = other.ItemID;
ItemName = other.ItemName;
ItemNamePlural = other.ItemNamePlural;
Locked = other.Locked;
Geometry2 = other.Geometry2;
Rollover = new ItemDataRollover(other.Rollover);
RankId = other.RankId;
Relationship = other.Relationship.Select(r => new ItemDataRelationship(r)).ToArray();
Stackable = other.Stackable;
AllowStacking = other.AllowStacking;
SaleFactor = other.SaleFactor;
Texture = other.Texture.Select(t => new ItemDataTexture(t)).ToArray();
Uses = other.Uses;
Availability = other.Availability.Select(a => new ItemAvailability(a)).ToArray();
RewardTypeID = other.RewardTypeID;
Points = other.Points;
NormalDiscoutModifier = other.NormalDiscoutModifier;
MemberDiscountModifier = other.MemberDiscountModifier;
}
[XmlElement(ElementName = "is")]
public List<ItemState> ItemStates { get; set; }

View File

@ -4,8 +4,15 @@ namespace sodoff.Schema;
[XmlRoot(ElementName = "IC", Namespace = "")]
[Serializable]
public class ItemDataCategory
{
public class ItemDataCategory {
public ItemDataCategory() { }
public ItemDataCategory(ItemDataCategory other) {
CategoryId = other.CategoryId;
CategoryName = other.CategoryName;
IconName = other.IconName;
}
[XmlElement(ElementName = "cid")]
public int CategoryId;

View File

@ -4,8 +4,17 @@ namespace sodoff.Schema;
[XmlRoot(ElementName = "IRE", Namespace = "")]
[Serializable]
public class ItemDataRelationship
{
public class ItemDataRelationship {
public ItemDataRelationship() { }
public ItemDataRelationship(ItemDataRelationship other) {
Type = other.Type;
ItemId = other.ItemId;
Weight = other.Weight;
Quantity = other.Quantity;
MaxQuantity = other.MaxQuantity;
}
[XmlElement(ElementName = "t")]
public string Type;

View File

@ -4,8 +4,14 @@ namespace sodoff.Schema;
[XmlRoot(ElementName = "IRO", Namespace = "")]
[Serializable]
public class ItemDataRollover
{
public class ItemDataRollover {
public ItemDataRollover() { }
public ItemDataRollover(ItemDataRollover other) {
DialogName = other.DialogName;
Bundle = other.Bundle;
}
[XmlElement(ElementName = "d")]
public string DialogName;

View File

@ -4,8 +4,16 @@ namespace sodoff.Schema;
[XmlRoot(ElementName = "IT", Namespace = "")]
[Serializable]
public class ItemDataTexture
{
public class ItemDataTexture {
public ItemDataTexture() { }
public ItemDataTexture(ItemDataTexture other) {
TextureName = other.TextureName;
TextureTypeName = other.TextureTypeName;
OffsetX = other.OffsetX;
OffsetY = other.OffsetY;
}
[XmlElement(ElementName = "n")]
public string TextureName;

View File

@ -4,8 +4,16 @@ namespace sodoff.Schema;
[XmlRoot(ElementName = "IPSM", Namespace = "", IsNullable = false)]
[Serializable]
public class ItemPossibleStatsMap
{
public class ItemPossibleStatsMap {
public ItemPossibleStatsMap() { }
public ItemPossibleStatsMap(ItemPossibleStatsMap other) {
ItemID = other.ItemID;
ItemStatsCount = other.ItemStatsCount;
SetID = other.SetID;
Stats = other.Stats.Select(s => new Stat(s)).ToList();
}
[XmlElement(ElementName = "IID", IsNullable = false)]
public int ItemID { get; set; }

View File

@ -4,8 +4,17 @@ namespace sodoff.Schema;
[XmlRoot(ElementName = "ISC", Namespace = "", IsNullable = true)]
[Serializable]
public class ItemSaleConfig
{
public class ItemSaleConfig {
public ItemSaleConfig() { }
public ItemSaleConfig(ItemSaleConfig other) {
ItemID = other.ItemID;
CategoryID = other.CategoryID;
RarityID = other.RarityID;
Quantity = other.Quantity;
RewardItemID = other.RewardItemID;
}
[XmlElement(ElementName = "IID", IsNullable = true)]
public int? ItemID { get; set; }

View File

@ -4,8 +4,16 @@ namespace sodoff.Schema;
[XmlRoot(ElementName = "IS", Namespace = "")]
[Serializable]
public class ItemStat
{
public class ItemStat {
public ItemStat() { }
public ItemStat(ItemStat other) {
ItemStatID = other.ItemStatID;
Name = other.Name;
Value = other.Value;
DataType = other.DataType;
}
[XmlElement(ElementName = "ID")]
public int ItemStatID { get; set; }

View File

@ -4,8 +4,18 @@ namespace sodoff.Schema;
[XmlRoot(ElementName = "ItemState", Namespace = "")]
[Serializable]
public class ItemState
{
public class ItemState {
public ItemState() { }
public ItemState(ItemState other) {
ItemStateID = other.ItemStateID;
Name = other.Name;
Rule = new ItemStateRule(other.Rule);
Order = other.Order;
AchievementID = other.AchievementID;
Rewards = other.Rewards.Select(r => new AchievementReward(r)).ToArray();
}
[XmlElement(ElementName = "ItemStateID")]
public int ItemStateID;

View File

@ -10,8 +10,13 @@ namespace sodoff.Schema;
[XmlInclude(typeof(ItemStateCriteriaSpeedUpItem))]
[XmlInclude(typeof(ItemStateCriteriaExpiry))]
[Serializable]
public class ItemStateCriteria
{
public class ItemStateCriteria {
public ItemStateCriteria() { }
public ItemStateCriteria(ItemStateCriteria other) {
Type = other.Type;
}
[XmlElement(ElementName = "Type")]
public ItemStateCriteriaType Type;
}

View File

@ -4,8 +4,14 @@ namespace sodoff.Schema;
[XmlRoot(ElementName = "ItemStateRule", Namespace = "")]
[Serializable]
public class ItemStateRule
{
public class ItemStateRule {
public ItemStateRule() { }
public ItemStateRule(ItemStateRule other) {
Criterias = other.Criterias.Select(c => new ItemStateCriteria(c)).ToList();
CompletionAction = new CompletionAction(other.CompletionAction);
}
[XmlElement(ElementName = "Criterias")]
public List<ItemStateCriteria> Criterias;

View File

@ -4,8 +4,15 @@ namespace sodoff.Schema;
[XmlRoot(ElementName = "ISM", Namespace = "", IsNullable = false)]
[Serializable]
public class ItemStatsMap
{
public class ItemStatsMap {
public ItemStatsMap() { }
public ItemStatsMap(ItemStatsMap other) {
ItemID = other.ItemID;
ItemTier = other.ItemTier;
ItemStats = other.ItemStats.Select(s => new ItemStat(s)).ToArray();
}
[XmlElement(ElementName = "IID", IsNullable = false)]
public int ItemID { get; set; }

View File

@ -3,8 +3,32 @@
namespace sodoff.Schema;
[XmlRoot(ElementName = "Mission", Namespace = "")]
[Serializable] // FIXME: Remove serializable once we have a different way of deep copying than BinaryFormatter
[Serializable]
public class Mission {
public Mission() { }
public Mission(Mission other) {
if (other == null) throw new ArgumentNullException(nameof(other));
MissionID = other.MissionID;
Name = other.Name;
GroupID = other.GroupID;
ParentID = other.ParentID;
Static = other.Static;
Accepted = other.Accepted;
Completed = other.Completed;
Rule = other.Rule;
VersionID = other.VersionID;
AchievementID = other.AchievementID;
AcceptanceAchievementID = other.AcceptanceAchievementID;
Repeatable = other.Repeatable;
MissionRule = new MissionRule(other.MissionRule);
Missions = other.Missions.Select(m => new Mission(m)).ToList();
Tasks = other.Tasks.Select(t => new Task(t)).ToList();
Rewards = other.Rewards.Select(r => new AchievementReward(r)).ToList();
AcceptanceRewards = other.AcceptanceRewards.Select(a => new AchievementReward(a)).ToList();
}
[XmlElement(ElementName = "I")]
public int MissionID;

View File

@ -5,6 +5,16 @@ namespace sodoff.Schema;
[XmlRoot(ElementName = "MissionCriteria", Namespace = "")]
[Serializable]
public class MissionCriteria {
public MissionCriteria() { }
public MissionCriteria(MissionCriteria other) {
Type = other.Type;
Ordered = other.Ordered;
Min = other.Min;
Repeat = other.Repeat;
RuleItems = other.RuleItems.Select(r => new RuleItem(r)).ToList();
}
[XmlElement(ElementName = "Type")]
public string Type;

View File

@ -5,6 +5,13 @@ namespace sodoff.Schema;
[XmlRoot(ElementName = "MissionRule", Namespace = "")]
[Serializable]
public class MissionRule {
public MissionRule() { }
public MissionRule(MissionRule other) {
Prerequisites = other.Prerequisites.Select(p => new PrerequisiteItem(p)).ToList();
Criteria = new MissionCriteria(other.Criteria);
}
[XmlElement(ElementName = "Prerequisites")]
public List<PrerequisiteItem> Prerequisites;

View File

@ -4,8 +4,15 @@ namespace sodoff.Schema;
[XmlRoot(ElementName = "Pair", Namespace = "")]
[Serializable]
public class Pair
{
public class Pair {
public Pair() { }
public Pair(Pair other) {
PairKey = other.PairKey;
PairValue = other.PairValue;
UpdateDate = other.UpdateDate;
}
[XmlElement(ElementName = "PairKey")]
public string PairKey;

View File

@ -5,6 +5,12 @@ namespace sodoff.Schema;
[XmlRoot(ElementName = "Pairs", Namespace = "", IsNullable = true)]
[Serializable]
public class PairData {
public PairData() { }
public PairData(PairData other) {
Pairs = other.Pairs.Select(p => new Pair(p)).ToArray();
}
[XmlElement("Pair", IsNullable = true)]
public Pair[] Pairs { get; set; }
}

View File

@ -5,6 +5,15 @@ namespace sodoff.Schema;
[XmlRoot(ElementName = "PrerequisiteItem", Namespace = "")]
[Serializable]
public class PrerequisiteItem {
public PrerequisiteItem() { }
public PrerequisiteItem(PrerequisiteItem other) {
Type = other.Type;
Value = other.Value;
Quantity = other.Quantity;
ClientRule = other.ClientRule;
}
[XmlElement(ElementName = "Type")]
public PrerequisiteRequiredType Type;

View File

@ -5,6 +5,15 @@ namespace sodoff.Schema;
[XmlRoot(ElementName = "RuleItem", Namespace = "")]
[Serializable]
public class RuleItem {
public RuleItem() { }
public RuleItem(RuleItem other) {
Type = other.Type;
MissionID = other.MissionID;
ID = other.ID;
Complete = other.Complete;
}
[XmlElement(ElementName = "Type")]
public RuleItemType Type;

View File

@ -4,8 +4,17 @@ namespace sodoff.Schema;
[XmlRoot(ElementName = "STAT", Namespace = "", IsNullable = false)]
[Serializable]
public class Stat
{
public class Stat {
public Stat() { }
public Stat(Stat other) {
ItemID = other.ItemID;
ItemStatsID = other.ItemStatsID;
SetID = other.SetID;
Probability = other.Probability;
ItemStatsRangeMaps = other.ItemStatsRangeMaps.Select(s => new StatRangeMap(s)).ToList();
}
[XmlElement(ElementName = "IID", IsNullable = false)]
public int ItemID { get; set; }

View File

@ -4,8 +4,17 @@ namespace sodoff.Schema;
[XmlRoot(ElementName = "SRM", Namespace = "", IsNullable = false)]
[Serializable]
public class StatRangeMap
{
public class StatRangeMap {
public StatRangeMap() { }
public StatRangeMap(StatRangeMap other) {
ItemStatsID = other.ItemStatsID;
ItemStatsName = other.ItemStatsName;
ItemTierID = other.ItemTierID;
StartRange = other.StartRange;
EndRange = other.EndRange;
}
[XmlElement(ElementName = "ISID", IsNullable = false)]
public int ItemStatsID { get; set; }

View File

@ -8,6 +8,17 @@ namespace sodoff.Schema;
[XmlRoot(ElementName = "Task", Namespace = "")]
[Serializable]
public class Task {
public Task() { }
public Task(Task other) {
TaskID = other.TaskID;
Name = other.Name;
Static = other.Static;
Completed = other.Completed;
Failed = other.Failed;
Payload = other.Payload;
}
[XmlElement(ElementName = "I")]
public int TaskID;

View File

@ -4,8 +4,22 @@ namespace sodoff.Schema;
[XmlRoot(ElementName = "UserItem", Namespace = "")]
[Serializable]
public class UserItemData
{
public class UserItemData {
public UserItemData() { }
public UserItemData(UserItemData other) {
ItemID = other.ItemID;
ModifiedDate = other.ModifiedDate;
UserItemAttributes = new PairData(other.UserItemAttributes);
ItemStats = other.ItemStats.Select(stat => new ItemStat(stat)).ToArray();
ItemTier = other.ItemTier;
CreatedDate = other.CreatedDate;
UserInventoryID = other.UserInventoryID;
Quantity = other.Quantity;
Uses = other.Uses;
Item = new ItemData(other.Item);
}
[XmlElement(ElementName = "iid")]
public int ItemID { get; set; }

View File

@ -119,20 +119,8 @@ public class MissionStoreSingleton {
}
}
// FIXME: Don't use BinaryFormatter for deep copying
// FIXME: Remove <EnableUnsafeBinaryFormatterSerialization> flag from the project file once we have a different way of deep copying
public static Mission DeepCopy(Mission original) {
using (MemoryStream memoryStream = new MemoryStream()) {
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(memoryStream, original);
memoryStream.Position = 0;
Mission clone = (Mission)formatter.Deserialize(memoryStream);
return clone;
}
return new Mission(original);
}
}

View File

@ -1,10 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<EnableUnsafeBinaryFormatterSerialization>true</EnableUnsafeBinaryFormatterSerialization>
<DefineConstants>USE_SQLITE;$(DefineConstants)</DefineConstants>
<DefineConstants>USE_POSTGRESQL;$(DefineConstants)</DefineConstants>
@ -12,26 +11,26 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="7.0.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="9.0.6" />
</ItemGroup>
<Choose>
<When Condition="$(DefineConstants.Contains('USE_SQLITE'))">
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.6" />
</ItemGroup>
</When>
</Choose>
<Choose>
<When Condition="$(DefineConstants.Contains('USE_POSTGRESQL'))">
<ItemGroup>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="7.0.11" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
</ItemGroup>
</When>
</Choose>
<Choose>
<When Condition="$(DefineConstants.Contains('USE_MYSQL'))">
<ItemGroup>
<PackageReference Include="MySql.EntityFrameworkCore" Version="7.0.5" />
<PackageReference Include="MySql.EntityFrameworkCore" Version="9.0.3" />
</ItemGroup>
</When>
</Choose>