diff --git a/src/main/java/emu/nebula/command/commands/BanCommand.java b/src/main/java/emu/nebula/command/commands/BanCommand.java new file mode 100644 index 0000000..2efb791 --- /dev/null +++ b/src/main/java/emu/nebula/command/commands/BanCommand.java @@ -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"; + } + } + } +} \ No newline at end of file diff --git a/src/main/java/emu/nebula/command/commands/UnbanCommand.java b/src/main/java/emu/nebula/command/commands/UnbanCommand.java new file mode 100644 index 0000000..228aee9 --- /dev/null +++ b/src/main/java/emu/nebula/command/commands/UnbanCommand.java @@ -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"; + } + } + } +} diff --git a/src/main/java/emu/nebula/game/GameContext.java b/src/main/java/emu/nebula/game/GameContext.java index cc2e3b4..0e6e143 100644 --- a/src/main/java/emu/nebula/game/GameContext.java +++ b/src/main/java/emu/nebula/game/GameContext.java @@ -9,6 +9,7 @@ import java.util.concurrent.TimeUnit; import emu.nebula.GameConstants; import emu.nebula.Nebula; import emu.nebula.game.activity.ActivityModule; +import emu.nebula.game.ban.BanModule; import emu.nebula.game.gacha.GachaModule; import emu.nebula.game.player.PlayerModule; import emu.nebula.game.scoreboss.ScoreBossModule; @@ -30,6 +31,7 @@ public class GameContext implements Runnable { private final TutorialModule tutorialModule; private final ActivityModule activityModule; private final ScoreBossModule scoreBossModule; + private final BanModule banModule; // Game loop private final ScheduledExecutorService scheduler; @@ -48,6 +50,7 @@ public class GameContext implements Runnable { this.tutorialModule = new TutorialModule(this); this.activityModule = new ActivityModule(this); this.scoreBossModule = new ScoreBossModule(this); + this.banModule = new BanModule(this); // Run game loop this.scheduler = Executors.newScheduledThreadPool(1); diff --git a/src/main/java/emu/nebula/game/ban/BanInfo.java b/src/main/java/emu/nebula/game/ban/BanInfo.java new file mode 100644 index 0000000..2b03353 --- /dev/null +++ b/src/main/java/emu/nebula/game/ban/BanInfo.java @@ -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)); + } +} \ No newline at end of file diff --git a/src/main/java/emu/nebula/game/ban/BanModule.java b/src/main/java/emu/nebula/game/ban/BanModule.java new file mode 100644 index 0000000..64ff70d --- /dev/null +++ b/src/main/java/emu/nebula/game/ban/BanModule.java @@ -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 cachedIpBans = new ConcurrentHashMap<>(); + private final Map 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 + *

+ * 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 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; + } +} \ No newline at end of file diff --git a/src/main/java/emu/nebula/game/player/PlayerErrorCode.java b/src/main/java/emu/nebula/game/player/PlayerErrorCode.java new file mode 100644 index 0000000..21ad15d --- /dev/null +++ b/src/main/java/emu/nebula/game/player/PlayerErrorCode.java @@ -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; + } +} diff --git a/src/main/java/emu/nebula/net/GameSession.java b/src/main/java/emu/nebula/net/GameSession.java index 160811f..6a499b8 100644 --- a/src/main/java/emu/nebula/net/GameSession.java +++ b/src/main/java/emu/nebula/net/GameSession.java @@ -24,6 +24,7 @@ public class GameSession { private String token; private Account account; private Player player; + private String ipAddress; // Crypto private int encryptMethod; // 0 = gcm, 1 = chacha20 @@ -143,6 +144,10 @@ public class GameSession { this.lastActiveTime = System.currentTimeMillis(); } + public void updateIpAddress(String ip) { + this.ipAddress = ip; + } + // Packet encoding helper functions @SneakyThrows diff --git a/src/main/java/emu/nebula/server/handlers/HandlerPlayerLoginReq.java b/src/main/java/emu/nebula/server/handlers/HandlerPlayerLoginReq.java index a20e0a7..50d5860 100644 --- a/src/main/java/emu/nebula/server/handlers/HandlerPlayerLoginReq.java +++ b/src/main/java/emu/nebula/server/handlers/HandlerPlayerLoginReq.java @@ -1,12 +1,15 @@ 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.NetMsgId; import emu.nebula.proto.PlayerLogin.LoginReq; import emu.nebula.proto.PlayerLogin.LoginResp; -import emu.nebula.net.HandlerId; -import emu.nebula.Nebula; -import emu.nebula.net.GameSession; +import emu.nebula.proto.Public.Error; @HandlerId(NetMsgId.player_login_req) public class HandlerPlayerLoginReq extends NetHandler { @@ -20,20 +23,35 @@ public class HandlerPlayerLoginReq extends NetHandler { // Parse request var req = LoginReq.parseFrom(message); - // os + // OS String loginToken = req.getOfficialOverseas().getToken(); - if (loginToken == null || loginToken.isEmpty()) { - // cn + if (loginToken.isEmpty()) { + // CN 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 boolean result = session.login(loginToken); - 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 diff --git a/src/main/java/emu/nebula/server/handlers/HandlerPlayerSignatureEdit.java b/src/main/java/emu/nebula/server/handlers/HandlerPlayerSignatureEdit.java index 921a3a8..1dd876a 100644 --- a/src/main/java/emu/nebula/server/handlers/HandlerPlayerSignatureEdit.java +++ b/src/main/java/emu/nebula/server/handlers/HandlerPlayerSignatureEdit.java @@ -1,5 +1,6 @@ package emu.nebula.server.handlers; +import emu.nebula.game.player.PlayerErrorCode; import emu.nebula.net.NetHandler; import emu.nebula.net.NetMsgId; import emu.nebula.proto.PlayerSignatureEdit.PlayerSignatureEditReq; @@ -27,7 +28,9 @@ public class HandlerPlayerSignatureEdit extends NetHandler { return session.encodeMsg( 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()) ); } diff --git a/src/main/java/emu/nebula/server/routes/AgentZoneHandler.java b/src/main/java/emu/nebula/server/routes/AgentZoneHandler.java index f4a3160..c1601f0 100644 --- a/src/main/java/emu/nebula/server/routes/AgentZoneHandler.java +++ b/src/main/java/emu/nebula/server/routes/AgentZoneHandler.java @@ -111,6 +111,7 @@ public class AgentZoneHandler implements Handler { // Update last active time for session if (session != null) { session.updateLastActiveTime(); + session.updateIpAddress(ctx.ip()); } // Handle packet diff --git a/src/main/java/emu/nebula/util/Utils.java b/src/main/java/emu/nebula/util/Utils.java index aa0b353..a4b7097 100644 --- a/src/main/java/emu/nebula/util/Utils.java +++ b/src/main/java/emu/nebula/util/Utils.java @@ -4,13 +4,17 @@ import java.io.File; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.ServerSocket; +import java.time.Instant; import java.time.LocalDate; import java.time.YearMonth; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; import java.util.Base64; import java.util.List; import java.util.concurrent.ThreadLocalRandom; +import emu.nebula.GameConstants; import it.unimi.dsi.fastutil.ints.IntList; public class Utils { @@ -321,4 +325,16 @@ public class Utils { var month = YearMonth.of(date.getYear(), date.getMonthValue()); 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)); + } }