Initial Commit

This commit is contained in:
Melledy
2025-10-27 02:02:26 -07:00
commit f58951fe2a
378 changed files with 315914 additions and 0 deletions
@@ -0,0 +1,122 @@
package emu.nebula.server;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import emu.nebula.Config.HttpServerConfig;
import emu.nebula.Nebula;
import emu.nebula.Nebula.ServerType;
import emu.nebula.server.routes.*;
import io.javalin.Javalin;
import io.javalin.http.ContentType;
import io.javalin.http.Context;
import lombok.Getter;
@Getter
public class HttpServer {
private final Javalin app;
private ServerType type;
private boolean started;
public HttpServer(ServerType type) {
this.app = Javalin.create();
this.type = type;
this.addRoutes();
}
public HttpServerConfig getServerConfig() {
return Nebula.getConfig().getHttpServer();
}
private HttpConnectionFactory getHttpFactory() {
HttpConfiguration httpsConfig = new HttpConfiguration();
SecureRequestCustomizer src = new SecureRequestCustomizer();
src.setSniHostCheck(false);
httpsConfig.addCustomizer(src);
return new HttpConnectionFactory(httpsConfig);
}
private SslContextFactory.Server getSSLContextFactory() {
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
sslContextFactory.setKeyStorePath(Nebula.getConfig().getKeystore().getPath());
sslContextFactory.setKeyStorePassword(Nebula.getConfig().getKeystore().getPassword());
sslContextFactory.setSniRequired(false);
sslContextFactory.setRenegotiationAllowed(false);
return sslContextFactory;
}
// Start server
public void start() {
if (this.started) return;
this.started = true;
// Http server
if (getServerConfig().isUseSSL()) {
ServerConnector sslConnector = new ServerConnector(getApp().jettyServer().server(), getSSLContextFactory(), getHttpFactory());
sslConnector.setHost(getServerConfig().getBindAddress());
sslConnector.setPort(getServerConfig().getBindPort());
getApp().jettyServer().server().addConnector(sslConnector);
getApp().start();
} else {
getApp().start(getServerConfig().getBindAddress(), getServerConfig().getBindPort());
}
// 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, _) -> {
e.printStackTrace();
});
// 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("/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
// https://nova-static.stellasora.global/
getApp().get("/meta/serverlist.html", new MetaServerlistHandler(this));
getApp().get("/meta/win.html", new MetaWinHandler());
}
private void addGameServerRoutes() {
getApp().post("/agent-zone-1/", new AgentZoneHandler());
}
private void notFoundHandler(Context ctx) {
ctx.status(404);
ctx.contentType(ContentType.APPLICATION_JSON);
ctx.result("{}");
}
}
@@ -0,0 +1,17 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.none)
public class Handler extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
// Template
return null;
}
}
@@ -0,0 +1,19 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.Public.Achievements;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.achievement_info_req)
public class HandlerAchievementInfoReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
var rsp = Achievements.newInstance();
return this.encodeMsg(NetMsgId.achievement_info_succeed_ack, rsp);
}
}
@@ -0,0 +1,27 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.ActivityDetail.ActivityMsg;
import emu.nebula.proto.ActivityDetail.ActivityResp;
import emu.nebula.proto.Public.ActivityTrial;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.activity_detail_req)
public class HandlerActivityDetailReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
var rsp = ActivityResp.newInstance();
var activity = ActivityMsg.newInstance()
.setId(700101)
.setTrial(ActivityTrial.newInstance());
rsp.addList(activity);
return this.encodeMsg(NetMsgId.activity_detail_succeed_ack, rsp);
}
}
@@ -0,0 +1,16 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.agent_apply_req)
public class HandlerAgentApplyReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
return this.encodeMsg(NetMsgId.agent_apply_failed_ack);
}
}
@@ -0,0 +1,16 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.battle_pass_info_req)
public class HandlerBattlePassInfoReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
return this.encodeMsg(NetMsgId.battle_pass_info_failed_ack);
}
}
@@ -0,0 +1,33 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.Public.UI32;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.char_advance_req)
public class HandlerCharAdvanceReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
var req = UI32.parseFrom(message);
// Get character
var character = session.getPlayer().getCharacters().getCharacterById(req.getValue());
if (character == null) {
return this.encodeMsg(NetMsgId.char_advance_failed_ack);
}
// Advance character
var change = character.advance();
if (change == null) {
return this.encodeMsg(NetMsgId.char_advance_failed_ack);
}
return this.encodeMsg(NetMsgId.char_advance_succeed_ack, change.toProto());
}
}
@@ -0,0 +1,34 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.CharSkillUpgrade.CharSkillUpgradeReq;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.char_skill_upgrade_req)
public class HandlerCharSkillUpgradeReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
var req = CharSkillUpgradeReq.parseFrom(message);
// Get character
var character = session.getPlayer().getCharacters().getCharacterById(req.getCharId());
if (character == null) {
return this.encodeMsg(NetMsgId.char_skill_upgrade_failed_ack);
}
// Advance character
int index = req.getIndex() - 1; // Lua indexes start at 1
var change = character.upgradeSkill(index);
if (change == null) {
return this.encodeMsg(NetMsgId.char_skill_upgrade_failed_ack);
}
return this.encodeMsg(NetMsgId.char_skill_upgrade_succeed_ack, change.toProto());
}
}
@@ -0,0 +1,43 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.CharUpgrade.CharUpgradeReq;
import emu.nebula.proto.CharUpgrade.CharUpgradeResp;
import emu.nebula.net.HandlerId;
import emu.nebula.game.inventory.ItemParamMap;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.char_upgrade_req)
public class HandlerCharUpgradeReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
// Parse request
var req = CharUpgradeReq.parseFrom(message);
// Get character
var character = session.getPlayer().getCharacters().getCharacterById(req.getCharId());
if (character == null) {
return this.encodeMsg(NetMsgId.char_upgrade_failed_ack);
}
// Upgrade character
var params = ItemParamMap.fromTemplates(req.getItems());
var change = character.upgrade(params);
if (change == null) {
return this.encodeMsg(NetMsgId.char_upgrade_failed_ack);
}
// Create response
var rsp = CharUpgradeResp.newInstance()
.setChange(change.toProto())
.setLevel(character.getLevel())
.setExp(character.getExp());
return this.encodeMsg(NetMsgId.char_upgrade_succeed_ack, rsp);
}
}
@@ -0,0 +1,16 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.client_event_report_req)
public class HandlerClientEventReportReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
return this.encodeMsg(NetMsgId.client_event_report_succeed_ack);
}
}
@@ -0,0 +1,40 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.DiscPromote.DiscPromoteReq;
import emu.nebula.proto.DiscPromote.DiscPromoteResp;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.disc_promote_req)
public class HandlerDiscPromoteReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
// Parse request
var req = DiscPromoteReq.parseFrom(message);
// Get character
var disc = session.getPlayer().getCharacters().getDiscById(req.getId());
if (disc == null) {
return this.encodeMsg(NetMsgId.disc_promote_failed_ack);
}
// Advance character
var change = disc.promote();
if (change == null) {
return this.encodeMsg(NetMsgId.disc_promote_failed_ack);
}
// Build request
var rsp = DiscPromoteResp.newInstance()
.setPhase(disc.getPhase())
.setChange(change.toProto());
return this.encodeMsg(NetMsgId.disc_promote_succeed_ack, rsp);
}
}
@@ -0,0 +1,16 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.disc_read_reward_receive_req)
public class HandlerDiscReadRewardReceiveReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
return this.encodeMsg(NetMsgId.disc_read_reward_receive_failed_ack);
}
}
@@ -0,0 +1,43 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.DiscStrengthen.DiscStrengthenReq;
import emu.nebula.proto.DiscStrengthen.DiscStrengthenResp;
import emu.nebula.net.HandlerId;
import emu.nebula.game.inventory.ItemParamMap;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.disc_strengthen_req)
public class HandlerDiscStrengthenReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
// Parse request
var req = DiscStrengthenReq.parseFrom(message);
// Get character
var disc = session.getPlayer().getCharacters().getDiscById(req.getId());
if (disc == null) {
return this.encodeMsg(NetMsgId.disc_strengthen_failed_ack);
}
// Upgrade character
var params = ItemParamMap.fromItemInfos(req.getItems());
var change = disc.upgrade(params);
if (change == null) {
return this.encodeMsg(NetMsgId.disc_strengthen_failed_ack);
}
// Create response
var rsp = DiscStrengthenResp.newInstance()
.setChange(change.toProto())
.setLevel(disc.getLevel())
.setExp(disc.getExp());
return this.encodeMsg(NetMsgId.disc_strengthen_succeed_ack, rsp);
}
}
@@ -0,0 +1,19 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.FriendListGet.FriendListGetResp;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.friend_list_get_req)
public class HandlerFriendListGetReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
var rsp = FriendListGetResp.newInstance();
return this.encodeMsg(NetMsgId.friend_list_get_succeed_ack, rsp);
}
}
@@ -0,0 +1,21 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.GachaInformation.GachaInformationResp;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.gacha_information_req)
public class HandlerGachaInformationReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
var rsp = GachaInformationResp.newInstance();
// TODO
return this.encodeMsg(NetMsgId.gacha_information_succeed_ack, rsp);
}
}
@@ -0,0 +1,19 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.GachaInformation.GachaInformationResp;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.gacha_newbie_info_req)
public class HandlerGachaNewbieInfoReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
var rsp = GachaInformationResp.newInstance();
return this.encodeMsg(NetMsgId.gacha_newbie_info_succeed_ack, rsp);
}
}
@@ -0,0 +1,21 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.GachaNewbieObtain.GachaNewbieObtainReq;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.gacha_newbie_obtain_req)
public class HandlerGachaNewbieObtainReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
@SuppressWarnings("unused")
var req = GachaNewbieObtainReq.parseFrom(message);
// TODO
return this.encodeMsg(NetMsgId.gacha_newbie_obtain_failed_ack);
}
}
@@ -0,0 +1,41 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.GachaNewbieSpin.GachaNewbieSpinResp;
import emu.nebula.proto.GachaSpin.GachaSpinReq;
import emu.nebula.util.Utils;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import emu.nebula.net.HandlerId;
import emu.nebula.data.GameData;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.gacha_newbie_spin_req)
public class HandlerGachaNewbieSpinReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
@SuppressWarnings("unused")
var req = GachaSpinReq.parseFrom(message);
// Temp
var list = new IntArrayList();
for (var d : GameData.getCharacterDataTable()) {
if (d.getGrade() == 1) {
list.add(d.getId());
}
}
//
var rsp = GachaNewbieSpinResp.newInstance();
for (int i = 0; i < 10; i++) {
int id = Utils.randomElement(list);
rsp.addCards(id);
}
return this.encodeMsg(NetMsgId.gacha_newbie_spin_succeed_ack, rsp);
}
}
@@ -0,0 +1,52 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.GachaSpin.GachaCard;
import emu.nebula.proto.GachaSpin.GachaSpinReq;
import emu.nebula.proto.GachaSpin.GachaSpinResp;
import emu.nebula.proto.Public.ItemTpl;
import emu.nebula.util.Utils;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import emu.nebula.net.HandlerId;
import emu.nebula.Nebula;
import emu.nebula.data.GameData;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.gacha_spin_req)
public class HandlerGachaSpinReq extends NetHandler {
@SuppressWarnings("unused")
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
var req = GachaSpinReq.parseFrom(message);
// Temp
var list = new IntArrayList();
for (var def : GameData.getCharacterDataTable()) {
if (def.getGrade() == 1 && def.isAvailable()) {
list.add(def.getId());
}
}
// Build response
var rsp = GachaSpinResp.newInstance()
.setTime(Nebula.getCurrentTime());
rsp.getMutableChange();
rsp.getMutableNextPackage();
for (int i = 0; i < 10; i++) {
int id = Utils.randomElement(list);
var card = GachaCard.newInstance()
.setCard(ItemTpl.newInstance().setTid(id).setQty(1));
rsp.addCards(card);
}
return this.encodeMsg(NetMsgId.gacha_spin_succeed_ack, rsp);
}
}
@@ -0,0 +1,61 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.Ike.IKEReq;
import emu.nebula.proto.Ike.IKEResp;
import emu.nebula.util.Utils;
import emu.nebula.net.HandlerId;
import emu.nebula.Nebula;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.ike_req)
public class HandlerIkeReq extends NetHandler {
@Override
public boolean requireSession() {
return false;
}
@Override
public boolean requirePlayer() {
return false;
}
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
// Make sure we dont already have a session
if (session != null) {
return this.encodeMsg(NetMsgId.ike_failed_ack);
}
// Parse
var req = IKEReq.parseFrom(message);
// Create session
session = new GameSession();
session.setClientKey(req.getPubKey());
session.generateServerKey();
session.calculateKey();
// Register session to game context
Nebula.getGameContext().generateSessionToken(session);
// Create response
var rsp = IKEResp.newInstance()
.setToken(session.getToken())
.setCipher(1) // 0 = gcm, 1 = chacha20
.setServerTs(Nebula.getCurrentTime())
.setPubKey(session.getServerPublicKey());
// Debug
Nebula.getLogger().info("Client Public: " + Utils.base64Encode(session.getClientPublicKey()));
Nebula.getLogger().info("Server Public: " + Utils.base64Encode(session.getServerPublicKey()));
Nebula.getLogger().info("Server Private: " + Utils.base64Encode(session.getServerPrivateKey()));
Nebula.getLogger().info("Key: " + Utils.base64Encode(session.getKey()));
// Encode and send to client
return this.encodeMsg(NetMsgId.ike_succeed_ack, rsp);
}
}
@@ -0,0 +1,24 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.Public.Mails;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.mail_list_req)
public class HandlerMailListReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
// Build mail list proto
var rsp = Mails.newInstance();
for (var mail : session.getPlayer().getMailbox()) {
rsp.addList(mail.toProto());
}
return this.encodeMsg(NetMsgId.mail_list_succeed_ack, rsp);
}
}
@@ -0,0 +1,35 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.MailPin.MailPinRequest;
import emu.nebula.net.HandlerId;
import emu.nebula.game.mail.GameMail;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.mail_pin_req)
public class HandlerMailPinReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
// Parse request
var req = MailPinRequest.parseFrom(message);
// Pin mail
GameMail mail = session.getPlayer().getMailbox().pinMail(req.getId(), req.getFlag(), req.hasPin());
// Sanity check
if (mail == null) {
return this.encodeMsg(NetMsgId.mail_pin_failed_ack);
}
// Build response
var rsp = MailPinRequest.newInstance()
.setId(mail.getId())
.setFlag(mail.getFlag())
.setPin(mail.isPin());
return this.encodeMsg(NetMsgId.mail_pin_succeed_ack, rsp);
}
}
@@ -0,0 +1,26 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.Public.MailRequest;
import emu.nebula.proto.Public.UI32;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.mail_read_req)
public class HandlerMailReadReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
var req = MailRequest.parseFrom(message);
boolean result = session.getPlayer().getMailbox().readMail(req.getId(), req.getFlag());
if (!result) {
return this.encodeMsg(NetMsgId.mail_read_failed_ack);
}
return this.encodeMsg(NetMsgId.mail_read_succeed_ack, UI32.newInstance().setValue(req.getId()));
}
}
@@ -0,0 +1,36 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.MailRecv.MailRecvResp;
import emu.nebula.proto.Public.MailRequest;
import it.unimi.dsi.fastutil.ints.IntList;
import emu.nebula.net.HandlerId;
import emu.nebula.game.player.PlayerChangeInfo;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.mail_recv_req)
public class HandlerMailRecvReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
// Parse request
var req = MailRequest.parseFrom(message);
// Claim mail
PlayerChangeInfo changes = session.getPlayer().getMailbox().recvMail(session.getPlayer(), req.getId());
// Build response
var rsp = MailRecvResp.newInstance()
.setItems(changes.toProto());
var recvList = (IntList) changes.getExtraData();
for (int id : recvList) {
rsp.addIds(id);
}
return this.encodeMsg(NetMsgId.mail_recv_succeed_ack, rsp);
}
}
@@ -0,0 +1,32 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.MailRemove.MailRemoveResp;
import emu.nebula.proto.Public.MailRequest;
import it.unimi.dsi.fastutil.ints.IntList;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.mail_remove_req)
public class HandlerMailRemoveReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
// Parse request
var req = MailRequest.parseFrom(message);
// Claim mail
IntList removed = session.getPlayer().getMailbox().removeMail(session.getPlayer(), req.getId());
// Build response
var rsp = MailRemoveResp.newInstance();
for (int id : removed) {
rsp.addIds(id);
}
return this.encodeMsg(NetMsgId.mail_remove_succeed_ack, rsp);
}
}
@@ -0,0 +1,29 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.MallGemListOuterClass.GemInfo;
import emu.nebula.proto.MallGemListOuterClass.MallGemList;
import emu.nebula.net.HandlerId;
import emu.nebula.data.GameData;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.mall_gem_list_req)
public class HandlerMallGemListReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
var rsp = MallGemList.newInstance();
for (var data : GameData.getMallGemDataTable()) {
var info = GemInfo.newInstance()
.setId(data.getIdString())
.setMaiden(true);
rsp.addList(info);
}
return this.encodeMsg(NetMsgId.mall_gem_list_succeed_ack, rsp);
}
}
@@ -0,0 +1,29 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.MallMonthlycardList.MallMonthlyCardList;
import emu.nebula.proto.MallMonthlycardList.MonthlyCardInfo;
import emu.nebula.net.HandlerId;
import emu.nebula.data.GameData;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.mall_monthlyCard_list_req)
public class HandlerMallMonthlyCardListReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
var rsp = MallMonthlyCardList.newInstance();
for (var data : GameData.getMallMonthlyCardDataTable()) {
var info = MonthlyCardInfo.newInstance()
.setId(data.getIdString())
.setRemaining(9);
rsp.addList(info);
}
return this.encodeMsg(NetMsgId.mall_monthlyCard_list_succeed_ack, rsp);
}
}
@@ -0,0 +1,29 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.MallPackageListOuterClass.MallPackageList;
import emu.nebula.proto.MallPackageListOuterClass.PackageInfo;
import emu.nebula.net.HandlerId;
import emu.nebula.data.GameData;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.mall_package_list_req)
public class HandlerMallPackageListReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
var rsp = MallPackageList.newInstance();
for (var data : GameData.getMallPackageDataTable()) {
var info = PackageInfo.newInstance()
.setId(data.getIdString())
.setStock(data.getStock());
rsp.addList(info);
}
return this.encodeMsg(NetMsgId.mall_package_list_succeed_ack, rsp);
}
}
@@ -0,0 +1,40 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.MallShopList.MallShopProductList;
import emu.nebula.proto.MallShopList.ProductInfo;
import emu.nebula.net.HandlerId;
import java.util.concurrent.TimeUnit;
import emu.nebula.Nebula;
import emu.nebula.data.GameData;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.mall_shop_list_req)
public class HandlerMallShopListReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
var rsp = MallShopProductList.newInstance();
long refreshTime = Nebula.getCurrentTime() + TimeUnit.DAYS.toSeconds(30);
for (var data : GameData.getMallShopDataTable()) {
if (data.getStock() <= 0) {
continue;
}
var info = ProductInfo.newInstance()
.setId(data.getIdString())
.setStock(data.getStock())
.setRefreshTime(refreshTime);
rsp.addList(info);
}
return this.encodeMsg(NetMsgId.mall_shop_list_succeed_ack, rsp);
}
}
@@ -0,0 +1,16 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.phone_contacts_info_req)
public class HandlerPhoneContactsInfoReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
return this.encodeMsg(NetMsgId.phone_contacts_info_succeed_ack);
}
}
@@ -0,0 +1,26 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.player_data_req)
public class HandlerPlayerDataReq extends NetHandler {
public boolean requirePlayer() {
return false;
}
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
// Check if player has been created yet
if (session.getPlayer() == null) {
return this.encodeMsg(NetMsgId.player_new_notify);
}
// Encode player data
return this.encodeMsg(NetMsgId.player_data_succeed_ack, session.getPlayer().toProto());
}
}
@@ -0,0 +1,21 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.PlayerFormation.PlayerFormationReq;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.player_formation_req)
public class HandlerPlayerFormationReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
var req = PlayerFormationReq.parseFrom(message);
boolean success = session.getPlayer().getFormations().updateFormation(req.getFormation());
return this.encodeMsg(success ? NetMsgId.player_formation_succeed_ack : NetMsgId.player_formation_failed_ack);
}
}
@@ -0,0 +1,18 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.player_gender_edit_req)
public class HandlerPlayerGenderEditReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
session.getPlayer().editGender();
return this.encodeMsg(NetMsgId.player_gender_edit_succeed_ack);
}
}
@@ -0,0 +1,19 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.PlayerHeadInfo.PlayerHeadIconInfoResp;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.player_head_icon_info_req)
public class HandlerPlayerHeadIconInfoReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
var rsp = PlayerHeadIconInfoResp.newInstance();
return this.encodeMsg(NetMsgId.player_head_icon_info_succeed_ack, rsp);
}
}
@@ -0,0 +1,22 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.Public.NewbieInfo;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.player_learn_req)
public class HandlerPlayerLearnReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
var req = NewbieInfo.parseFrom(message);
// TODO set newbie info
session.getPlayer().setNewbieInfo(req.getGroupId(), req.getStepId());
return this.encodeMsg(NetMsgId.player_learn_succeed_ack);
}
}
@@ -0,0 +1,42 @@
package emu.nebula.server.handlers;
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;
@HandlerId(NetMsgId.player_login_req)
public class HandlerPlayerLoginReq extends NetHandler {
public boolean requirePlayer() {
return false;
}
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
// Parse request
var req = LoginReq.parseFrom(message);
var loginToken = req.getOfficialOverseas().getToken();
// Login
boolean result = session.login(loginToken);
if (!result) {
return this.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 this.encodeMsg(NetMsgId.player_login_succeed_ack, rsp);
}
}
@@ -0,0 +1,16 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.player_music_set_req)
public class HandlerPlayerMusicSetReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
return this.encodeMsg(NetMsgId.player_music_set_failed_ack);
}
}
@@ -0,0 +1,21 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.PlayerNameEdit.PlayerNameEditReq;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.player_name_edit_req)
public class HandlerPlayerNameEditReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
var req = PlayerNameEditReq.parseFrom(message);
boolean success = session.getPlayer().editName(req.getName());
return this.encodeMsg(success ? NetMsgId.player_name_edit_succeed_ack : NetMsgId.player_name_edit_failed_ack);
}
}
@@ -0,0 +1,21 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.PlayerPing.Pong;
import emu.nebula.net.HandlerId;
import emu.nebula.Nebula;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.player_ping_req)
public class HandlerPlayerPingReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
var rsp = Pong.newInstance()
.setServerTs(Nebula.getCurrentTime());
return this.encodeMsg(NetMsgId.player_ping_succeed_ack, rsp);
}
}
@@ -0,0 +1,39 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.PlayerRegOuterClass.PlayerReg;
import emu.nebula.net.HandlerId;
import emu.nebula.Nebula;
import emu.nebula.game.player.Player;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.player_reg_req)
public class HandlerPlayerRegReq extends NetHandler {
public boolean requirePlayer() {
return false;
}
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
// Parse request
var req = PlayerReg.parseFrom(message);
// Sanity
if (req.getNickname() == null || req.getNickname().isEmpty()) {
return this.encodeMsg(NetMsgId.player_reg_failed_ack);
}
// Create player
Player player = Nebula.getGameContext().getPlayerModule().createPlayer(session, req.getNickname(), req.getGender());
if (player == null) {
return this.encodeMsg(NetMsgId.player_reg_failed_ack);
}
// Encode player data
return this.encodeMsg(NetMsgId.player_data_succeed_ack, session.getPlayer().toProto());
}
}
@@ -0,0 +1,35 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.StarTowerApply.StarTowerApplyReq;
import emu.nebula.proto.StarTowerApply.StarTowerApplyResp;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.star_tower_apply_req)
public class HandlerStarTowerApplyReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
// Parse req
var req = StarTowerApplyReq.parseFrom(message);
// Apply to create a star tower instance
var instance = session.getPlayer().getStarTowerManager().apply(req);
if (instance == null) {
return this.encodeMsg(NetMsgId.star_tower_apply_failed_ack);
}
// Create response
var rsp = StarTowerApplyResp.newInstance()
.setLastId(req.getId())
.setInfo(instance.toProto());
rsp.getMutableChange();
return this.encodeMsg(NetMsgId.star_tower_apply_succeed_ack, rsp);
}
}
@@ -0,0 +1,16 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.star_tower_build_brief_list_get_req)
public class HandlerStarTowerBuildBriefListGetReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
return this.encodeMsg(NetMsgId.star_tower_build_brief_list_get_succeed_ack);
}
}
@@ -0,0 +1,31 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.StarTowerInteract.StarTowerInteractReq;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.star_tower_interact_req)
public class HandlerStarTowerInteractReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
// Get star tower instance
var instance = session.getPlayer().getStarTowerManager().getInstance();
if (instance == null) {
return this.encodeMsg(NetMsgId.star_tower_interact_failed_ack);
}
// Parse request
var req = StarTowerInteractReq.parseFrom(message);
// Handle interaction
var rsp = instance.handleInteract(req);
// Template
return this.encodeMsg(NetMsgId.star_tower_interact_succeed_ack, rsp);
}
}
@@ -0,0 +1,16 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.tower_growth_detail_req)
public class HandlerTowerGrowthDetailReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
return this.encodeMsg(NetMsgId.tower_growth_detail_succeed_ack);
}
}
@@ -0,0 +1,212 @@
package emu.nebula.server.routes;
import java.util.Set;
import org.reflections.Reflections;
import emu.nebula.Nebula;
import emu.nebula.game.GameContext;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgIdUtils;
import emu.nebula.net.GameSession;
import emu.nebula.net.HandlerId;
import emu.nebula.util.AeadHelper;
import emu.nebula.util.Utils;
import io.javalin.http.Context;
import io.javalin.http.Handler;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import lombok.Getter;
@Getter
public class AgentZoneHandler implements Handler {
private final Int2ObjectMap<NetHandler> handlers;
private final static byte[] EMPTY_BYTES = new byte[0];
public AgentZoneHandler() {
this.handlers = new Int2ObjectOpenHashMap<>();
this.registerHandlers();
}
protected GameContext getGameContext() {
return Nebula.getGameContext();
}
@Override
public void handle(Context ctx) throws Exception {
// Setup session
GameSession session = null;
byte[] sessionKey = AeadHelper.serverGarbleKey;
boolean hasKey3 = false;
// Get token
String token = ctx.header("X-Token");
// Set headers
ctx.res().setHeader("Server", "agent");
// Check if we have a token
if (token != null) {
// Get session
session = getGameContext().getSessionByToken(token);
// Uh oh - session not found
if (session == null || session.getKey() == null) {
ctx.status(500);
ctx.result("");
return;
}
// Set key
sessionKey = session.getKey();
hasKey3 = true;
}
// Parse request
byte[] data = null;
int msgId = 0;
try {
// Get message
byte[] message = ctx.bodyAsBytes();
int offset = 0;
// Sanity for malformed packets
if (message.length <= 12) {
ctx.status(500);
ctx.result("");
return;
}
// Decrypt message
if (hasKey3) {
message = AeadHelper.decryptChaCha(message, sessionKey);
offset = 10;
} else {
message = AeadHelper.decryptBasic(message, sessionKey);
message = AeadHelper.decryptGCM(message, sessionKey);
}
// Get message id
msgId = (message[offset++] << 8) | (message[offset++] & 0xff);
// Set data
data = new byte[message.length - offset];
System.arraycopy(message, offset, data, 0, data.length);
// Log
if (Nebula.getConfig().getLogOptions().packets) {
this.logRecv(msgId, data);
}
} catch (Exception e) {
// Decrypt error
e.printStackTrace();
ctx.status(500);
ctx.result("");
return;
}
// Update last active time for session
if (session != null) {
session.updateLastActiveTime();
}
// Handle packet
NetHandler handler = this.handlers.get(msgId);
byte[] result = null;
try {
if (handler == null) {
Nebula.getLogger().warn("Unhandled request: " + msgId);
return;
}
// Check handler requirements
if (session == null) {
if (handler.requireSession()) {
return;
}
} else if (session.getPlayer() == null && handler.requirePlayer()) {
return;
}
// Handle data
result = handler.handle(session, data);
} catch (Exception e) {
// Handler error
e.printStackTrace();
} finally {
// Send result
if (result == null) {
ctx.status(500);
ctx.result(EMPTY_BYTES);
return;
}
// Log
if (Nebula.getConfig().getLogOptions().packets) {
this.logSend(result);
}
// Encrypt
if (hasKey3) {
result = AeadHelper.encryptChaCha(result, sessionKey);
} else {
result = AeadHelper.encryptGCM(result, sessionKey);
result = AeadHelper.encryptBasic(result, sessionKey);
}
// Send to client
ctx.status(200);
ctx.result(result);
ctx.res().setHeader("Content-Type", null);
}
}
// Loggers
private void logRecv(int msgId, byte[] data) {
Nebula.getLogger().info("RECV: " + NetMsgIdUtils.getMsgIdName(msgId) + " (" + msgId + ")");
System.out.println(Utils.bytesToHex(data));
}
private void logSend(byte[] data) {
int sendMsgId = (data[0] << 8) | (data[1] & 0xff);
Nebula.getLogger().info("SEND: " + NetMsgIdUtils.getMsgIdName(sendMsgId) + " (" + sendMsgId + ")");
System.out.println(Utils.bytesToHex(data, 2));
}
// Register handlers
private void registerHandlers() {
// Setup handlers
Reflections reflections = new Reflections(Nebula.class.getPackageName());
Set<Class<?>> handlers = reflections.getTypesAnnotatedWith(HandlerId.class);
for (Class<?> cls : handlers) {
// Make sure class is a handler
if (!NetHandler.class.isAssignableFrom(cls)) {
continue;
}
try {
NetHandler handler = (NetHandler) cls.getConstructor().newInstance();
HandlerId def = cls.getAnnotation(HandlerId.class);
int opcode = def.value();
if (opcode != 0) {
// Put in handler map
this.handlers.put(opcode, handler);
}
} catch (Exception e) {
e.printStackTrace();
}
}
// Log registration
Nebula.getLogger().info("Registered " + handlers.size() + " handlers");
}
}
@@ -0,0 +1,30 @@
package emu.nebula.server.routes;
import org.jetbrains.annotations.NotNull;
import emu.nebula.server.HttpServer;
import io.javalin.http.ContentType;
import io.javalin.http.Context;
import io.javalin.http.Handler;
import lombok.Getter;
@Getter
public class CommonConfigHandler implements Handler {
private HttpServer server;
private String json;
public CommonConfigHandler(HttpServer server) {
this.server = server;
this.json = "{\"Code\":200,\"Data\":{\"AppConfig\":{\"ACCOUNT_RETRIEVAL\":{\"FIRST_LOGIN_POPUP\":false,\"LOGIN_POPUP\":false,\"PAGE_URL\":\"\"},\"AGREEMENT_POPUP_TYPE\":\"Browser\",\"APPLE_CURRENCY_BLOCK_LIST\":null,\"APPLE_TYPE_KEY\":\"apple_hk\",\"APP_CLIENT_LANG\":[\"en\"],\"APP_DEBUG\":0,\"APP_GL\":\"en\",\"BIND_METHOD\":[\"google\",\"apple_hk\",\"facebook\"],\"CAPTCHA_ENABLED\":false,\"CLIENT_LOG_REPORTING\":{\"ENABLE\":true},\"CREDIT_INVESTIGATION\":\"0.0\",\"DESTROY_USER_DAYS\":15,\"DESTROY_USER_ENABLE\":1,\"DETECTION_ADDRESS\":{\"AUTO\":{\"DNS\":[\"${url}\",\"${url}\",\"${url}/meta/serverlist.html\"],\"HTTP\":[\"${url}\",\"${url}\",\"${url}\"],\"MTR\":[\"${url}\",\"${url}\",\"${url}/meta/serverlist.html\"],\"PING\":[\"${url}\",\"${url}\",\"${url}/meta/serverlist.html\"],\"TCP\":[\"${url}\",\"${url}\",\"${url}/meta/serverlist.html\"]},\"ENABLE\":true,\"ENABLE_MANUAL\":true,\"INTERNET\":\"https://www.google.com\",\"INTERNET_ADDRESS\":\"https://www.google.com\",\"NETWORK_ENDPORINT\":\"\",\"NETWORK_PROJECT\":\"\",\"NETWORK_SECRET_KEY\":\"\"},\"ENABLE_AGREEMENT\":true,\"ENABLE_MULTI_LANG_AGREEMENT\":false,\"ENABLE_TEXT_REVIEW\":true,\"ERROR_CODE\":\"4.4\",\"FILE_DOMAIN\":\"\",\"GEETEST_ENABLE\":false,\"GEETEST_ID\":\"\",\"GOOGLE_ANALYTICS_MEASUREMENT_ID\":\"\",\"MIGRATE_POPUP\":true,\"NICKNAME_REG\":\"^[A-Za-z0-9]{2,20}$\",\"POPUP\":{\"Data\":[{\"Lang\":\"ja\",\"Text\":\"YostarIDを作成\"},{\"Lang\":\"en\",\"Text\":\"CreateaYostaraccount\"},{\"Lang\":\"kr\",\"Text\":\"YOSTAR계정가입하기\"},{\"Lang\":\"fr\",\"Text\":\"CréezvotrecompteYostar\"},{\"Lang\":\"de\",\"Text\":\"EinenYostar-Accounterstellen\"}],\"Enable\":true},\"PRIVACY_AGREEMENT\":\"0.1\",\"RECHARGE_LIMIT\":{\"Enable\":false,\"IsOneLimit\":false,\"Items\":[],\"OneLimitAmount\":0},\"SHARE\":{\"CaptureScreen\":{\"AutoCloseDelay\":0,\"Enabled\":false},\"Facebook\":{\"AppID\":\"\",\"Enabled\":false},\"Instagram\":{\"Enabled\":false},\"Kakao\":{\"AppKey\":\"\",\"Enabled\":false},\"Naver\":{\"Enabled\":false},\"Twitter\":{\"Enabled\":false}},\"SLS\":{\"ACCESS_KEY_ID\":\"7b5d0ffd0943f26704fc547a871c68b1b5d56b5c9caeb354205b81f445d7af59\",\"ACCESS_KEY_SECRET\":\"4a5e9cc8a50819290c9bfa1fedc79da7c50e85189a05eb462a3d28a7688eabb0\",\"ENABLE\":false},\"SURVEY_POPUP_TYPE\":\"Browser\",\"UDATA\":{\"Enable\":false,\"URL\":\"${url}\"},\"USER_AGREEMENT\":\"0.1\",\"YOSTAR_PREFIX\":\"yoyo\"},\"EuropeUnion\":false,\"StoreConfig\":{\"ADJUST_APPID\":\"\",\"ADJUST_CHARGEEVENTTOKEN\":\"\",\"ADJUST_ENABLED\":0,\"ADJUST_EVENTTOKENS\":null,\"ADJUST_ISDEBUG\":0,\"AIRWALLEX_ENABLED\":false,\"AI_HELP\":{\"AihelpAppID\":\"yostar1_platform_2db52a57068b1ee3fe3652c8b53d581b\",\"AihelpAppKey\":\"YOSTAR1_app_bc226f4419a7447c9de95711f8a2d3d9\",\"AihelpDomain\":\"yostar1.aihelp.net\",\"CustomerServiceURL\":\"\",\"CustomerWay\":1,\"DisplayType\":\"Browser\",\"Enable\":1,\"Mode\":\"robot\"},\"APPLEID\":\"\",\"CODA_ENABLED\":false,\"ENABLED_PAY\":{\"AIRWALLEX_ENABLED\":false,\"CODA_ENABLED\":false,\"GMOAlipay\":false,\"GMOAu\":false,\"GMOCreditcard\":false,\"GMOCvs\":false,\"GMODocomo\":false,\"GMOPaypal\":false,\"GMOPaypay\":false,\"GMOSoftbank\":false,\"MYCARD_ENABLED\":false,\"PAYPAL_ENABLED\":true,\"RAZER_ENABLED\":false,\"STEAM_ENABLED\":false,\"STRIPE_ENABLED\":true,\"TOSS_ENABLED\":false,\"WEBMONEY_ENABLED\":false},\"FACEBOOK_APPID\":\"\",\"FACEBOOK_CLIENT_TOKEN\":\"\",\"FACEBOOK_SECRET\":\"\",\"FIREBASE_ENABLED\":0,\"GMO_CC_JS\":\"https://\",\"GMO_CC_KEY\":\"\",\"GMO_CC_SHOPID\":\"\",\"GMO_PAY_CHANNEL\":{\"GMOAlipay\":false,\"GMOAu\":false,\"GMOCreditcard\":false,\"GMOCvs\":false,\"GMODocomo\":false,\"GMOPaypal\":false,\"GMOPaypay\":false,\"GMOSoftbank\":false},\"GMO_PAY_ENABLED\":false,\"GOOGLE_CLIENT_ID\":\"\",\"GOOGLE_CLIENT_SECRET\":\"\",\"GUEST_CREATE_METHOD\":0,\"GUIDE_POPUP\":{\"DATA\":null,\"ENABLE\":0},\"LOGIN\":{\"DEFAULT\":\"yostar\",\"ICON_SIZE\":\"big\",\"SORT\":[\"google\",\"apple\",\"device\"]},\"MYCARD_ENABLED\":false,\"ONE_STORE_LICENSE_KEY\":\"\",\"PAYPAL_ENABLED\":false,\"RAZER_ENABLED\":false,\"REMOTE_CONFIG\":[],\"SAMSUNG_SANDBOX_MODE\":false,\"STEAM_APPID\":\"\",\"STEAM_ENABLED\":false,\"STEAM_PAY_APPID\":\"\",\"STRIPE_ENABLED\":false,\"TOSS_ENABLED\":false,\"TWITTER_KEY\":\"\",\"TWITTER_SECRET\":\"\",\"WEBMONEY_ENABLED\":false}},\"Msg\":\"OK\"}";
String address = server.getServerConfig().getDisplayAddress();
this.json = this.json.replaceAll("\\$\\{url}", address);
}
@Override
public void handle(@NotNull Context ctx) throws Exception {
ctx.contentType(ContentType.APPLICATION_JSON);
ctx.result(this.json);
}
}
@@ -0,0 +1,83 @@
package emu.nebula.server.routes;
import org.jetbrains.annotations.NotNull;
import emu.nebula.Nebula;
import emu.nebula.game.account.Account;
import emu.nebula.game.account.AccountHelper;
import emu.nebula.util.JsonUtils;
import io.javalin.http.ContentType;
import io.javalin.http.Context;
import io.javalin.http.Handler;
import lombok.Getter;
@Getter
public class GetAuthHandler implements Handler {
@Override
public void handle(@NotNull Context ctx) throws Exception {
// Parse request
var req = JsonUtils.decode(ctx.body(), GetAuthRequestJson.class);
if (req == null || req.Account == null || req.Account.isEmpty() || req.Code == null) {
ctx.contentType(ContentType.APPLICATION_JSON);
ctx.result("{\"Code\":100600,\"Data\":{},\"Msg\":\"Error\"}"); // PARAM_IS_EMPTY
return;
}
// Get account
Account account = AccountHelper.getAccountByEmail(req.Account);
if (account == null) {
// Create an account if were allowed to
if (Nebula.getConfig().getServerOptions().isAutoCreateAccount()) {
account = AccountHelper.createAccount(req.Account, null, 0);
}
} else {
// Check passcode sent by email
if (!account.verifyCode(req.Code)) {
account = null;
}
}
// Sanity
if (account == null) {
ctx.contentType(ContentType.APPLICATION_JSON);
ctx.result("{\"Code\":100403,\"Data\":{},\"Msg\":\"Error\"}"); // TOKEN_AUTH_FAILED
return;
}
// Build request
var response = new GetAuthResponseJson();
response.Code = 200;
response.Msg = "OK";
response.Data = new GetAuthResponseJson.GetAuthDataJson();
response.Data.UID = account.getEmail();
response.Data.Token = account.generateLoginToken();
response.Data.Account = account.getEmail();
// Result
ctx.contentType(ContentType.APPLICATION_JSON);
ctx.result(JsonUtils.encode(response));
}
private static class GetAuthRequestJson {
public String Account;
public String Code;
}
@SuppressWarnings("unused")
private static class GetAuthResponseJson {
public int Code;
public GetAuthDataJson Data;
public String Msg;
private static class GetAuthDataJson {
public String UID;
public String Token;
public String Account;
}
}
}
@@ -0,0 +1,22 @@
package emu.nebula.server.routes;
import org.jetbrains.annotations.NotNull;
import io.javalin.http.ContentType;
import io.javalin.http.Context;
import io.javalin.http.Handler;
public class HttpJsonResponse implements Handler {
private final String json;
public HttpJsonResponse(String jsonString) {
this.json = jsonString;
}
@Override
public void handle(@NotNull Context ctx) throws Exception {
ctx.status(200);
ctx.contentType(ContentType.APPLICATION_JSON);
ctx.result(json);
}
}
@@ -0,0 +1,60 @@
package emu.nebula.server.routes;
import org.jetbrains.annotations.NotNull;
import emu.nebula.proto.Pb.ServerAgent;
import emu.nebula.proto.Pb.ServerListMeta;
import emu.nebula.server.HttpServer;
import emu.nebula.util.AeadHelper;
import io.javalin.http.ContentType;
import io.javalin.http.Context;
import io.javalin.http.Handler;
import lombok.AccessLevel;
import lombok.Getter;
@Getter(AccessLevel.PRIVATE)
public class MetaServerlistHandler implements Handler {
private HttpServer server;
private ServerListMeta list;
private byte[] proto;
public MetaServerlistHandler(HttpServer server) {
this.server = server;
// Create server list
this.list = ServerListMeta.newInstance()
.setVersion(22)
.setReportEndpoint(server.getServerConfig().getDisplayAddress() + "/report");
var agent = ServerAgent.newInstance()
.setName("Nebula") // TODO allow change in config
.setAddr(server.getServerConfig().getDisplayAddress() + "/agent-zone-1/")
.setStatus(1)
.setZone(1);
this.list.addAgent(agent);
var agent2 = ServerAgent.newInstance()
.setName("Test") // TODO allow change in config
.setAddr(server.getServerConfig().getDisplayAddress() + "/agent-zone-1/")
.setStatus(1)
.setZone(1);
this.list.addAgent(agent2);
// Cache proto
this.proto = list.toByteArray();
}
@Override
public void handle(@NotNull Context ctx) throws Exception {
// Result
try {
ctx.contentType(ContentType.APPLICATION_OCTET_STREAM);
ctx.result(AeadHelper.encryptCBC(this.getProto()));
} catch (Exception e) {
e.printStackTrace();
}
}
}
@@ -0,0 +1,35 @@
package emu.nebula.server.routes;
import org.jetbrains.annotations.NotNull;
import emu.nebula.proto.Pb.ClientDiff;
import emu.nebula.util.AeadHelper;
import io.javalin.http.ContentType;
import io.javalin.http.Context;
import io.javalin.http.Handler;
import lombok.AccessLevel;
import lombok.Getter;
@Getter(AccessLevel.PRIVATE)
public class MetaWinHandler implements Handler {
private ClientDiff list;
private byte[] proto;
public MetaWinHandler() {
// Create client diff
this.list = ClientDiff.newInstance();
// TODO load from json or something
// Cache proto
this.proto = list.toByteArray();
}
@Override
public void handle(@NotNull Context ctx) throws Exception {
// Result
ctx.contentType(ContentType.APPLICATION_OCTET_STREAM);
ctx.result(AeadHelper.encryptCBC(this.getProto()));
}
}
@@ -0,0 +1,51 @@
package emu.nebula.server.routes;
import java.util.List;
public class UserLoginEntity {
public int Code;
public UserDetailJson Data;
public String Msg;
public static class UserDetailJson {
public long AgeVerifyMethod;
public Object Destroy;
public boolean IsTestAccount;
public List<UserKeyJson> Keys;
public long ServerNowAt;
public UserInfoJson UserInfo;
public LoginYostarJson Yostar;
public Object YostarDestroy;
}
public static class UserKeyJson {
public String ID;
public String Type;
public String Key;
public String NickName;
public long CreatedAt;
}
public static class UserInfoJson {
public String ID;
public int UID2;
public String PID;
public String Token;
public String Birthday;
public String RegChannel;
public String TransCode;
public int State;
public String DeviceID;
public long CreatedAt;
}
public static class LoginYostarJson {
public String ID;
public String Country;
public String Nickname;
public String Picture;
public int State;
public int AgreeAd;
public long CreatedAt;
}
}
@@ -0,0 +1,127 @@
package emu.nebula.server.routes;
import java.util.ArrayList;
import org.jetbrains.annotations.NotNull;
import emu.nebula.game.account.Account;
import emu.nebula.game.account.AccountHelper;
import emu.nebula.server.routes.UserLoginEntity.LoginYostarJson;
import emu.nebula.server.routes.UserLoginEntity.UserDetailJson;
import emu.nebula.server.routes.UserLoginEntity.UserInfoJson;
import emu.nebula.server.routes.UserLoginEntity.UserKeyJson;
import emu.nebula.util.JsonUtils;
import io.javalin.http.ContentType;
import io.javalin.http.Context;
import io.javalin.http.Handler;
import lombok.Getter;
@Getter
public class UserLoginHandler implements Handler {
@Override
public void handle(@NotNull Context ctx) throws Exception {
// Get account from header first
Account account = this.getAccountFromHeader(ctx);
// Check req body for account details
if (account == null) {
account = this.getAccountFromBody(ctx);
}
// Check
if (account == null) {
ctx.contentType(ContentType.APPLICATION_JSON);
ctx.result("{\"Code\":100403,\"Data\":{},\"Msg\":\"Error\"}"); // TOKEN_AUTH_FAILED
return;
}
// Create response
var response = new UserLoginEntity();
response.Code = 200;
response.Msg = "OK";
response.Data = new UserDetailJson();
response.Data.Keys = new ArrayList<>();
response.Data.UserInfo = new UserInfoJson();
response.Data.Yostar = new LoginYostarJson();
response.Data.UserInfo.ID = account.getUid();
response.Data.UserInfo.UID2 = 0;
response.Data.UserInfo.PID = "NEBULA";
response.Data.UserInfo.Token = account.getLoginToken();
response.Data.UserInfo.Birthday = "";
response.Data.UserInfo.RegChannel = "pc";
response.Data.UserInfo.TransCode = "";
response.Data.UserInfo.State = 1;
response.Data.UserInfo.DeviceID = "";
response.Data.UserInfo.CreatedAt = account.getCreatedAt();
response.Data.Yostar.ID = account.getUid();
response.Data.Yostar.Country = "US";
response.Data.Yostar.Nickname = account.getNickname();
response.Data.Yostar.Picture = account.getPicture();
response.Data.Yostar.State = 1;
response.Data.Yostar.AgreeAd = 0;
response.Data.Yostar.CreatedAt = account.getCreatedAt();
var key = new UserKeyJson();
key.ID = account.getUid();
key.Type = "yostar";
key.Key = account.getEmail();
key.NickName = account.getEmail();
key.CreatedAt = account.getCreatedAt();
response.Data.Keys.add(key);
// Result
ctx.contentType(ContentType.APPLICATION_JSON);
ctx.result(JsonUtils.encode(response, true));
}
protected Account getAccountFromBody(Context ctx) {
// Parse request
var req = JsonUtils.decode(ctx.body(), UserLoginRequestJson.class);
if (req == null || req.OpenID == null || req.Token == null) {
return null;
}
// Get account
return AccountHelper.getAccountByLoginToken(req.Token);
}
protected Account getAccountFromHeader(Context ctx) {
// Parse request
var req = JsonUtils.decode(ctx.header("Authorization"), UserAuthDataJson.class);
if (req == null || req.Head == null || req.Head.Token == null) {
return null;
}
// Get account
return AccountHelper.getAccountByLoginToken(req.Head.Token);
}
@SuppressWarnings("unused")
private static class UserLoginRequestJson {
public String OpenID;
public String Token;
public String Type;
public String UserName;
public String Secret;
public int CheckAccount;
}
@SuppressWarnings("unused")
private static class UserAuthDataJson {
public UserAuthHeadJson Head;
public String Sign;
protected static class UserAuthHeadJson {
public String UID;
public String Token;
}
}
}
@@ -0,0 +1,51 @@
package emu.nebula.server.routes;
import org.jetbrains.annotations.NotNull;
import emu.nebula.game.account.Account;
import emu.nebula.util.JsonUtils;
import io.javalin.http.ContentType;
import io.javalin.http.Context;
import lombok.Getter;
@Getter
public class UserSetDataHandler extends UserLoginHandler {
@Override
public void handle(@NotNull Context ctx) throws Exception {
// Get account from header first
Account account = this.getAccountFromHeader(ctx);
// Check
if (account == null) {
ctx.contentType(ContentType.APPLICATION_JSON);
ctx.result("{\"Code\":100403,\"Data\":{},\"Msg\":\"Error\"}"); // TOKEN_AUTH_FAILED
return;
}
// Parse request
var req = JsonUtils.decode(ctx.body(), UserSetDataReqJson.class);
if (req.Key == null || req.Value == null) {
ctx.contentType(ContentType.APPLICATION_JSON);
ctx.result("{\"Code\":100110,\"Data\":{},\"Msg\":\"Error\"}"); // VALID_FAIL
return;
}
if (req.Key.equals("Nickname")) {
account.setNickname(req.Value);
account.save();
}
// Result
ctx.contentType(ContentType.APPLICATION_JSON);
ctx.result("{\"Code\":200,\"Data\":{},\"Msg\":\"OK\"}");
}
@SuppressWarnings("unused")
private static class UserSetDataReqJson {
public String Key;
public String Value;
}
}