Skip to content

[Bugfix] Updating scorehash on every password changes/Explicitly save config.xml into shift_jis - #6

Merged
Unengine merged 9 commits into
mainfrom
bugfix/xml-encoding-password-nuke
Jun 28, 2026
Merged

[Bugfix] Updating scorehash on every password changes/Explicitly save config.xml into shift_jis#6
Unengine merged 9 commits into
mainfrom
bugfix/xml-encoding-password-nuke

Conversation

@Unengine

Copy link
Copy Markdown
Owner

Changes

  • Fix config.xml to be saved explicitly in shift_jis and xml version 1.0
  • Make every scorehash in scores table in <player>.db updated when the password is changed.

Related Issues

#4, #5

@Unengine Unengine self-assigned this Jun 28, 2026
@Unengine Unengine added the bug Something isn't working label Jun 28, 2026
updateScoreCmd.Parameters.Add("@hash", SqliteType.Text);
updateScoreCmd.Prepare();

using (var selectScoresCmd = new SqliteCommand(SelectScoresQuery, connection, transaction))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd make sure current 'scorehash' values are valid, otherwise 'scorehash' as a concept loses value
You can even do it in the same loop since you have a transaction, just don't forget to ROLLBACK on bad scores.
To check scorehash validity, you'd have to add some fallbacks that manipulate 'rank'.
Use OpenLR2 as a reference: https://github.com/GOMazk/OpenLR2/blob/15b2fc066a8bdbfcc28d3d2a88e6467048f7bd7f/LR2/LR2_statlong.cpp#L190

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ve added the validation logic as you suggested. Any invalid scorehash is now automatically corrected to the valid one within the transaction loop. By ensuring only valid data is written, we prevent bad scores from being persisted, which keeps the data integrity solid without needing extra rollback steps.

Regarding the ROLLBACK strategy: I've ensured that any unexpected exception during the hash correction will trigger a rollback.

Comment thread src/Models/DBPlayerRow.cs

public class DBPlayerRow(SqliteDataReader reader)
{
public string? Id { get; set; } = DatabaseUtils.GetValue<string>(reader, "id");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here and everywhere below, is it necessary to have strings as optionals? I'm not familiar with C#

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The goal is to explicitly distinguish between an intentional null assignment and an implicit default null state. While the type distinction might seem like extra overhead at first, it's far more cost-effective to handle these cases as compile-time warnings than to deal with runtime errors.

That's why C# strictly flags potential nullability for all variables at compile time. It acts as a contract: it either warns you that a variable might be null and requires handling, or gives you a strong guarantee that it won't be null, allowing you to use it safely. I'm leveraging these features to make the code's intent clearer and more robust.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In short, cleaning up all those compiler warnings by using nullable types gives me a sense of psychological safety. And honestly, that kind of peace of mind is invaluable XD

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know SQL tables don't have 'NOT NULL' in them, but they will never be NULL in practice.
If we detect a NULL, we should reject such database early.
If I understand you correctly, this nullability flag forces you to check that the string is not null every time you use it.
But it should be done just once when we read from the database.
Also, why are only string fields nullable, but not the int ones?

@Unengine Unengine Jun 28, 2026

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know SQL tables don't have 'NOT NULL' in them, but they will never be NULL in practice.
If we detect a NULL, we should reject such database early.
If I understand you correctly, this nullability flag forces you to check that the string is not null every time you use it.
But it should be done just once when we read from the database.

SqliteDataReader.GetValue returns object.
If the returned value is casted to ref type, it is potentially nullable so we should declare it nullable.

Also, why are only string fields nullable, but not the int ones?

Cause string is originally ref type and int is value type. It is kinda hard to explain....

Comment thread src/Utils/MD5Util.cs Outdated
{
public static class MD5Util
{
public static string CalculateMD5(object[] dataRaw)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, it would be more clear, if you concatenated all fields on the outside and then used 'CalculateMD5(string input)'.
Performance wise, this method already does 'string.Concat' anyway, so I have doubts it is any faster than 'CalculateMD5(string input)'.

@Unengine
Unengine requested a review from chown2 June 28, 2026 16:42
}
catch (OperationCanceledException)
{
Console.WriteLine("Password update cancelled.");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rollback?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops!

Comment on lines +115 to +121
if (!DBScoreRow.IsScorehashValid(score, targetPlayerPasswordHashMD5, targetScorehashMD5))
{
updateScoreCmd.Parameters["@scorehash"].Value = targetScorehashMD5.Body;
updateScoreCmd.Parameters["@hash"].Value = score.Hash;
updateScoreCmd.ExecuteNonQuery();
Console.WriteLine($"Updating scorehash of a score.\n[Update] Hash : {score.Hash} / Scorehash : {targetScorehashMD5}");
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If an invalid scorehash is found, the database should be rejected
(pseudo-code, not sure if this compiles)

Suggested change
if (!DBScoreRow.IsScorehashValid(score, targetPlayerPasswordHashMD5, targetScorehashMD5))
{
updateScoreCmd.Parameters["@scorehash"].Value = targetScorehashMD5.Body;
updateScoreCmd.Parameters["@hash"].Value = score.Hash;
updateScoreCmd.ExecuteNonQuery();
Console.WriteLine($"Updating scorehash of a score.\n[Update] Hash : {score.Hash} / Scorehash : {targetScorehashMD5}");
}
if (!DBScoreRow.IsScorehashValid(score, targetPlayerPasswordHashMD5, targetScorehashMD5))
throw new Exception("Invalid score found in the database");
updateScoreCmd.Parameters["@scorehash"].Value = targetScorehashMD5.Body;
updateScoreCmd.Parameters["@hash"].Value = score.Hash;
updateScoreCmd.ExecuteNonQuery();
Console.WriteLine($"Updating scorehash of a score.\n[Update] Hash : {score.Hash} / Scorehash : {targetScorehashMD5}");

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anyone can theoretically generate a valid scorehash by reverse-engineering the algorithm, so scorehash's value as a security measure is definitely limited. I agree that it’s not a perfect defense against malicious users.

However, my intent is to ensure that the system remains consistent for all users. By force-updating the scorehash whenever it's invalid, I'm making sure that legitimate player data is always in a stable state and doesn't suffer from 'stale' or inconsistent records. I see this as maintaining data integrity for the game's overall reliability, rather than trying to block sophisticated attackers.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

by reverse-engineering the algorithm

Exactly, and that's something a bit deeper than what a script kiddie could achive. But modifying a score with DB Browser for SQLite is very simple and would take just a few minutes even for someone who's never heard of SQL before.

@chown2 chown2 Jun 28, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doesn't suffer from 'stale' or inconsistent records

In LR2 itself, OpenLR2 included, there is no possiblity of that happening

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In LR2 itself, OpenLR2 included, there is no possiblity of that happening

Yes, I definitely trust it!
But.... actually I don't trust LR2Nexus. Password can be changed with that, and it could be risky. And I want to handle that.

@Unengine Unengine Jun 28, 2026

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In other words, LR2Nexus might be a 'sophisticated attacker' with that.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you don't trust LR2Nexus, how about making a backup of score DB file if the password is being changed?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool! I'm on my way.

@Unengine Unengine Jun 28, 2026

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, I guaranteed the backup. If creating backup failed, I made it immediately throw exception to prevent further operations progressed.
I really appreciate your dedication and time for the LR2 community. Thanks for the help!

@Unengine
Unengine requested a review from chown2 June 28, 2026 17:35
@Unengine
Unengine merged commit 5369b02 into main Jun 28, 2026
1 check passed
@Unengine
Unengine deleted the bugfix/xml-encoding-password-nuke branch June 28, 2026 18:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants