mirror of
https://github.com/Melledy/Nebula.git
synced 2025-12-12 12:24:35 +01:00
Implement ban functionality
- Added ban module - Added Ban and Unban commands, supporting banning and unbanning players through various command parameters. - Added timestamp formatting method in Utils utility class for displaying ban expiration times - Introduced PlayerErrorCode enum defining various error codes including ErrBan - Added dual ban checking for both IP and user during player login - Optimized login failure responses to provide specific error reasons and parameters
This commit is contained in:
115
src/main/java/emu/nebula/command/commands/BanCommand.java
Normal file
115
src/main/java/emu/nebula/command/commands/BanCommand.java
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
package emu.nebula.command.commands;
|
||||||
|
|
||||||
|
import emu.nebula.Nebula;
|
||||||
|
import emu.nebula.command.Command;
|
||||||
|
import emu.nebula.command.CommandArgs;
|
||||||
|
import emu.nebula.command.CommandHandler;
|
||||||
|
import emu.nebula.game.player.Player;
|
||||||
|
import emu.nebula.util.Utils;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
@Command(label = "ban",
|
||||||
|
permission = "admin.ban",
|
||||||
|
desc = """
|
||||||
|
!ban {all | ip | uid} [player uid | ip] (end timestamp) (reason) - Ban a player\
|
||||||
|
|
||||||
|
- all mode bans both the player object and their IP address by UID, so the next parameter should be UID instead of IP
|
||||||
|
- ip mode can only ban IP addresses, so the next parameter should be an IP
|
||||||
|
- uid mode can only ban UIDs, so the next parameter should be a UID
|
||||||
|
- If you don't fill in the end timestamp, it will be permanently banned by default\
|
||||||
|
""")
|
||||||
|
public class BanCommand implements CommandHandler {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String execute(CommandArgs args) {
|
||||||
|
if (args.size() < 2) {
|
||||||
|
return "Invalid amount of args";
|
||||||
|
}
|
||||||
|
|
||||||
|
int bannedUid = 0;
|
||||||
|
long banEndTime = 0;
|
||||||
|
String bannedIp = null;
|
||||||
|
String reason = null;
|
||||||
|
|
||||||
|
String mode = args.get(0).toLowerCase(Locale.ROOT);
|
||||||
|
|
||||||
|
switch (args.size()) {
|
||||||
|
case 4:
|
||||||
|
reason = args.get(3);
|
||||||
|
case 3: {
|
||||||
|
try {
|
||||||
|
banEndTime = Long.parseLong(args.get(2));
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
return "Unable to parse timestamp.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 2: {
|
||||||
|
if (mode.equals("ip")) {
|
||||||
|
bannedIp = args.get(1);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
bannedUid = Integer.parseInt(args.get(1));
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
return "Unable to parse uid.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 1: {
|
||||||
|
if (!mode.equals("all") && !mode.equals("uid") && !mode.equals("ip"))
|
||||||
|
return "Unable to parse mode.";
|
||||||
|
}
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (banEndTime != 0 && banEndTime < System.currentTimeMillis()) {
|
||||||
|
return "Failed, the end timestamp must be greater than the current time";
|
||||||
|
}
|
||||||
|
|
||||||
|
var banModule = Nebula.getGameContext().getBanModule();
|
||||||
|
|
||||||
|
Player player;
|
||||||
|
if (!mode.equals("ip")) {
|
||||||
|
player = Nebula.getGameContext().getPlayerModule().getPlayer(bannedUid);
|
||||||
|
|
||||||
|
if (player == null) {
|
||||||
|
return "Failed, player not found.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (mode) {
|
||||||
|
case "all" -> {
|
||||||
|
banModule.banPlayer(
|
||||||
|
bannedUid,
|
||||||
|
banEndTime,
|
||||||
|
reason,
|
||||||
|
true,
|
||||||
|
args.getSender() != null ? String.valueOf(args.getSender().getUid()) : "Console");
|
||||||
|
return "Banned player all mode " + bannedUid + " until " + Utils.formatTimestamp(banEndTime) +
|
||||||
|
(reason != null ? " (" + reason + ")" : "");
|
||||||
|
}
|
||||||
|
case "ip" -> {
|
||||||
|
banModule.banIp(
|
||||||
|
bannedIp,
|
||||||
|
banEndTime,
|
||||||
|
reason,
|
||||||
|
args.getSender() != null ? String.valueOf(args.getSender().getUid()) : "Console");
|
||||||
|
return "Banned ip " + bannedIp + " until " + Utils.formatTimestamp(banEndTime) +
|
||||||
|
(reason != null ? " (" + reason + ")" : "");
|
||||||
|
}
|
||||||
|
case "uid" -> {
|
||||||
|
banModule.banPlayer(
|
||||||
|
bannedUid,
|
||||||
|
banEndTime,
|
||||||
|
reason,
|
||||||
|
false,
|
||||||
|
args.getSender() != null ? String.valueOf(args.getSender().getUid()) : "Console");
|
||||||
|
return "Banned player " + bannedUid + " until " + Utils.formatTimestamp(banEndTime) +
|
||||||
|
(reason != null ? " (" + reason + ")" : "");
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
return "Ban sub command not found";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
81
src/main/java/emu/nebula/command/commands/UnbanCommand.java
Normal file
81
src/main/java/emu/nebula/command/commands/UnbanCommand.java
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
package emu.nebula.command.commands;
|
||||||
|
|
||||||
|
import emu.nebula.Nebula;
|
||||||
|
import emu.nebula.command.Command;
|
||||||
|
import emu.nebula.command.CommandArgs;
|
||||||
|
import emu.nebula.command.CommandHandler;
|
||||||
|
import emu.nebula.game.ban.BanInfo;
|
||||||
|
import emu.nebula.game.player.Player;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
@Command(label = "unban", permission = "admin.ban", desc = """
|
||||||
|
!unban {all | ip | uid} [player uid | ip]\
|
||||||
|
|
||||||
|
- all mode unbans both the player object and their IP address by UID, so the next parameter should be UID instead of IP
|
||||||
|
- ip mode can only unban IP addresses, so the next parameter should be an IP
|
||||||
|
- uid mode can only unban UIDs, so the next parameter should be a UID\
|
||||||
|
""")
|
||||||
|
public class UnbanCommand implements CommandHandler {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String execute(CommandArgs args) {
|
||||||
|
if (args.size() < 2) {
|
||||||
|
return "Invalid amount of args";
|
||||||
|
}
|
||||||
|
|
||||||
|
int unbannedUid = 0;
|
||||||
|
String unbannedIp = null;
|
||||||
|
|
||||||
|
String mode = args.get(0).toLowerCase(Locale.ROOT);
|
||||||
|
|
||||||
|
if (!mode.equals("all") && !mode.equals("uid") && !mode.equals("ip"))
|
||||||
|
return "Unable to parse mode.";
|
||||||
|
|
||||||
|
if (mode.equals("ip")) {
|
||||||
|
unbannedIp = args.get(1);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
unbannedUid = Integer.parseInt(args.get(1));
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
return "Unable to parse uid.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var banModule = Nebula.getGameContext().getBanModule();
|
||||||
|
|
||||||
|
Player player = null;
|
||||||
|
if (!mode.equals("ip")) {
|
||||||
|
player = Nebula.getGameContext().getPlayerModule().getPlayer(unbannedUid);
|
||||||
|
|
||||||
|
if (player == null) {
|
||||||
|
return "Failed, player not found.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (mode) {
|
||||||
|
case "all" -> {
|
||||||
|
BanInfo banInfo = banModule.getPlayerBanInfo(player.getUid());
|
||||||
|
|
||||||
|
banModule.unbanPlayer(player.getUid());
|
||||||
|
|
||||||
|
if (banInfo != null) {
|
||||||
|
unbannedIp = banInfo.getIpAddress();
|
||||||
|
if (unbannedIp != null)
|
||||||
|
banModule.unbanIp(unbannedIp);
|
||||||
|
}
|
||||||
|
return "Unban a player all mode " + unbannedUid;
|
||||||
|
} case "uid" -> {
|
||||||
|
banModule.unbanPlayer(player.getUid());
|
||||||
|
return "Unban a player " + unbannedUid;
|
||||||
|
} case "ip" -> {
|
||||||
|
banModule.unbanIp(unbannedIp);
|
||||||
|
return "Unban a ip " + unbannedIp;
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
// Fallback
|
||||||
|
return "Unban sub command not found";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ import java.util.concurrent.TimeUnit;
|
|||||||
import emu.nebula.GameConstants;
|
import emu.nebula.GameConstants;
|
||||||
import emu.nebula.Nebula;
|
import emu.nebula.Nebula;
|
||||||
import emu.nebula.game.activity.ActivityModule;
|
import emu.nebula.game.activity.ActivityModule;
|
||||||
|
import emu.nebula.game.ban.BanModule;
|
||||||
import emu.nebula.game.gacha.GachaModule;
|
import emu.nebula.game.gacha.GachaModule;
|
||||||
import emu.nebula.game.player.PlayerModule;
|
import emu.nebula.game.player.PlayerModule;
|
||||||
import emu.nebula.game.scoreboss.ScoreBossModule;
|
import emu.nebula.game.scoreboss.ScoreBossModule;
|
||||||
@@ -30,6 +31,7 @@ public class GameContext implements Runnable {
|
|||||||
private final TutorialModule tutorialModule;
|
private final TutorialModule tutorialModule;
|
||||||
private final ActivityModule activityModule;
|
private final ActivityModule activityModule;
|
||||||
private final ScoreBossModule scoreBossModule;
|
private final ScoreBossModule scoreBossModule;
|
||||||
|
private final BanModule banModule;
|
||||||
|
|
||||||
// Game loop
|
// Game loop
|
||||||
private final ScheduledExecutorService scheduler;
|
private final ScheduledExecutorService scheduler;
|
||||||
@@ -48,6 +50,7 @@ public class GameContext implements Runnable {
|
|||||||
this.tutorialModule = new TutorialModule(this);
|
this.tutorialModule = new TutorialModule(this);
|
||||||
this.activityModule = new ActivityModule(this);
|
this.activityModule = new ActivityModule(this);
|
||||||
this.scoreBossModule = new ScoreBossModule(this);
|
this.scoreBossModule = new ScoreBossModule(this);
|
||||||
|
this.banModule = new BanModule(this);
|
||||||
|
|
||||||
// Run game loop
|
// Run game loop
|
||||||
this.scheduler = Executors.newScheduledThreadPool(1);
|
this.scheduler = Executors.newScheduledThreadPool(1);
|
||||||
|
|||||||
65
src/main/java/emu/nebula/game/ban/BanInfo.java
Normal file
65
src/main/java/emu/nebula/game/ban/BanInfo.java
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package emu.nebula.game.ban;
|
||||||
|
|
||||||
|
import dev.morphia.annotations.Entity;
|
||||||
|
import dev.morphia.annotations.Id;
|
||||||
|
import emu.nebula.Nebula;
|
||||||
|
import emu.nebula.database.GameDatabaseObject;
|
||||||
|
import emu.nebula.game.player.PlayerErrorCode;
|
||||||
|
import emu.nebula.proto.Public.Error;
|
||||||
|
import emu.nebula.util.Utils;
|
||||||
|
import lombok.Getter;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Entity(value = "bans", useDiscriminator = false)
|
||||||
|
public class BanInfo implements GameDatabaseObject {
|
||||||
|
@Id
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
private int playerUid;
|
||||||
|
private long startTime;
|
||||||
|
private long endTime;
|
||||||
|
private String reason;
|
||||||
|
private String bannedBy;
|
||||||
|
private String ipAddress;
|
||||||
|
|
||||||
|
@Deprecated // Morphia only
|
||||||
|
public BanInfo() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public BanInfo(int playerUid, long endTime, String reason, String bannedBy, String ipAddress) {
|
||||||
|
this.playerUid = playerUid;
|
||||||
|
this.startTime = System.currentTimeMillis();
|
||||||
|
this.endTime = endTime;
|
||||||
|
this.reason = reason;
|
||||||
|
this.bannedBy = bannedBy;
|
||||||
|
this.ipAddress = ipAddress;
|
||||||
|
// Generate ID based on either player UID or IP address
|
||||||
|
this.id = (ipAddress != null && !ipAddress.isEmpty() && playerUid == 0) ? "ip_" + ipAddress :
|
||||||
|
"player_" + playerUid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isExpired() {
|
||||||
|
return endTime != 0 && endTime < System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getExpirationDateString() {
|
||||||
|
if (endTime == 0) {
|
||||||
|
return "Never";
|
||||||
|
}
|
||||||
|
return Utils.formatTimestamp(this.endTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void save() {
|
||||||
|
GameDatabaseObject.super.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Error toProto() {
|
||||||
|
return Error.newInstance()
|
||||||
|
.setCode(PlayerErrorCode.ErrBan.getValue())
|
||||||
|
.addArguments(
|
||||||
|
getExpirationDateString() + "\n" +
|
||||||
|
(this.reason != null ? "\n (" + this.reason + ")" : "\n" + this.id));
|
||||||
|
}
|
||||||
|
}
|
||||||
242
src/main/java/emu/nebula/game/ban/BanModule.java
Normal file
242
src/main/java/emu/nebula/game/ban/BanModule.java
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
package emu.nebula.game.ban;
|
||||||
|
|
||||||
|
import emu.nebula.Nebula;
|
||||||
|
import emu.nebula.game.player.Player;
|
||||||
|
import emu.nebula.game.GameContext;
|
||||||
|
import emu.nebula.game.GameContextModule;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
public class BanModule extends GameContextModule {
|
||||||
|
private final Map<String, BanInfo> cachedIpBans = new ConcurrentHashMap<>();
|
||||||
|
private final Map<Integer, BanInfo> cachedPlayerBans = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public BanModule(GameContext context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ban a player by UID
|
||||||
|
* @param uid Player UID
|
||||||
|
* @param endTime Ban expiration time (0 = permanent)
|
||||||
|
* @param isBanIp Ban Ip
|
||||||
|
* @param reason Ban reason
|
||||||
|
* @param bannedBy Who banned the player
|
||||||
|
*/
|
||||||
|
public void banPlayer(int uid, long endTime, String reason, boolean isBanIp, String bannedBy) {
|
||||||
|
Player player = getGameContext().getPlayerModule().getPlayer(uid);
|
||||||
|
|
||||||
|
// It is only used as a supplement to find the banned IP when unblocking a player
|
||||||
|
String playerBindIp = null;
|
||||||
|
|
||||||
|
if (isBanIp) {
|
||||||
|
if (player != null && player.getSession() != null) {
|
||||||
|
String ipAddress = player.getSession().getIpAddress();
|
||||||
|
if (ipAddress != null && !ipAddress.isEmpty()) {
|
||||||
|
playerBindIp = ipAddress;
|
||||||
|
banIp(ipAddress, endTime, reason, bannedBy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BanInfo banInfo = new BanInfo(uid, endTime, reason, bannedBy, playerBindIp);
|
||||||
|
cachedPlayerBans.put(uid, banInfo);
|
||||||
|
banInfo.save();
|
||||||
|
|
||||||
|
// Kick player
|
||||||
|
if (player != null && player.isLoaded()) {
|
||||||
|
player.setSession(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ban an IP address
|
||||||
|
* <p>
|
||||||
|
* Please be cautious about disabling IPs
|
||||||
|
* in some regions where IPs are scarce, many people may share a public IP
|
||||||
|
* and restarting the optical cat device will reassign a new IP
|
||||||
|
*
|
||||||
|
* @param ipAddress IP address to ban
|
||||||
|
* @param endTime Ban expiration time (0 = permanent)
|
||||||
|
* @param reason Ban reason
|
||||||
|
* @param bannedBy Who banned the IP
|
||||||
|
*/
|
||||||
|
public void banIp(String ipAddress, long endTime, String reason, String bannedBy) {
|
||||||
|
BanInfo banInfo = new BanInfo(0, endTime, reason, bannedBy, ipAddress);
|
||||||
|
cachedIpBans.put(ipAddress, banInfo);
|
||||||
|
banInfo.save();
|
||||||
|
|
||||||
|
List<Player> playerList = Nebula.getGameContext().getPlayerModule().getCachedPlayers()
|
||||||
|
.values().stream().toList();
|
||||||
|
|
||||||
|
String playerIpAddress;
|
||||||
|
for (Player player : playerList) {
|
||||||
|
playerIpAddress = player.getSession().getIpAddress();
|
||||||
|
if (playerIpAddress == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kick player
|
||||||
|
if (playerIpAddress.equals(ipAddress)) {
|
||||||
|
player.setSession(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unban a player
|
||||||
|
* @param uid Player UID
|
||||||
|
*/
|
||||||
|
public void unbanPlayer(int uid) {
|
||||||
|
BanInfo banInfo = cachedPlayerBans.remove(uid);
|
||||||
|
if (banInfo == null) {
|
||||||
|
banInfo = getPlayerBanInfo(uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (banInfo == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteBan(banInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unban an IP address
|
||||||
|
* @param ipAddress IP address to unban
|
||||||
|
*/
|
||||||
|
public void unbanIp(String ipAddress) {
|
||||||
|
BanInfo banInfo = cachedIpBans.remove(ipAddress);
|
||||||
|
if (banInfo == null) {
|
||||||
|
banInfo = getIpBanInfo(ipAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (banInfo == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteBan(banInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a player is banned
|
||||||
|
* @param uid Player UID
|
||||||
|
* @return True if banned, false otherwise
|
||||||
|
*/
|
||||||
|
public boolean isPlayerBanned(int uid) {
|
||||||
|
BanInfo banInfo = cachedPlayerBans.get(uid);
|
||||||
|
if (banInfo == null) {
|
||||||
|
banInfo = loadPlayerBanFromDatabase(uid);
|
||||||
|
if (banInfo == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
cachedPlayerBans.put(uid, banInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if ban has expired
|
||||||
|
if (banInfo.isExpired()) {
|
||||||
|
unbanPlayer(uid);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if an IP address is banned
|
||||||
|
* @param ipAddress IP address
|
||||||
|
* @return True if banned, false otherwise
|
||||||
|
*/
|
||||||
|
public boolean isIpBanned(String ipAddress) {
|
||||||
|
BanInfo banInfo = cachedIpBans.get(ipAddress);
|
||||||
|
if (banInfo == null) {
|
||||||
|
banInfo = loadIpBanFromDatabase(ipAddress);
|
||||||
|
if (banInfo == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
cachedIpBans.put(ipAddress, banInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if ban has expired
|
||||||
|
if (banInfo.isExpired()) {
|
||||||
|
unbanIp(ipAddress);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get ban info for a player
|
||||||
|
* @param uid Player UID
|
||||||
|
* @return BanInfo or null if not banned
|
||||||
|
*/
|
||||||
|
public BanInfo getPlayerBanInfo(int uid) {
|
||||||
|
BanInfo banInfo = cachedPlayerBans.get(uid);
|
||||||
|
if (banInfo == null) {
|
||||||
|
banInfo = loadPlayerBanFromDatabase(uid);
|
||||||
|
if (banInfo != null) {
|
||||||
|
cachedPlayerBans.put(uid, banInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return banInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get ban info for an IP
|
||||||
|
* @param ipAddress IP address
|
||||||
|
* @return BanInfo or null if not banned
|
||||||
|
*/
|
||||||
|
public BanInfo getIpBanInfo(String ipAddress) {
|
||||||
|
BanInfo banInfo = cachedIpBans.get(ipAddress);
|
||||||
|
if (banInfo == null) {
|
||||||
|
banInfo = loadIpBanFromDatabase(ipAddress);
|
||||||
|
if (banInfo != null) {
|
||||||
|
cachedIpBans.put(ipAddress, banInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return banInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete ban from database
|
||||||
|
*/
|
||||||
|
private void deleteBan(BanInfo banInfo) {
|
||||||
|
try {
|
||||||
|
Nebula.getGameDatabase().delete(banInfo);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Nebula.getLogger().error("Failed to delete ban info from database", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load player ban from database
|
||||||
|
*/
|
||||||
|
private BanInfo loadPlayerBanFromDatabase(int uid) {
|
||||||
|
if (uid == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return Nebula.getGameDatabase().getObjectByField(BanInfo.class, "playerUid", uid);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Nebula.getLogger().error("Failed to load player ban from database", e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load IP ban from database
|
||||||
|
*/
|
||||||
|
private BanInfo loadIpBanFromDatabase(String ipAddress) {
|
||||||
|
if (ipAddress == null || ipAddress.isEmpty())
|
||||||
|
return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return Nebula.getGameDatabase().getObjectByField(BanInfo.class, "ipAddress", ipAddress);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Nebula.getLogger().error("Failed to load IP ban from database", e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
192
src/main/java/emu/nebula/game/player/PlayerErrorCode.java
Normal file
192
src/main/java/emu/nebula/game/player/PlayerErrorCode.java
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
package emu.nebula.game.player;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
// resources\bin\ErrorCode.json
|
||||||
|
// resources\language\en_US\ErrorCode.json
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public enum PlayerErrorCode {
|
||||||
|
ErrInvalidToken(100101),
|
||||||
|
ErrInvalidTimestamp(100102),
|
||||||
|
ErrInvalidMsgId(100103),
|
||||||
|
ErrInvalidMsgBody(100104),
|
||||||
|
ErrAgentBusy(100105),
|
||||||
|
ErrServiceUnavailable(100106),
|
||||||
|
ErrRequestTimeout(100107),
|
||||||
|
ErrLoginFailed(100108),
|
||||||
|
ErrInvalidVersion(100109),
|
||||||
|
ErrLogin(100110),
|
||||||
|
ErrMask(110101),
|
||||||
|
ErrServerBusy(110102),
|
||||||
|
ErrData(110103),
|
||||||
|
ErrArgs(110104),
|
||||||
|
ErrInternal(110105),
|
||||||
|
ErrTokenExpire(110106),
|
||||||
|
ErrRelogin(110107),
|
||||||
|
ErrInvalidReq(110108),
|
||||||
|
ErrApiDisabled(110109),
|
||||||
|
ErrResUpdated(110110),
|
||||||
|
ErrOperateTooFast(110111),
|
||||||
|
ErrNickLenViolation(110201),
|
||||||
|
ErrNickIllegal(110202),
|
||||||
|
ErrNickForbidden(110203),
|
||||||
|
ErrBan(110205),
|
||||||
|
ErrSignatureForbidden(110206),
|
||||||
|
ErrSignatureLenViolation(110207),
|
||||||
|
ErrSignatureIllegal(110208),
|
||||||
|
ErrTitleNotFound(110209),
|
||||||
|
ErrSurveyNotFound(110210),
|
||||||
|
ErrSurveyNotOpenYet(110211),
|
||||||
|
ErrSurveyHasCompleted(110212),
|
||||||
|
ErrMailMiss(110401),
|
||||||
|
ErrMailRevoked(110402),
|
||||||
|
ErrMailUpdate(110403),
|
||||||
|
ErrMailPinned(110404),
|
||||||
|
ErrQuestNotComplete(110501),
|
||||||
|
ErrQuestClosed(110502),
|
||||||
|
ErrProductNotReady(110601),
|
||||||
|
ErrProductRestockLimit(110602),
|
||||||
|
ErrGoodsNotFound(110603),
|
||||||
|
ErrGoodsLockPurchase(110604),
|
||||||
|
ErrGoodsDelisted(110605),
|
||||||
|
ErrShopNotFound(110606),
|
||||||
|
ErrShopAlreadyClosed(110607),
|
||||||
|
ErrRecvReward(110701),
|
||||||
|
ErrMainlineCondPremission(110702),
|
||||||
|
ErrEnergyHoldLimit(110801),
|
||||||
|
ErrEnergyBuyLimit(110802),
|
||||||
|
ErrEnergyLack(110803),
|
||||||
|
ErrEnergyRestore(110804),
|
||||||
|
ErrGachaSpinLimit(110901),
|
||||||
|
ErrItemUseRefused(111001),
|
||||||
|
ErrRglInProgress(111101),
|
||||||
|
ErrRglCond(111102),
|
||||||
|
ErrRglBuildNotFound(111103),
|
||||||
|
ErrRglBuildLock(111104),
|
||||||
|
ErrFormationIdNeed(111105),
|
||||||
|
ErrFormationFullNeed(111106),
|
||||||
|
ErrBuildNameMinLenLimit(111107),
|
||||||
|
ErrBuildNameMaxLenLimit(111108),
|
||||||
|
ErrBuildNameViolation(111109),
|
||||||
|
ErrOutfitStrengthenMax(111201),
|
||||||
|
ErrOutfitNoData(111202),
|
||||||
|
ErrOutfitBeEquipped(111203),
|
||||||
|
ErrOutfitBeLocked(111204),
|
||||||
|
ErrOutfitPhaseMax(111205),
|
||||||
|
ErrOutfitStarMax(111206),
|
||||||
|
ErrOutfitLevelNotReached(111207),
|
||||||
|
ErrGiftPackageInvalid(111301),
|
||||||
|
ErrGiftPackageExpired(111302),
|
||||||
|
ErrGiftPackError(111303),
|
||||||
|
ErrRedeemCodeReachedLimit(111304),
|
||||||
|
ErrUserExchangeCountReachedLimit(111305),
|
||||||
|
ErrGiftPackageAlreadyExists(111306),
|
||||||
|
ErrGiftPackageInfoInvalid(111307),
|
||||||
|
ErrRedeemCodeItemListEmpty(111308),
|
||||||
|
ErrRedeemCodeCodeItemInvalid(111309),
|
||||||
|
ErrGiftPackageStartTimeError(111310),
|
||||||
|
ErrGiftPackageEndTimeError(111311),
|
||||||
|
ErrGiftPackageRedeemCodeQuantityExceedsLimit(111312),
|
||||||
|
ErrFixedRedeemCodeAlreadyExists(111313),
|
||||||
|
ErrFixedRedeemCodeCannotBeEmpty(111314),
|
||||||
|
ErrRedeemCodeQuantityCannotBeZero(111315),
|
||||||
|
ErrFixedRedeemCodeUserUsageQuantityRequired(111316),
|
||||||
|
ErrRedeemCodeInvalid(111317),
|
||||||
|
ErrRedeemCodeFailed(111318),
|
||||||
|
ErrRedeemCodeAlreadyUsed(111319),
|
||||||
|
ErrRedeemFailed(111320),
|
||||||
|
ErrInviteNotFound(111401),
|
||||||
|
ErrUserInviteCountLimit(111402),
|
||||||
|
ErrUserFriendCountLimit(111403),
|
||||||
|
ErrAlreadyExistFriend(111404),
|
||||||
|
ErrFriendEnergyReceiveMax(110681),
|
||||||
|
ErrMallPackageNotListed(111501),
|
||||||
|
ErrMallPackageDelisted(111502),
|
||||||
|
ErrMallPackageStockLimit(111503),
|
||||||
|
ErrMallPackageOrderLimit(111504),
|
||||||
|
ErrOrderCantRemove(111505),
|
||||||
|
ErrHeartStoneMax(111601),
|
||||||
|
ErrWeaponMaxAdvance(111602),
|
||||||
|
ErrWeaponCondNoMet(111603),
|
||||||
|
ErrCharMaxLevel(111604),
|
||||||
|
ErrCharCondMaxAdvance(111605),
|
||||||
|
ErrCharMaxAdvance(111606),
|
||||||
|
ErrCharSkillMaxLevel(111607),
|
||||||
|
ErrCharSkillCondNotMet(111608),
|
||||||
|
ErrPresentsNoData(111609),
|
||||||
|
ErrPresentsUpgradeMax(111610),
|
||||||
|
ErrPresentsBeEquipped(111611),
|
||||||
|
ErrPresentsBeLocked(111612),
|
||||||
|
ErrNotAllowedRecruit(111613),
|
||||||
|
ErrAffinityMaxLevel(111614),
|
||||||
|
ErrAffinityGiftSendCountLimit(111615),
|
||||||
|
ErrDatingCountLimit(111616),
|
||||||
|
ErrDatingSendCountLimit(111617),
|
||||||
|
ErrCharWorldClassCondNotMet(111618),
|
||||||
|
ErrDatingCharLimit(111619),
|
||||||
|
ErrCharArchiveRewardReceived(111620),
|
||||||
|
ErrReceiveEnergyFriendNotData(111621),
|
||||||
|
ErrBattlePassVersionMismatch(111701),
|
||||||
|
ErrTravelerDuelSeasonEnd(111801),
|
||||||
|
ErrProductionFormulaUnactivated(111901),
|
||||||
|
ErrTalentAlreayActiveated(112001),
|
||||||
|
ErrTalentGroupNotYetOpen(112002),
|
||||||
|
ErrTalentKeyNodesInsufficientQuantity(112003),
|
||||||
|
ErrTalentOrdinaryNodesInsufficientQuantity(112004),
|
||||||
|
ErrTalentListEmpty(112005),
|
||||||
|
ErrTalentResetFrequently(112006),
|
||||||
|
ErrTalentActivationLimit(112007),
|
||||||
|
ErrTalentNotYetActivated(112008),
|
||||||
|
ErrEquipmentNoData(112101),
|
||||||
|
ErrEquipmentUpgradeMax(112102),
|
||||||
|
ErrEquipmentBeEquipped(112103),
|
||||||
|
ErrEquipmentBeLocked(112104),
|
||||||
|
ErrDiscNoData(112201),
|
||||||
|
ErrDiscLevelNotReached(112202),
|
||||||
|
ErrDiscStrengthenMax(112203),
|
||||||
|
ErrDiscPhaseMax(112204),
|
||||||
|
ErrDiscStarMax(112205),
|
||||||
|
ErrStarTowerRankSeasonNoData(112301),
|
||||||
|
ErrStarTowerRankSeasonNotYetOpen(112302),
|
||||||
|
ErrStarTowerRankSeasonEnd(112303),
|
||||||
|
ErrAgentNoData(112401),
|
||||||
|
ErrAgentInProcess(112402),
|
||||||
|
ErrAgentCond(112403),
|
||||||
|
ErrAgentCharOccupied(112404),
|
||||||
|
ErrAgentBuildOccupied(112405),
|
||||||
|
ErrAgentBuildScoreLow(112406),
|
||||||
|
ErrAgentCountLimit(112407),
|
||||||
|
ErrAgentDailyLimit(112408),
|
||||||
|
ErrAgentWeeklyLimit(112409),
|
||||||
|
ErrAgentNotCompleted(112410),
|
||||||
|
ErrVampireSurvivorBuildNum(112501),
|
||||||
|
ErrVampireSurvivorBuildCharRepetition(112502),
|
||||||
|
ErrTowerGrowthGroupActivated(112601),
|
||||||
|
ErrJointDrillNotExist(112701),
|
||||||
|
ErrJointDrillInProgress(112702),
|
||||||
|
ErrJointDrillNotYetOpen(112703),
|
||||||
|
ErrJointDrillAlreadyClosed(112704),
|
||||||
|
ErrScoreBossNotOpen(112801),
|
||||||
|
ErrNickNameResetLimit(112901),
|
||||||
|
ErrCapOverflow(119901),
|
||||||
|
ErrConfig(119902),
|
||||||
|
ErrCondCheck(119903),
|
||||||
|
ErrNotYetOpen(119904),
|
||||||
|
ErrAlreadyClosed(119905),
|
||||||
|
ErrCondNotMet(119906),
|
||||||
|
ErrInsufficientWorldClass(119907),
|
||||||
|
ErrNoRewardsToReceive(119908),
|
||||||
|
ErrAlreayExists(119909),
|
||||||
|
ErrExchangeNotSupported(119910),
|
||||||
|
ErrRequestTooFrequent(119911),
|
||||||
|
ErrAlreaydReceive(119912),
|
||||||
|
ErrLimit(119913),
|
||||||
|
ErrDataUpdated(119914);
|
||||||
|
|
||||||
|
final int value;
|
||||||
|
|
||||||
|
PlayerErrorCode(int value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,6 +24,7 @@ public class GameSession {
|
|||||||
private String token;
|
private String token;
|
||||||
private Account account;
|
private Account account;
|
||||||
private Player player;
|
private Player player;
|
||||||
|
private String ipAddress;
|
||||||
|
|
||||||
// Crypto
|
// Crypto
|
||||||
private int encryptMethod; // 0 = gcm, 1 = chacha20
|
private int encryptMethod; // 0 = gcm, 1 = chacha20
|
||||||
@@ -143,6 +144,10 @@ public class GameSession {
|
|||||||
this.lastActiveTime = System.currentTimeMillis();
|
this.lastActiveTime = System.currentTimeMillis();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void updateIpAddress(String ip) {
|
||||||
|
this.ipAddress = ip;
|
||||||
|
}
|
||||||
|
|
||||||
// Packet encoding helper functions
|
// Packet encoding helper functions
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
package emu.nebula.server.handlers;
|
package emu.nebula.server.handlers;
|
||||||
|
|
||||||
|
import emu.nebula.Nebula;
|
||||||
|
import emu.nebula.game.player.Player;
|
||||||
|
import emu.nebula.game.player.PlayerErrorCode;
|
||||||
|
import emu.nebula.net.GameSession;
|
||||||
|
import emu.nebula.net.HandlerId;
|
||||||
import emu.nebula.net.NetHandler;
|
import emu.nebula.net.NetHandler;
|
||||||
import emu.nebula.net.NetMsgId;
|
import emu.nebula.net.NetMsgId;
|
||||||
import emu.nebula.proto.PlayerLogin.LoginReq;
|
import emu.nebula.proto.PlayerLogin.LoginReq;
|
||||||
import emu.nebula.proto.PlayerLogin.LoginResp;
|
import emu.nebula.proto.PlayerLogin.LoginResp;
|
||||||
import emu.nebula.net.HandlerId;
|
import emu.nebula.proto.Public.Error;
|
||||||
import emu.nebula.Nebula;
|
|
||||||
import emu.nebula.net.GameSession;
|
|
||||||
|
|
||||||
@HandlerId(NetMsgId.player_login_req)
|
@HandlerId(NetMsgId.player_login_req)
|
||||||
public class HandlerPlayerLoginReq extends NetHandler {
|
public class HandlerPlayerLoginReq extends NetHandler {
|
||||||
@@ -20,20 +23,35 @@ public class HandlerPlayerLoginReq extends NetHandler {
|
|||||||
// Parse request
|
// Parse request
|
||||||
var req = LoginReq.parseFrom(message);
|
var req = LoginReq.parseFrom(message);
|
||||||
|
|
||||||
// os
|
// OS
|
||||||
String loginToken = req.getOfficialOverseas().getToken();
|
String loginToken = req.getOfficialOverseas().getToken();
|
||||||
|
|
||||||
if (loginToken == null || loginToken.isEmpty()) {
|
if (loginToken.isEmpty()) {
|
||||||
// cn
|
// CN
|
||||||
loginToken = req.getOfficial().getToken();
|
loginToken = req.getOfficial().getToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var banModule = Nebula.getGameContext().getBanModule();
|
||||||
|
|
||||||
|
// Check IP ban
|
||||||
|
if (banModule.isIpBanned(session.getIpAddress())) {
|
||||||
|
var banInfo = banModule.getIpBanInfo(session.getIpAddress());
|
||||||
|
return session.encodeMsg(NetMsgId.player_login_failed_ack, banInfo.toProto());
|
||||||
|
}
|
||||||
|
|
||||||
// Login
|
// Login
|
||||||
boolean result = session.login(loginToken);
|
boolean result = session.login(loginToken);
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return session.encodeMsg(NetMsgId.player_login_failed_ack);
|
Error errorCause = Error.newInstance().setCode(PlayerErrorCode.ErrLogin.getValue());
|
||||||
|
return session.encodeMsg(NetMsgId.player_login_failed_ack, errorCause);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check player ban
|
||||||
|
int playerUid = session.getPlayer().getUid();
|
||||||
|
if (banModule.isPlayerBanned(playerUid)) {
|
||||||
|
var banInfo = banModule.getPlayerBanInfo(playerUid);
|
||||||
|
return session.encodeMsg(NetMsgId.player_login_failed_ack, banInfo.toProto());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Regenerate session token because we are switching encrpytion method
|
// Regenerate session token because we are switching encrpytion method
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package emu.nebula.server.handlers;
|
package emu.nebula.server.handlers;
|
||||||
|
|
||||||
|
import emu.nebula.game.player.PlayerErrorCode;
|
||||||
import emu.nebula.net.NetHandler;
|
import emu.nebula.net.NetHandler;
|
||||||
import emu.nebula.net.NetMsgId;
|
import emu.nebula.net.NetMsgId;
|
||||||
import emu.nebula.proto.PlayerSignatureEdit.PlayerSignatureEditReq;
|
import emu.nebula.proto.PlayerSignatureEdit.PlayerSignatureEditReq;
|
||||||
@@ -27,7 +28,9 @@ public class HandlerPlayerSignatureEdit extends NetHandler {
|
|||||||
|
|
||||||
return session.encodeMsg(
|
return session.encodeMsg(
|
||||||
NetMsgId.player_signature_edit_failed_ack,
|
NetMsgId.player_signature_edit_failed_ack,
|
||||||
Error.newInstance().setCode(119902).addArguments("\nCommand Result: " + result.getMessage())
|
Error.newInstance()
|
||||||
|
.setCode(PlayerErrorCode.ErrConfig.getValue())
|
||||||
|
.addArguments("\nCommand Result: " + result.getMessage())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -111,6 +111,7 @@ public class AgentZoneHandler implements Handler {
|
|||||||
// Update last active time for session
|
// Update last active time for session
|
||||||
if (session != null) {
|
if (session != null) {
|
||||||
session.updateLastActiveTime();
|
session.updateLastActiveTime();
|
||||||
|
session.updateIpAddress(ctx.ip());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle packet
|
// Handle packet
|
||||||
|
|||||||
@@ -4,13 +4,17 @@ import java.io.File;
|
|||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.ServerSocket;
|
import java.net.ServerSocket;
|
||||||
|
import java.time.Instant;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.YearMonth;
|
import java.time.YearMonth;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.ThreadLocalRandom;
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
|
import emu.nebula.GameConstants;
|
||||||
import it.unimi.dsi.fastutil.ints.IntList;
|
import it.unimi.dsi.fastutil.ints.IntList;
|
||||||
|
|
||||||
public class Utils {
|
public class Utils {
|
||||||
@@ -321,4 +325,16 @@ public class Utils {
|
|||||||
var month = YearMonth.of(date.getYear(), date.getMonthValue());
|
var month = YearMonth.of(date.getYear(), date.getMonthValue());
|
||||||
return month.lengthOfMonth();
|
return month.lengthOfMonth();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String formatTimestamp(long timestamp) {
|
||||||
|
return Instant.ofEpochMilli(timestamp)
|
||||||
|
.atZone(GameConstants.UTC_ZONE)
|
||||||
|
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String formatTimestamp(long timestamp, String pattern) {
|
||||||
|
return Instant.ofEpochMilli(timestamp)
|
||||||
|
.atZone(GameConstants.UTC_ZONE)
|
||||||
|
.format(DateTimeFormatter.ofPattern(pattern));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user