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.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);
|
||||
|
||||
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 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user