mirror of
https://github.com/Melledy/Nebula.git
synced 2025-12-12 20:34:36 +01:00
Add Remote Command API (Use KEY)
This commit is contained in:
@@ -17,10 +17,11 @@ public class Config {
|
||||
|
||||
public HttpServerConfig httpServer = new HttpServerConfig(80);
|
||||
public GameServerConfig gameServer = new GameServerConfig(80);
|
||||
|
||||
|
||||
public ServerOptions serverOptions = new ServerOptions();
|
||||
public ServerRates serverRates = new ServerRates();
|
||||
public LogOptions logOptions = new LogOptions();
|
||||
public RemoteCommand remoteCommand = new RemoteCommand();
|
||||
|
||||
public int customDataVersion = 0;
|
||||
public String resourceDir = "./resources";
|
||||
@@ -54,35 +55,35 @@ public class Config {
|
||||
public int bindPort;
|
||||
public String publicAddress = "127.0.0.1"; // Will return bindAddress if publicAddress is null
|
||||
public Integer publicPort; // Will return bindPort if publicPort is null
|
||||
|
||||
|
||||
public ServerConfig(int port) {
|
||||
this.bindPort = port;
|
||||
}
|
||||
|
||||
|
||||
public String getPublicAddress() {
|
||||
if (this.publicAddress != null && !this.publicAddress.isEmpty()) {
|
||||
return this.publicAddress;
|
||||
}
|
||||
|
||||
|
||||
return this.bindAddress;
|
||||
}
|
||||
|
||||
|
||||
public int getPublicPort() {
|
||||
if (this.publicPort != null && this.publicPort != 0) {
|
||||
return this.publicPort;
|
||||
}
|
||||
|
||||
|
||||
return this.bindPort;
|
||||
}
|
||||
|
||||
|
||||
public String getDisplayAddress() {
|
||||
return (useSSL ? "https" : "http") + "://" + getPublicAddress() + ":" + getPublicPort();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Getter
|
||||
public static class HttpServerConfig extends ServerConfig {
|
||||
|
||||
|
||||
public HttpServerConfig(int port) {
|
||||
super(port);
|
||||
}
|
||||
@@ -90,53 +91,59 @@ public class Config {
|
||||
|
||||
@Getter
|
||||
public static class GameServerConfig extends ServerConfig {
|
||||
|
||||
|
||||
public GameServerConfig(int port) {
|
||||
super(port);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Getter
|
||||
public static class ServerOptions {
|
||||
public Set<String> defaultPermissions = Set.of("*");
|
||||
public boolean autoCreateAccount = true;
|
||||
public boolean skipIntro = false;
|
||||
public boolean unlockInstances = true;
|
||||
public int sessionTimeout = 600; // How long to wait (in seconds) after the last http request from a session before removing it from the server
|
||||
public int sessionTimeout = 600; // How long to wait (in seconds) after the last http request from a session
|
||||
// before removing it from the server
|
||||
public int dailyResetHour = 0;
|
||||
public int leaderboardRefreshTime = 60; // Leaderboard refresh time in seconds
|
||||
public WelcomeMail welcomeMail = new WelcomeMail();
|
||||
}
|
||||
|
||||
|
||||
@Getter
|
||||
public static class ServerRates {
|
||||
public double exp = 1.0;
|
||||
}
|
||||
|
||||
|
||||
@Getter
|
||||
public static class LogOptions {
|
||||
public boolean commands = true;
|
||||
public boolean packets = false;
|
||||
}
|
||||
|
||||
|
||||
@Getter
|
||||
public static class RemoteCommand {
|
||||
public boolean useRemoteServices = false;
|
||||
public String serverAdminKey = "HJHASDPIIQWEASDHHAN";
|
||||
}
|
||||
|
||||
@Getter
|
||||
public static class WelcomeMail {
|
||||
public String title;
|
||||
public String sender;
|
||||
public String content;
|
||||
public List<ItemParam> attachments;
|
||||
|
||||
|
||||
public WelcomeMail() {
|
||||
this.title = "Welcome to a Nebula server";
|
||||
this.sender = "Server";
|
||||
this.content = "Welcome to Nebula! Please take these items as a starter gift.";
|
||||
this.attachments = List.of(
|
||||
new ItemParam(86009, 1),
|
||||
new ItemParam(86002, 1),
|
||||
new ItemParam(1, 1_000_000),
|
||||
new ItemParam(2, 30_000)
|
||||
);
|
||||
new ItemParam(86009, 1),
|
||||
new ItemParam(86002, 1),
|
||||
new ItemParam(1, 1_000_000),
|
||||
new ItemParam(2, 30_000));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
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 java.util.Random;
|
||||
|
||||
@Command(label = "remote", permission = "player.remote", requireTarget = true, desc = "/remote. Send remote to web remote")
|
||||
public class RemoteKeyCommand implements CommandHandler {
|
||||
|
||||
private static String lastMessage;
|
||||
|
||||
public static String getLastMessage() {
|
||||
return lastMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandArgs args) {
|
||||
if (Nebula.getConfig().getRemoteCommand().useRemoteServices) {
|
||||
String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
Random random = new Random();
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
int index = random.nextInt(characters.length());
|
||||
sb.append(characters.charAt(index));
|
||||
}
|
||||
args.getTarget().setPlayerRemoteToken(sb.toString());
|
||||
args.getTarget().save();
|
||||
String textsend = "Key Generated: " + sb.toString();
|
||||
lastMessage = textsend;
|
||||
args.sendMessage(textsend);
|
||||
return;
|
||||
}
|
||||
String textsend = "RemoteCommand Disabled on Server";
|
||||
args.getTarget().setPlayerRemoteToken(null);
|
||||
args.getTarget().save();
|
||||
lastMessage = textsend;
|
||||
args.sendMessage(textsend);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,7 @@ import emu.nebula.proto.Public.WorldClassRewardState;
|
||||
import emu.nebula.proto.Public.Title;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import us.hebi.quickbuf.ProtoMessage;
|
||||
import us.hebi.quickbuf.RepeatedInt;
|
||||
|
||||
@@ -87,6 +88,7 @@ public class Player implements GameDatabaseObject {
|
||||
private final transient InfinityTowerManager infinityTowerManager;
|
||||
private final transient VampireSurvivorManager vampireSurvivorManager;
|
||||
private final transient ScoreBossManager scoreBossManager;
|
||||
@Indexed @Setter @Getter private String playerRemoteToken;
|
||||
|
||||
// Referenced data
|
||||
private transient Inventory inventory;
|
||||
@@ -142,6 +144,7 @@ public class Player implements GameDatabaseObject {
|
||||
this.honor = new int[3];
|
||||
this.showChars = new int[3];
|
||||
this.boards = new int[] {410301};
|
||||
this.playerRemoteToken = null;
|
||||
|
||||
this.level = 1;
|
||||
this.energy = 240;
|
||||
|
||||
@@ -24,45 +24,49 @@ public class GameSession {
|
||||
private String token;
|
||||
private Account account;
|
||||
private Player player;
|
||||
|
||||
|
||||
// Crypto
|
||||
private int encryptMethod; // 0 = gcm, 1 = chacha20
|
||||
private byte[] clientPublicKey;
|
||||
private byte[] serverPublicKey;
|
||||
private byte[] serverPrivateKey;
|
||||
private byte[] key;
|
||||
|
||||
|
||||
// Session cleanup
|
||||
private boolean remove;
|
||||
private long lastActiveTime;
|
||||
|
||||
|
||||
public GameSession() {
|
||||
this.updateLastActiveTime();
|
||||
}
|
||||
|
||||
|
||||
public synchronized Player getPlayer() {
|
||||
return this.player;
|
||||
}
|
||||
|
||||
|
||||
public synchronized void setPlayer(Player player) {
|
||||
this.player = player;
|
||||
this.player.setSession(this);
|
||||
this.player.onLogin();
|
||||
}
|
||||
|
||||
|
||||
public synchronized void clearPlayer() {
|
||||
// Sanity check
|
||||
if (this.player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Clear player
|
||||
var player = this.player;
|
||||
this.player = null;
|
||||
|
||||
|
||||
// Clear remote token
|
||||
player.setPlayerRemoteToken(null);
|
||||
player.save();
|
||||
|
||||
// Remove session from player
|
||||
player.removeSession();
|
||||
|
||||
|
||||
// Set remove flag
|
||||
this.remove = true;
|
||||
}
|
||||
@@ -70,76 +74,77 @@ public class GameSession {
|
||||
public synchronized boolean hasPlayer() {
|
||||
return this.player != null;
|
||||
}
|
||||
|
||||
|
||||
// Encryption
|
||||
|
||||
public void setClientKey(RepeatedByte key) {
|
||||
this.clientPublicKey = key.toArray();
|
||||
}
|
||||
|
||||
|
||||
public void generateServerKey() {
|
||||
var pair = AeadHelper.generateECDHKEyPair();
|
||||
|
||||
|
||||
this.serverPrivateKey = ((ECPrivateKeyParameters) pair.getPrivate()).getD().toByteArray();
|
||||
this.serverPublicKey = ((ECPublicKeyParameters) pair.getPublic()).getQ().getEncoded(false);
|
||||
}
|
||||
|
||||
|
||||
public void calculateKey() {
|
||||
this.key = AeadHelper.generateKey(clientPublicKey, serverPublicKey, serverPrivateKey);
|
||||
this.encryptMethod = Utils.randomRange(0, 1);
|
||||
}
|
||||
|
||||
|
||||
public String generateToken() {
|
||||
String temp = System.currentTimeMillis() + ":" + AeadHelper.generateBytes(64).toString();
|
||||
|
||||
String temp = System.currentTimeMillis() + ":" + AeadHelper.generateBytes(64).toString();
|
||||
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-512");
|
||||
byte[] bytes = md.digest(temp.getBytes());
|
||||
|
||||
|
||||
this.token = Base64.getEncoder().encodeToString(bytes);
|
||||
} catch (Exception e) {
|
||||
this.token = Base64.getEncoder().encodeToString(temp.getBytes());
|
||||
}
|
||||
|
||||
|
||||
return this.token;
|
||||
}
|
||||
|
||||
|
||||
// Login
|
||||
|
||||
|
||||
public boolean login(String loginToken) {
|
||||
// Sanity check
|
||||
if (this.account != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Get account
|
||||
this.account = AccountHelper.getAccountByLoginToken(loginToken);
|
||||
|
||||
|
||||
if (account == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Note: We should cache players in case multiple sessions try to login to the same player at the time
|
||||
|
||||
// Note: We should cache players in case multiple sessions try to login to the
|
||||
// same player at the time
|
||||
// Get player by account
|
||||
var player = Nebula.getGameContext().getPlayerModule().loadPlayer(account);
|
||||
|
||||
|
||||
// Skip intro
|
||||
if (player == null && Nebula.getConfig().getServerOptions().skipIntro) {
|
||||
player = Nebula.getGameContext().getPlayerModule().createPlayer(this, "Player", false);
|
||||
}
|
||||
|
||||
|
||||
// Set player
|
||||
if (player != null) {
|
||||
this.setPlayer(player);
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public void updateLastActiveTime() {
|
||||
this.lastActiveTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
|
||||
// Packet encoding helper functions
|
||||
|
||||
@SneakyThrows
|
||||
@@ -154,101 +159,98 @@ public class GameSession {
|
||||
this.addNextPackages(proto);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Encode to message like normal
|
||||
return PacketHelper.encodeMsg(msgId, proto);
|
||||
}
|
||||
|
||||
|
||||
public byte[] encodeMsg(int msgId) {
|
||||
// Check if we have any packages to send to the client
|
||||
if (this.getPlayer() != null) {
|
||||
// Check if player should add any packages
|
||||
this.checkPlayerStates();
|
||||
|
||||
|
||||
// Chain next packages for player
|
||||
if (this.getPlayer().hasNextPackages()) {
|
||||
// Create a proto so we can add next packages
|
||||
var proto = Nil.newInstance();
|
||||
|
||||
|
||||
// Encode proto with next packages
|
||||
return this.encodeMsg(msgId, this.addNextPackages(proto));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Encode simple message
|
||||
return PacketHelper.encodeMsg(msgId);
|
||||
}
|
||||
|
||||
|
||||
private void checkPlayerStates() {
|
||||
// Update mail state flag
|
||||
if (this.getPlayer().getMailbox().isNewState()) {
|
||||
// Clear
|
||||
this.getPlayer().getMailbox().clearNewState();
|
||||
|
||||
|
||||
// Send mail state notify
|
||||
this.getPlayer().addNextPackage(
|
||||
NetMsgId.mail_state_notify,
|
||||
MailState.newInstance().setNew(true)
|
||||
);
|
||||
NetMsgId.mail_state_notify,
|
||||
MailState.newInstance().setNew(true));
|
||||
}
|
||||
|
||||
|
||||
// Check handbook states
|
||||
if (this.getPlayer().getCharacters().isUpdateCharHandbook()) {
|
||||
getPlayer().getCharacters().setUpdateCharHandbook(false);
|
||||
getPlayer().addNextPackage(
|
||||
NetMsgId.handbook_change_notify,
|
||||
this.getPlayer().getCharacters().getCharacterHandbook()
|
||||
);
|
||||
NetMsgId.handbook_change_notify,
|
||||
this.getPlayer().getCharacters().getCharacterHandbook());
|
||||
}
|
||||
if (this.getPlayer().getCharacters().isUpdateDiscHandbook()) {
|
||||
getPlayer().getCharacters().setUpdateDiscHandbook(false);
|
||||
getPlayer().addNextPackage(
|
||||
NetMsgId.handbook_change_notify,
|
||||
this.getPlayer().getCharacters().getDiscHandbook()
|
||||
);
|
||||
NetMsgId.handbook_change_notify,
|
||||
this.getPlayer().getCharacters().getDiscHandbook());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private ProtoMessage<?> addNextPackages(ProtoMessage<?> proto) {
|
||||
// Sanity check and make sure proto has a "nextPackage" field
|
||||
if (!PacketHelper.hasNextPackageMethod(proto)) {
|
||||
return proto;
|
||||
}
|
||||
|
||||
|
||||
// Set next package
|
||||
if (this.getPlayer().getNextPackages().size() > 0) {
|
||||
// Set current package
|
||||
NetMsgPacket curPacket = null;
|
||||
|
||||
|
||||
// Chain link next packages
|
||||
while (getPlayer().getNextPackages().size() > 0) {
|
||||
// Make sure the current packet has a nextPackage field
|
||||
if (curPacket != null && !PacketHelper.hasNextPackageMethod(curPacket.getProto())) {
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
// Get current package
|
||||
var nextPacket = getPlayer().getNextPackages().pop();
|
||||
|
||||
|
||||
// Set cur packet if its null
|
||||
if (curPacket == null) {
|
||||
curPacket = nextPacket;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// Set next package
|
||||
PacketHelper.setNextPackage(nextPacket.getProto(), curPacket.toByteArray());
|
||||
|
||||
|
||||
// Update next packet
|
||||
curPacket = nextPacket;
|
||||
}
|
||||
|
||||
|
||||
// Set next package of current proto via reflection
|
||||
if (curPacket != null) {
|
||||
PacketHelper.setNextPackage(proto, curPacket.toByteArray());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return proto;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,11 +27,11 @@ public class HttpServer {
|
||||
private final Javalin app;
|
||||
private ServerType type;
|
||||
private boolean started;
|
||||
|
||||
|
||||
// Cached client diff
|
||||
private PatchList patchlist;
|
||||
private byte[] diff;
|
||||
|
||||
|
||||
public HttpServer(ServerType type) {
|
||||
this.type = type;
|
||||
this.app = Javalin.create(javalinConfig -> {
|
||||
@@ -44,7 +44,7 @@ public class HttpServer {
|
||||
this.loadPatchList();
|
||||
this.addRoutes();
|
||||
}
|
||||
|
||||
|
||||
public HttpServerConfig getServerConfig() {
|
||||
return Nebula.getConfig().getHttpServer();
|
||||
}
|
||||
@@ -65,26 +65,26 @@ public class HttpServer {
|
||||
sslContextFactory.setRenegotiationAllowed(false);
|
||||
return sslContextFactory;
|
||||
}
|
||||
|
||||
|
||||
// Patch list
|
||||
|
||||
|
||||
public long getDataVersion() {
|
||||
return getPatchlist() != null ? getPatchlist().getVersion() : GameConstants.getDataVersion();
|
||||
}
|
||||
|
||||
|
||||
public synchronized void loadPatchList() {
|
||||
// Clear
|
||||
this.patchlist = null;
|
||||
this.diff = null;
|
||||
|
||||
|
||||
// Get file
|
||||
File file = new File(Nebula.getConfig().getPatchListPath());
|
||||
|
||||
|
||||
if (!file.exists()) {
|
||||
this.diff = ClientDiff.newInstance().toByteArray();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Load
|
||||
try (FileReader reader = new FileReader(file)) {
|
||||
this.patchlist = JsonUtils.loadToClass(reader, PatchList.class);
|
||||
@@ -93,21 +93,23 @@ public class HttpServer {
|
||||
this.patchlist = null;
|
||||
this.diff = ClientDiff.newInstance().toByteArray();
|
||||
}
|
||||
|
||||
|
||||
if (this.patchlist != null) {
|
||||
Nebula.getLogger().info("Loaded patchlist (Data version: " + patchlist.getVersion() + ")");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Start server
|
||||
|
||||
|
||||
public void start() {
|
||||
if (this.started) return;
|
||||
if (this.started)
|
||||
return;
|
||||
this.started = true;
|
||||
|
||||
// Http server
|
||||
if (getServerConfig().isUseSSL()) {
|
||||
ServerConnector sslConnector = new ServerConnector(getApp().jettyServer().server(), getSSLContextFactory(), getHttpFactory());
|
||||
ServerConnector sslConnector = new ServerConnector(getApp().jettyServer().server(), getSSLContextFactory(),
|
||||
getHttpFactory());
|
||||
sslConnector.setHost(getServerConfig().getBindAddress());
|
||||
sslConnector.setPort(getServerConfig().getBindPort());
|
||||
getApp().jettyServer().server().addConnector(sslConnector);
|
||||
@@ -120,19 +122,20 @@ public class HttpServer {
|
||||
// Done
|
||||
Nebula.getLogger().info("Http Server started on " + getServerConfig().getBindPort());
|
||||
}
|
||||
|
||||
|
||||
// Server endpoints
|
||||
|
||||
private void addRoutes() {
|
||||
|
||||
// Add routes
|
||||
if (this.getType().runLogin()) {
|
||||
this.addLoginServerRoutes();
|
||||
}
|
||||
|
||||
|
||||
if (this.getType().runGame()) {
|
||||
this.addGameServerRoutes();
|
||||
}
|
||||
|
||||
|
||||
// Exception handler
|
||||
getApp().exception(Exception.class, (e, c) -> {
|
||||
e.printStackTrace();
|
||||
@@ -141,29 +144,40 @@ public class HttpServer {
|
||||
// Fallback handler
|
||||
getApp().error(404, this::notFoundHandler);
|
||||
}
|
||||
|
||||
|
||||
private void addLoginServerRoutes() {
|
||||
// https://en-sdk-api.yostarplat.com/
|
||||
getApp().post("/common/config", new CommonConfigHandler(this));
|
||||
getApp().post("/common/version", new HttpJsonResponse("{\"Code\":200,\"Data\":{\"Agreement\":[{\"Version\":\"0.1\",\"Type\":\"user_agreement\",\"Title\":\"用户协议\",\"Content\":\"\",\"Lang\":\"en\"},{\"Version\":\"0.1\",\"Type\":\"privacy_agreement\",\"Title\":\"隐私政策\",\"Content\":\"\",\"Lang\":\"en\"}],\"ErrorCode\":\"4.4\"},\"Msg\":\"OK\"}"));
|
||||
|
||||
getApp().post("/common/version", new HttpJsonResponse(
|
||||
"{\"Code\":200,\"Data\":{\"Agreement\":[{\"Version\":\"0.1\",\"Type\":\"user_agreement\",\"Title\":\"用户协议\",\"Content\":\"\",\"Lang\":\"en\"},{\"Version\":\"0.1\",\"Type\":\"privacy_agreement\",\"Title\":\"隐私政策\",\"Content\":\"\",\"Lang\":\"en\"}],\"ErrorCode\":\"4.4\"},\"Msg\":\"OK\"}"));
|
||||
|
||||
getApp().post("/user/detail", new UserLoginHandler());
|
||||
getApp().post("/user/set", new UserSetDataHandler());
|
||||
getApp().post("/user/login", new UserLoginHandler());
|
||||
getApp().post("/user/quick-login", new UserLoginHandler());
|
||||
|
||||
|
||||
getApp().post("/yostar/get-auth", new GetAuthHandler());
|
||||
getApp().post("/yostar/send-code", new HttpJsonResponse("{\"Code\":200,\"Data\":{},\"Msg\":\"OK\"}")); // Dummy handler
|
||||
|
||||
getApp().post("/yostar/send-code", new HttpJsonResponse("{\"Code\":200,\"Data\":{},\"Msg\":\"OK\"}")); // Dummy
|
||||
// handler
|
||||
|
||||
// https://nova-static.stellasora.global/
|
||||
getApp().get("/meta/serverlist.html", new MetaServerlistHandler(this));
|
||||
getApp().get("/meta/win.html", new MetaWinHandler(this));
|
||||
// if (!Nebula.getConfig().getRemoteCommand().useRemoteServices) {
|
||||
// getApp().post("/api/command", new RemoteHandler());
|
||||
// }
|
||||
getApp().post("/api/command", new RemoteHandler());
|
||||
// getApp.get("/notice/noticelist.html");
|
||||
getApp().get("/webchatv3/*", ctx -> {
|
||||
ctx.redirect("https://google.com");
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void addGameServerRoutes() {
|
||||
getApp().post("/agent-zone-1/", new AgentZoneHandler());
|
||||
}
|
||||
|
||||
|
||||
private void notFoundHandler(Context ctx) {
|
||||
ctx.status(404);
|
||||
ctx.contentType(ContentType.APPLICATION_JSON);
|
||||
|
||||
@@ -10,7 +10,7 @@ import emu.nebula.net.GameSession;
|
||||
|
||||
@HandlerId(NetMsgId.player_login_req)
|
||||
public class HandlerPlayerLoginReq extends NetHandler {
|
||||
|
||||
|
||||
public boolean requirePlayer() {
|
||||
return false;
|
||||
}
|
||||
@@ -20,21 +20,21 @@ public class HandlerPlayerLoginReq extends NetHandler {
|
||||
// Parse request
|
||||
var req = LoginReq.parseFrom(message);
|
||||
var loginToken = req.getOfficialOverseas().getToken();
|
||||
|
||||
|
||||
// Login
|
||||
boolean result = session.login(loginToken);
|
||||
|
||||
|
||||
if (!result) {
|
||||
return session.encodeMsg(NetMsgId.player_login_failed_ack);
|
||||
}
|
||||
|
||||
|
||||
// Regenerate session token because we are switching encrpytion method
|
||||
Nebula.getGameContext().generateSessionToken(session);
|
||||
|
||||
|
||||
// Create rsp
|
||||
var rsp = LoginResp.newInstance()
|
||||
.setToken(session.getToken());
|
||||
|
||||
|
||||
// Encode and send to client
|
||||
return session.encodeMsg(NetMsgId.player_login_succeed_ack, rsp);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,24 @@ public class HandlerPlayerSignatureEdit extends NetHandler {
|
||||
|
||||
// Check if we need to handle a command
|
||||
if (signature.charAt(0) == '!' || signature.charAt(0) == '/') {
|
||||
String commandLabel = signature.toLowerCase().trim();
|
||||
if (commandLabel.startsWith("!") || commandLabel.startsWith("/")) {
|
||||
commandLabel = commandLabel.substring(1).split(" ")[0];
|
||||
}
|
||||
|
||||
Nebula.getCommandManager().invoke(session.getPlayer(), signature);
|
||||
|
||||
// If this is the remote command, return the message
|
||||
if ("remote".equals(commandLabel)) {
|
||||
String remoteMessage = emu.nebula.command.commands.RemoteKeyCommand.getLastMessage();
|
||||
if (remoteMessage != null) {
|
||||
return session.encodeMsg(
|
||||
NetMsgId.player_signature_edit_failed_ack,
|
||||
Error.newInstance().setCode(119902).addArguments("\n" + remoteMessage)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return session.encodeMsg(
|
||||
NetMsgId.player_signature_edit_failed_ack,
|
||||
Error.newInstance().setCode(119902).addArguments("\nCommand Success")
|
||||
|
||||
94
src/main/java/emu/nebula/server/routes/RemoteHandler.java
Normal file
94
src/main/java/emu/nebula/server/routes/RemoteHandler.java
Normal file
@@ -0,0 +1,94 @@
|
||||
package emu.nebula.server.routes;
|
||||
|
||||
import emu.nebula.Nebula;
|
||||
import emu.nebula.game.player.Player;
|
||||
import emu.nebula.util.JsonUtils;
|
||||
import io.javalin.http.ContentType;
|
||||
import io.javalin.http.Context;
|
||||
import io.javalin.http.Handler;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class RemoteHandler implements Handler {
|
||||
|
||||
static class RemoteCommandRequest {
|
||||
public String token;
|
||||
public String command;
|
||||
}
|
||||
|
||||
// Cache: Token -> UID
|
||||
private static final java.util.Map<String, Integer> tokenCache = new java.util.concurrent.ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void handle(@NotNull Context ctx) throws Exception {
|
||||
if (!Nebula.getConfig().getRemoteCommand().useRemoteServices) {
|
||||
ctx.status(403);
|
||||
ctx.result("{\"Code\":403,\"Msg\":\"RemoteServer not enable\"}");
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse body
|
||||
RemoteCommandRequest req = JsonUtils.decode(ctx.body(), RemoteCommandRequest.class);
|
||||
if (req == null || req.token == null || req.command == null) {
|
||||
ctx.status(400);
|
||||
ctx.result("{\"Code\":400,\"Msg\":\"Invalid request\"}");
|
||||
return;
|
||||
}
|
||||
|
||||
String token = req.token;
|
||||
String command = req.command;
|
||||
String adminKey = Nebula.getConfig().getRemoteCommand().getServerAdminKey();
|
||||
|
||||
// Check admin key
|
||||
if (token.equals(adminKey)) {
|
||||
Nebula.getCommandManager().invoke(null, command);
|
||||
Nebula.getLogger().warn(
|
||||
"\u001B[38;2;252;186;3mRemote Server (Using Admin Key) sent command: /" + command + "\u001B[0m");
|
||||
ctx.status(200);
|
||||
ctx.contentType(ContentType.APPLICATION_JSON);
|
||||
ctx.result("{\"Code\":200,\"Data\":{},\"Msg\":\"Command executed\"}");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check player
|
||||
Player player = null;
|
||||
|
||||
// 1. Try cache
|
||||
Integer cachedUid = tokenCache.get(token);
|
||||
if (cachedUid != null) {
|
||||
player = Nebula.getGameContext().getPlayerModule().getPlayer(cachedUid);
|
||||
// Verify token matches (in case player changed token or cache is stale)
|
||||
if (player != null && !token.equals(player.getPlayerRemoteToken())) {
|
||||
player = null;
|
||||
tokenCache.remove(token);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Fallback to DB if not in cache or cache invalid
|
||||
if (player == null) {
|
||||
player = Nebula.getGameDatabase().getObjectByField(Player.class, "playerRemoteToken", token);
|
||||
if (player != null) {
|
||||
tokenCache.put(token, player.getUid());
|
||||
}
|
||||
}
|
||||
|
||||
if (player != null) {
|
||||
// Append target UID to command to ensure it targets the player
|
||||
// CommandArgs parses @UID to set the target
|
||||
String finalCommand = command + " @" + player.getUid();
|
||||
|
||||
Nebula.getLogger().info("Remote Player Request [" + player.getUid() + "]: " + finalCommand);
|
||||
|
||||
// Execute as console (null sender) but targeting the player
|
||||
Nebula.getCommandManager().invoke(null, finalCommand);
|
||||
|
||||
ctx.status(200);
|
||||
ctx.contentType(ContentType.APPLICATION_JSON);
|
||||
ctx.result("{\"Code\":200,\"Data\":{},\"Msg\":\"Command executed\"}");
|
||||
return;
|
||||
}
|
||||
|
||||
// Invalid token
|
||||
ctx.status(403);
|
||||
ctx.result("{\"Code\":403,\"Msg\":\"Invalid token\"}");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user