Log every refused character creation with the account and IP

All validation refusals in generatePCFromCommitNewCharacterMsg now go
through refuseCreation, which warns with the reason, account and IP. The
negative stat line used to be the only one that named the account.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
MagicBotandClaude Opus 5.5 committed 2026-10-22 07:11:02 -04:00
1 parent 6a4e7f0e1b
commit 2798d29d0a
1 file changed
+33 -50
+33 -50
View File
@@ -730,6 +730,14 @@ public class PlayerCharacter extends AbstractCharacter {
} }
// The client enforces these rules, so a refusal usually means a modified client.
private static PlayerCharacter refuseCreation(Account account, ClientConnection clientConnection, String reason) {
Logger.warn("Character creation refused: " + reason + " ACCOUNT: " + account.getUname() + "(" + account.getObjectUUID()
+ ") IP ADDRESS: " + clientConnection.getClientIpAddress());
return null;
}
public static PlayerCharacter generatePCFromCommitNewCharacterMsg(Account a, CommitNewCharacterMsg msg, ClientConnection clientConnection) { public static PlayerCharacter generatePCFromCommitNewCharacterMsg(Account a, CommitNewCharacterMsg msg, ClientConnection clientConnection) {
String firstName = msg.getFirstName().trim(); String firstName = msg.getFirstName().trim();
@@ -768,8 +776,7 @@ public class PlayerCharacter extends AbstractCharacter {
Race race = Race.getRace(raceID); Race race = Race.getRace(raceID);
if (race == null) { if (race == null) {
Logger.info("Invalid RaceID: " + raceID); return refuseCreation(a, clientConnection, "Invalid RaceID: " + raceID);
return null;
} }
// Verify BaseClass Object. // Verify BaseClass Object.
@@ -777,8 +784,7 @@ public class PlayerCharacter extends AbstractCharacter {
BaseClass baseClass = DbManager.BaseClassQueries.GET_BASE_CLASS(baseClassID); BaseClass baseClass = DbManager.BaseClassQueries.GET_BASE_CLASS(baseClassID);
if (baseClass == null) { if (baseClass == null) {
Logger.info("Invalid BaseClasID: " + baseClassID); return refuseCreation(a, clientConnection, "Invalid BaseClasID: " + baseClassID);
return null;
} }
// Verify Race/baseClass combo. // Verify Race/baseClass combo.
@@ -793,8 +799,7 @@ public class PlayerCharacter extends AbstractCharacter {
} }
if (!valid) { if (!valid) {
Logger.info("Invalid BaseClass/Race Combo"); return refuseCreation(a, clientConnection, "Invalid BaseClass/Race Combo");
return null;
} }
// Verify HairStyle/BeardStyle/SkinColor/HairColor/BeardColor // Verify HairStyle/BeardStyle/SkinColor/HairColor/BeardColor
@@ -805,23 +810,19 @@ public class PlayerCharacter extends AbstractCharacter {
int beardColorID = msg.getBeardColor(); int beardColorID = msg.getBeardColor();
if (!race.isValidHairStyle(hairStyleID)) { if (!race.isValidHairStyle(hairStyleID)) {
Logger.info("Invalid HairStyleID: " + hairStyleID + " for race: " + race.getName()); return refuseCreation(a, clientConnection, "Invalid HairStyleID: " + hairStyleID + " for race: " + race.getName());
return null;
} }
if (!race.isValidSkinColor(skinColorID)) { if (!race.isValidSkinColor(skinColorID)) {
Logger.info("Invalid skinColorID: " + skinColorID + " for race: " + race.getName()); return refuseCreation(a, clientConnection, "Invalid skinColorID: " + skinColorID + " for race: " + race.getName());
return null;
} }
if (!race.isValidHairColor(hairColorID)) { if (!race.isValidHairColor(hairColorID)) {
Logger.info("Invalid hairColorID: " + hairColorID + " for race: " + race.getName()); return refuseCreation(a, clientConnection, "Invalid hairColorID: " + hairColorID + " for race: " + race.getName());
return null;
} }
if (!race.isValidBeardColor(beardColorID)) { if (!race.isValidBeardColor(beardColorID)) {
Logger.info("Invalid beardColorID: " + beardColorID + " for race: " + race.getName()); return refuseCreation(a, clientConnection, "Invalid beardColorID: " + beardColorID + " for race: " + race.getName());
return null;
} }
// Get stat modifiers // Get stat modifiers
@@ -833,8 +834,7 @@ public class PlayerCharacter extends AbstractCharacter {
if (intMod < -5 || dexMod < -5 || conMod < -5 || strMod < -5 || spiMod < -5) { if (intMod < -5 || dexMod < -5 || conMod < -5 || strMod < -5 || spiMod < -5) {
Logger.error("NEGATIVE STAT CHEAT ATTEMPTED! ACCOUNT: " + a.getUname() + "(" + a.getObjectUUID() + ") IP ADDRESS: " + clientConnection.getClientIpAddress()); return refuseCreation(a, clientConnection, "Negative stat mod");
return null;
} }
// calculate current stats: // calculate current stats:
@@ -855,8 +855,7 @@ public class PlayerCharacter extends AbstractCharacter {
int numRunes = msg.getNumRunes(); int numRunes = msg.getNumRunes();
if (numRunes > 10) { if (numRunes > 10) {
Logger.info("Too many Runes applied"); return refuseCreation(a, clientConnection, "Too many Runes applied");
return null;
} }
// Get Runes // Get Runes
@@ -907,39 +906,33 @@ public class PlayerCharacter extends AbstractCharacter {
// Null check // Null check
if (runeBase == null) { if (runeBase == null) {
Logger.info("GOM returned NULL RuneBase"); return refuseCreation(a, clientConnection, "GOM returned NULL RuneBase");
return null;
} }
// Validate Rune against Race // Validate Rune against Race
if (!race.isAllowedRune(runeBase)) { if (!race.isAllowedRune(runeBase)) {
Logger.info("Trait Not valid for Race"); return refuseCreation(a, clientConnection, "Trait Not valid for Race");
return null;
} }
// Validate BaseClass against Race // Validate BaseClass against Race
if (!baseClass.isAllowedRune(runeBase)) { if (!baseClass.isAllowedRune(runeBase)) {
Logger.info("Trait Not valid for BaseClass"); return refuseCreation(a, clientConnection, "Trait Not valid for BaseClass");
return null;
} }
if (!usedRuneIDs.add(runeBase.getObjectUUID())) { if (!usedRuneIDs.add(runeBase.getObjectUUID())) {
Logger.info("Duplicate RuneBase"); return refuseCreation(a, clientConnection, "Duplicate RuneBase");
return null;
} }
// Creation talents are rank 0, so two of the same sub type block each other (client 0x4695D0) // Creation talents are rank 0, so two of the same sub type block each other (client 0x4695D0)
if (runeBase.getSubTypeToken() != 0 && !usedSubTypeTokens.add(runeBase.getSubTypeToken())) { if (runeBase.getSubTypeToken() != 0 && !usedSubTypeTokens.add(runeBase.getSubTypeToken())) {
Logger.info("Duplicate RuneBase Subtype"); return refuseCreation(a, clientConnection, "Duplicate RuneBase Subtype");
return null;
} }
usedRunes.add(runeBase); usedRunes.add(runeBase);
// Talent cap of 8 (client 0x4695D0) // Talent cap of 8 (client 0x4695D0)
if (usedRunes.size() > 8) { if (usedRunes.size() > 8) {
Logger.info("Too many talents applied"); return refuseCreation(a, clientConnection, "Too many talents applied");
return null;
} }
int maxValue = 0; int maxValue = 0;
@@ -984,43 +977,37 @@ public class PlayerCharacter extends AbstractCharacter {
remainingPoints -= value; remainingPoints -= value;
continue; continue;
} }
Logger.info("Not enough points left"); return refuseCreation(a, clientConnection, "Not enough points left");
return null;
case MBServerStatics.RUNE_STR_MIN_NEEDED_ATTRIBUTE_ID: case MBServerStatics.RUNE_STR_MIN_NEEDED_ATTRIBUTE_ID:
if (strCur >= value) if (strCur >= value)
continue; continue;
Logger.info("STR fails to meet Rune Minimum --> " + rb.getName()); return refuseCreation(a, clientConnection, "STR fails to meet Rune Minimum --> " + rb.getName());
return null;
case MBServerStatics.RUNE_DEX_MIN_NEEDED_ATTRIBUTE_ID: case MBServerStatics.RUNE_DEX_MIN_NEEDED_ATTRIBUTE_ID:
if (dexCur >= value) if (dexCur >= value)
continue; continue;
Logger.info("DEX fails to meet Rune Minimum --> " + rb.getName()); return refuseCreation(a, clientConnection, "DEX fails to meet Rune Minimum --> " + rb.getName());
return null;
case MBServerStatics.RUNE_CON_MIN_NEEDED_ATTRIBUTE_ID: case MBServerStatics.RUNE_CON_MIN_NEEDED_ATTRIBUTE_ID:
if (conCur >= value) if (conCur >= value)
continue; continue;
Logger.info("CON fails to meet Rune Minimum --> " + rb.getName()); return refuseCreation(a, clientConnection, "CON fails to meet Rune Minimum --> " + rb.getName());
return null;
case MBServerStatics.RUNE_INT_MIN_NEEDED_ATTRIBUTE_ID: case MBServerStatics.RUNE_INT_MIN_NEEDED_ATTRIBUTE_ID:
if (intCur >= value) if (intCur >= value)
continue; continue;
Logger.info("INT fails to meet Rune Minimum --> " + rb.getName()); return refuseCreation(a, clientConnection, "INT fails to meet Rune Minimum --> " + rb.getName());
return null;
case MBServerStatics.RUNE_SPI_MIN_NEEDED_ATTRIBUTE_ID: case MBServerStatics.RUNE_SPI_MIN_NEEDED_ATTRIBUTE_ID:
if (spiCur >= value) if (spiCur >= value)
continue; continue;
Logger.info("SPI fails to meet Rune Minimum --> " + rb.getName()); return refuseCreation(a, clientConnection, "SPI fails to meet Rune Minimum --> " + rb.getName());
return null;
case MBServerStatics.RUNE_STR_ATTRIBUTE_ID: case MBServerStatics.RUNE_STR_ATTRIBUTE_ID:
if (value < 0) if (value < 0)
@@ -1085,21 +1072,18 @@ public class PlayerCharacter extends AbstractCharacter {
continue; continue;
default: default:
Logger.info("Unknown ATTRIBUTE_ID while checking RuneBaseAttributes: " + attrID); return refuseCreation(a, clientConnection, "Unknown ATTRIBUTE_ID while checking RuneBaseAttributes: " + attrID);
return null;
} }
} }
} }
// Checked before the penalties, as the client's +/- does (0xBAC140): Giant's Blood cuts a max below its stat // Checked before the penalties, as the client's +/- does (0xBAC140): Giant's Blood cuts a max below its stat
if (strCur < 1 || dexCur < 1 || conCur < 1 || intCur < 1 || spiCur < 1) { if (strCur < 1 || dexCur < 1 || conCur < 1 || intCur < 1 || spiCur < 1) {
Logger.info("Attribute below 1"); return refuseCreation(a, clientConnection, "Attribute below 1");
return null;
} }
if (strCur > strMax || dexCur > dexMax || conCur > conMax || intCur > intMax || spiCur > spiMax) { if (strCur > strMax || dexCur > dexMax || conCur > conMax || intCur > intMax || spiCur > spiMax) {
Logger.info("Attribute above racial maximum"); return refuseCreation(a, clientConnection, "Attribute above racial maximum");
return null;
} }
// Add in all of the penalties. // Add in all of the penalties.
@@ -1130,8 +1114,7 @@ public class PlayerCharacter extends AbstractCharacter {
} }
if (kit == null) { if (kit == null) {
Logger.info("Unable to find matching kitID: " + kitID); return refuseCreation(a, clientConnection, "Unable to find matching kitID: " + kitID);
return null;
} }
byte runningTrains = 0; byte runningTrains = 0;