You've already forked Arcturus-Morningstar-Extended
mirror of
https://github.com/duckietm/Arcturus-Morningstar-Extended.git
synced 2026-06-19 15:06:19 +00:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 60e5ba3a6a | |||
| 9fa3fad70c | |||
| 860f61f765 | |||
| c5137bf3dc | |||
| 5150418796 | |||
| 5c71b318fb | |||
| 1cac407c45 | |||
| d85eecd624 | |||
| c50098a945 | |||
| 0224f3f416 | |||
| 03d37650a0 | |||
| f4e5449443 | |||
| 1ebc8314a8 | |||
| 85a60cf591 | |||
| 41d7420251 | |||
| 5dd602ebab |
@@ -1,37 +1,3 @@
|
||||
-- =============================================================================
|
||||
-- Consolidated Database Updates - All-in-One
|
||||
-- =============================================================================
|
||||
-- This file combines ALL individual update scripts from SQL/Database Updates/
|
||||
-- into a single idempotent migration. Every statement is safe to re-run:
|
||||
-- - ALTER TABLE ADD COLUMN IF NOT EXISTS (MariaDB 10.0+)
|
||||
-- - ALTER TABLE CHANGE/MODIFY COLUMN IF EXISTS
|
||||
-- - CREATE TABLE IF NOT EXISTS
|
||||
-- - INSERT IGNORE / ON DUPLICATE KEY UPDATE for settings
|
||||
-- - TRUNCATE + re-insert for reference data (breeding)
|
||||
--
|
||||
-- Run order: This file FIRST, then 001_optimize_gameserver.sql
|
||||
--
|
||||
-- Source files (in applied order):
|
||||
-- 1. UpdateDatabase_Allow_diagonale.sql
|
||||
-- 2. UpdateDatabase_BOT.sql
|
||||
-- 3. UpdateDatabase_Banners.sql
|
||||
-- 4. UpdateDatabase_DanceCMD.sql
|
||||
-- 5. UpdateDatabase_Happiness.sql
|
||||
-- 6. UpdateDatabase_Websocket.sql
|
||||
-- 7. UpdateDatabase_unignorable.sql
|
||||
-- 8. Default_Camera.sql
|
||||
-- 9. 07012026_UpdateDatabase_to_4-0-1.sql
|
||||
-- 10. 09012026_UpdateDatabase_to_4-0-2.sql
|
||||
-- 11. 12012026_Battle Banzai.sql (same as #10, deduplicated)
|
||||
-- 12. 12012026_Breeding Fixes.sql
|
||||
-- 13. 12012026_ChatBubbles.sql
|
||||
-- 14. 16032026_updateall_command.sql
|
||||
-- 15. 17032026_allow_underpass.sql
|
||||
-- 16. 19032026_hotel_timezone.sql
|
||||
-- 17. 21022026_user_prefixes.sql
|
||||
-- 18. 06042026_builders_club_catalog_offers.sql
|
||||
-- =============================================================================
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
SET @OLD_SQL_MODE = @@SQL_MODE;
|
||||
@@ -512,8 +478,13 @@ ALTER TABLE `users_settings`
|
||||
ADD COLUMN IF NOT EXISTS `builders_club_bonus_furni` INT(11) NOT NULL DEFAULT 0 AFTER `hc_gifts_claimed`;
|
||||
|
||||
|
||||
INSERT INTO `permission_definitions` (`permission_key`, `max_value`, `comment`)
|
||||
VALUES ( 'acc_staff_chat', 1, 'Grants access to the in-game Staff Chat group buddy: receives broadcasts from other staff and can broadcast to anyone holding this permission.' )
|
||||
ON DUPLICATE KEY UPDATE `max_value` = VALUES(`max_value`), `comment` = VALUES(`comment`);
|
||||
|
||||
-- =============================================================================
|
||||
-- Done
|
||||
-- Done.
|
||||
-- =============================================================================
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
SET SQL_MODE = @OLD_SQL_MODE;
|
||||
|
||||
File diff suppressed because one or more lines are too long
+1
-1
@@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>com.eu.habbo</groupId>
|
||||
<artifactId>Habbo</artifactId>
|
||||
<version>4.1.6</version>
|
||||
<version>4.1.11</version>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
|
||||
@@ -219,6 +219,10 @@ public class Messenger {
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("Caught SQL exception", e);
|
||||
}
|
||||
|
||||
if (habbo.hasPermission(StaffChatBuddy.PERMISSION_KEY)) {
|
||||
this.friends.putIfAbsent(StaffChatBuddy.BUDDY_ID, new StaffChatBuddy(habbo.getHabboInfo().getId()));
|
||||
}
|
||||
}
|
||||
|
||||
public MessengerBuddy loadFriend(Habbo habbo, int userId) {
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.eu.habbo.habbohotel.messenger;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.commands.CommandHandler;
|
||||
import com.eu.habbo.habbohotel.users.Habbo;
|
||||
import com.eu.habbo.habbohotel.users.HabboGender;
|
||||
import com.eu.habbo.messages.ServerMessage;
|
||||
import com.eu.habbo.messages.outgoing.friends.FriendChatMessageComposer;
|
||||
|
||||
public class StaffChatBuddy extends MessengerBuddy {
|
||||
public static final int BUDDY_ID = -1;
|
||||
public static final String PERMISSION_KEY = "acc_staff_chat";
|
||||
public static final String DISPLAY_NAME = "Staff Chat";
|
||||
public static final String DEFAULT_LOOK = "ADM";
|
||||
|
||||
public StaffChatBuddy(int userOne) {
|
||||
super(BUDDY_ID, DISPLAY_NAME, DEFAULT_LOOK, (short) 0, userOne);
|
||||
this.setOnline(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessageReceived(Habbo from, String message) {
|
||||
if (from == null || message == null || message.isEmpty()) return;
|
||||
// Re-check permission so a staff member who was demoted mid-session
|
||||
// can no longer broadcast to the staff channel.
|
||||
if (!from.hasPermission(PERMISSION_KEY)) return;
|
||||
|
||||
if (message.charAt(0) == ':') {
|
||||
CommandHandler.handleCommand(from.getClient(), message);
|
||||
return;
|
||||
}
|
||||
|
||||
Message chatMessage = new Message(from.getHabboInfo().getId(), BUDDY_ID, message);
|
||||
Emulator.getGameServer().getGameClientManager().sendBroadcastResponse(
|
||||
new FriendChatMessageComposer(chatMessage, BUDDY_ID, from.getHabboInfo().getId()).compose(),
|
||||
PERMISSION_KEY,
|
||||
from.getClient());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(ServerMessage message) {
|
||||
message.appendInt(this.getId());
|
||||
message.appendString(this.getUsername());
|
||||
message.appendInt(this.getGender().equals(HabboGender.M) ? 0 : 1);
|
||||
message.appendBoolean(true); // online
|
||||
message.appendBoolean(false); // not in room
|
||||
message.appendString(this.getLook());
|
||||
message.appendInt(0); // category
|
||||
message.appendString(""); // motto
|
||||
message.appendString(""); // last seen
|
||||
message.appendString(""); // realname
|
||||
message.appendBoolean(true); // offline messaging supported
|
||||
message.appendBoolean(false);
|
||||
message.appendBoolean(false);
|
||||
message.appendShort(0); // relation
|
||||
}
|
||||
}
|
||||
+18
-2
@@ -1,14 +1,30 @@
|
||||
package com.eu.habbo.messages.incoming.users;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.rooms.Room;
|
||||
import com.eu.habbo.habbohotel.users.Habbo;
|
||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||
|
||||
public class ActivateEffectEvent extends MessageHandler {
|
||||
@Override
|
||||
public void handle() throws Exception {
|
||||
int effectId = this.packet.readInt();
|
||||
Habbo habbo = this.client.getHabbo();
|
||||
if (habbo == null) return;
|
||||
|
||||
if (this.client.getHabbo().getInventory().getEffectsComponent().ownsEffect(effectId)) {
|
||||
this.client.getHabbo().getInventory().getEffectsComponent().activateEffect(effectId);
|
||||
if (habbo.getInventory().getEffectsComponent().ownsEffect(effectId)) {
|
||||
habbo.getInventory().getEffectsComponent().activateEffect(effectId);
|
||||
return;
|
||||
}
|
||||
|
||||
int rankId = habbo.getHabboInfo().getRank().getId();
|
||||
if (Emulator.getGameEnvironment().getPermissionsManager().isEffectBlocked(effectId, rankId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Room room = habbo.getHabboInfo().getCurrentRoom();
|
||||
if (room == null || habbo.getHabboInfo().getRiding() != null) return;
|
||||
|
||||
room.giveEffect(habbo, effectId, -1);
|
||||
}
|
||||
}
|
||||
+227
-51
@@ -33,6 +33,7 @@ public class AuthHttpHandler extends ChannelInboundHandlerAdapter {
|
||||
private static final String CHECK_EMAIL_PATH = "/api/auth/check-email";
|
||||
private static final String CHECK_USERNAME_PATH = "/api/auth/check-username";
|
||||
private static final String ROOM_TEMPLATES_PATH = "/api/auth/room-templates";
|
||||
private static final String NEWS_PATH = "/api/auth/news";
|
||||
private static final String REMEMBER_PATH = "/api/auth/remember";
|
||||
private static final String REFRESH_PATH = "/api/auth/refresh";
|
||||
private static final String SERVER_KEY_PATH = "/api/auth/server-key";
|
||||
@@ -57,6 +58,7 @@ public class AuthHttpHandler extends ChannelInboundHandlerAdapter {
|
||||
&& !path.equals(FORGOT_PATH) && !path.equals(LOGOUT_PATH)
|
||||
&& !path.equals(CHECK_EMAIL_PATH) && !path.equals(CHECK_USERNAME_PATH)
|
||||
&& !path.equals(ROOM_TEMPLATES_PATH)
|
||||
&& !path.equals(NEWS_PATH)
|
||||
&& !path.equals(REMEMBER_PATH)
|
||||
&& !path.equals(REFRESH_PATH)
|
||||
&& !path.equals(SERVER_KEY_PATH)
|
||||
@@ -98,6 +100,22 @@ public class AuthHttpHandler extends ChannelInboundHandlerAdapter {
|
||||
return;
|
||||
}
|
||||
|
||||
if (path.equals(NEWS_PATH)) {
|
||||
if (req.method() != HttpMethod.GET && req.method() != HttpMethod.HEAD) {
|
||||
sendJson(ctx, req, HttpResponseStatus.METHOD_NOT_ALLOWED, errorPayload("Use GET."));
|
||||
return;
|
||||
}
|
||||
String ip = resolveClientIp(ctx, req);
|
||||
if (!AuthRateLimiter.tryProbe(ip)) {
|
||||
long secs = AuthRateLimiter.secondsUntilProbeReset(ip);
|
||||
sendJson(ctx, req, HttpResponseStatus.TOO_MANY_REQUESTS,
|
||||
errorPayload("Too many requests. Try again in " + secs + "s."));
|
||||
return;
|
||||
}
|
||||
handleNews(ctx, req);
|
||||
return;
|
||||
}
|
||||
|
||||
if (path.equals(SERVER_KEY_PATH)) {
|
||||
if (req.method() != HttpMethod.GET && req.method() != HttpMethod.HEAD) {
|
||||
sendJson(ctx, req, HttpResponseStatus.METHOD_NOT_ALLOWED, errorPayload("Use GET."));
|
||||
@@ -364,62 +382,82 @@ public class AuthHttpHandler extends ChannelInboundHandlerAdapter {
|
||||
return;
|
||||
}
|
||||
|
||||
try (Connection conn = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement stmt = conn.prepareStatement(
|
||||
"SELECT id, username, password FROM users WHERE username = ? LIMIT 1")) {
|
||||
stmt.setString(1, username);
|
||||
try (ResultSet rs = stmt.executeQuery()) {
|
||||
if (!rs.next()) {
|
||||
LOGGER.info("[auth/login] user not found username='{}' ip={}", username, ip);
|
||||
AuthRateLimiter.recordFailure(ip);
|
||||
sendJson(ctx, req, HttpResponseStatus.UNAUTHORIZED,
|
||||
errorPayload("Invalid Habbo name or password."));
|
||||
try (Connection conn = Emulator.getDatabase().getDataSource().getConnection()) {
|
||||
if (ip != null && !ip.isEmpty()) {
|
||||
BanInfo ipBan = lookupIpBan(conn, ip);
|
||||
if (ipBan != null) {
|
||||
LOGGER.info("[auth/login] ip ban hit ip={} type={} expires={}",
|
||||
ip, ipBan.type, ipBan.expiresAt);
|
||||
sendJson(ctx, req, HttpResponseStatus.FORBIDDEN, bannedPayload(ipBan));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int userId = rs.getInt("id");
|
||||
String stored = rs.getString("password");
|
||||
String storedPreview = stored == null
|
||||
? "<null>"
|
||||
: (stored.isEmpty() ? "<empty>" : stored.substring(0, Math.min(7, stored.length())) + "…(" + stored.length() + " chars)");
|
||||
|
||||
if (stored == null || stored.isEmpty() || !checkPassword(password, stored)) {
|
||||
LOGGER.info("[auth/login] password mismatch for user id={} username='{}' stored='{}'",
|
||||
userId, username, storedPreview);
|
||||
AuthRateLimiter.recordFailure(ip);
|
||||
sendJson(ctx, req, HttpResponseStatus.UNAUTHORIZED,
|
||||
errorPayload("Invalid Habbo name or password."));
|
||||
return;
|
||||
}
|
||||
|
||||
String ssoTicket = mintSsoTicket();
|
||||
|
||||
try (PreparedStatement upd = conn.prepareStatement(
|
||||
"UPDATE users SET auth_ticket = ?, ip_current = ? WHERE id = ? LIMIT 1")) {
|
||||
upd.setString(1, ssoTicket);
|
||||
upd.setString(2, ip == null ? "" : ip);
|
||||
upd.setInt(3, userId);
|
||||
upd.executeUpdate();
|
||||
}
|
||||
|
||||
String rememberToken = null;
|
||||
if (rememberMe) {
|
||||
try {
|
||||
RememberJwtService.RotationResult issued = RememberJwtService.issueForNewFamily(
|
||||
conn, userId, rs.getString("username"), ip);
|
||||
rememberToken = issued.jwt;
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("Failed to issue remember-me JWT for userId=" + userId, e);
|
||||
try (PreparedStatement stmt = conn.prepareStatement(
|
||||
"SELECT id, username, password FROM users WHERE username = ? LIMIT 1")) {
|
||||
stmt.setString(1, username);
|
||||
try (ResultSet rs = stmt.executeQuery()) {
|
||||
if (!rs.next()) {
|
||||
LOGGER.info("[auth/login] user not found username='{}' ip={}", username, ip);
|
||||
AuthRateLimiter.recordFailure(ip);
|
||||
sendJson(ctx, req, HttpResponseStatus.UNAUTHORIZED,
|
||||
errorPayload("Invalid Habbo name or password."));
|
||||
return;
|
||||
}
|
||||
|
||||
int userId = rs.getInt("id");
|
||||
String stored = rs.getString("password");
|
||||
String storedPreview = stored == null
|
||||
? "<null>"
|
||||
: (stored.isEmpty() ? "<empty>" : stored.substring(0, Math.min(7, stored.length())) + "…(" + stored.length() + " chars)");
|
||||
|
||||
if (stored == null || stored.isEmpty() || !checkPassword(password, stored)) {
|
||||
LOGGER.info("[auth/login] password mismatch for user id={} username='{}' stored='{}'",
|
||||
userId, username, storedPreview);
|
||||
AuthRateLimiter.recordFailure(ip);
|
||||
sendJson(ctx, req, HttpResponseStatus.UNAUTHORIZED,
|
||||
errorPayload("Invalid Habbo name or password."));
|
||||
return;
|
||||
}
|
||||
|
||||
BanInfo accountBan = lookupAccountBan(conn, userId);
|
||||
if (accountBan != null) {
|
||||
LOGGER.info("[auth/login] account ban hit userId={} type={} expires={}",
|
||||
userId, accountBan.type, accountBan.expiresAt);
|
||||
AuthRateLimiter.recordSuccess(ip);
|
||||
sendJson(ctx, req, HttpResponseStatus.FORBIDDEN, bannedPayload(accountBan));
|
||||
return;
|
||||
}
|
||||
|
||||
String ssoTicket = mintSsoTicket();
|
||||
|
||||
try (PreparedStatement upd = conn.prepareStatement(
|
||||
"UPDATE users SET auth_ticket = ?, ip_current = ? WHERE id = ? LIMIT 1")) {
|
||||
upd.setString(1, ssoTicket);
|
||||
upd.setString(2, ip == null ? "" : ip);
|
||||
upd.setInt(3, userId);
|
||||
upd.executeUpdate();
|
||||
}
|
||||
|
||||
String rememberToken = null;
|
||||
if (rememberMe) {
|
||||
try {
|
||||
RememberJwtService.RotationResult issued = RememberJwtService.issueForNewFamily(
|
||||
conn, userId, rs.getString("username"), ip);
|
||||
rememberToken = issued.jwt;
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("Failed to issue remember-me JWT for userId=" + userId, e);
|
||||
}
|
||||
}
|
||||
|
||||
AuthRateLimiter.recordSuccess(ip);
|
||||
|
||||
JsonObject ok = new JsonObject();
|
||||
ok.addProperty("ssoTicket", ssoTicket);
|
||||
ok.addProperty("username", rs.getString("username"));
|
||||
if (rememberToken != null) ok.addProperty("rememberToken", rememberToken);
|
||||
sendJson(ctx, req, HttpResponseStatus.OK, ok);
|
||||
}
|
||||
|
||||
AuthRateLimiter.recordSuccess(ip);
|
||||
|
||||
JsonObject ok = new JsonObject();
|
||||
ok.addProperty("ssoTicket", ssoTicket);
|
||||
ok.addProperty("username", rs.getString("username"));
|
||||
if (rememberToken != null) ok.addProperty("rememberToken", rememberToken);
|
||||
sendJson(ctx, req, HttpResponseStatus.OK, ok);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Login query failed for username=" + username, e);
|
||||
@@ -662,6 +700,76 @@ public class AuthHttpHandler extends ChannelInboundHandlerAdapter {
|
||||
sendJson(ctx, req, HttpResponseStatus.OK, res);
|
||||
}
|
||||
|
||||
private static final long NEWS_CACHE_TTL_MS = 30_000L;
|
||||
private static final int NEWS_IMAGE_MAX_BYTES = 512 * 1024;
|
||||
private static volatile NewsCacheEntry NEWS_CACHE = null;
|
||||
|
||||
private static final class NewsCacheEntry {
|
||||
final byte[] jsonBytes;
|
||||
final long expiresAt;
|
||||
NewsCacheEntry(byte[] j, long e) { jsonBytes = j; expiresAt = e; }
|
||||
}
|
||||
|
||||
private void handleNews(ChannelHandlerContext ctx, FullHttpRequest req) {
|
||||
long now = System.currentTimeMillis();
|
||||
NewsCacheEntry cached = NEWS_CACHE;
|
||||
|
||||
if (cached == null || cached.expiresAt < now) {
|
||||
JsonArray items = new JsonArray();
|
||||
int limit = Math.max(1, Math.min(20, Emulator.getConfig().getInt("login.news.limit", 5)));
|
||||
try (Connection conn = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement stmt = conn.prepareStatement(
|
||||
"SELECT id, title, body, image, link_text, link_url " +
|
||||
"FROM ui_news WHERE enabled = 1 " +
|
||||
"ORDER BY sort_order ASC, id DESC LIMIT ?")) {
|
||||
stmt.setInt(1, limit);
|
||||
try (ResultSet rs = stmt.executeQuery()) {
|
||||
while (rs.next()) {
|
||||
int id = rs.getInt("id");
|
||||
JsonObject n = new JsonObject();
|
||||
n.addProperty("id", id);
|
||||
n.addProperty("title", rs.getString("title"));
|
||||
n.addProperty("body", rs.getString("body"));
|
||||
|
||||
String image = rs.getString("image");
|
||||
if (image != null && image.length() > NEWS_IMAGE_MAX_BYTES) {
|
||||
LOGGER.warn("ui_news id={} image is {} bytes (>{}KB cap), omitting in response",
|
||||
id, image.length(), NEWS_IMAGE_MAX_BYTES / 1024);
|
||||
image = null;
|
||||
}
|
||||
n.addProperty("image", image); // gson encodes null as JSON null
|
||||
|
||||
n.addProperty("linkText", rs.getString("link_text"));
|
||||
n.addProperty("linkUrl", rs.getString("link_url"));
|
||||
items.add(n);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("ui_news list failed", e);
|
||||
sendJson(ctx, req, HttpResponseStatus.INTERNAL_SERVER_ERROR, errorPayload("Server error."));
|
||||
return;
|
||||
}
|
||||
|
||||
JsonObject res = new JsonObject();
|
||||
res.add("news", items);
|
||||
byte[] bytes = res.toString().getBytes(StandardCharsets.UTF_8);
|
||||
cached = new NewsCacheEntry(bytes, now + NEWS_CACHE_TTL_MS);
|
||||
NEWS_CACHE = cached;
|
||||
}
|
||||
|
||||
FullHttpResponse response = new DefaultFullHttpResponse(
|
||||
HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
|
||||
Unpooled.wrappedBuffer(cached.jsonBytes));
|
||||
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json; charset=utf-8");
|
||||
response.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, cached.jsonBytes.length);
|
||||
response.headers().set(HttpHeaderNames.CACHE_CONTROL, "public, max-age=30");
|
||||
applyCors(req, response);
|
||||
boolean keepAlive = isKeepAlive(req);
|
||||
if (keepAlive) response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
|
||||
var future = ctx.writeAndFlush(response);
|
||||
if (!keepAlive) future.addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
|
||||
private void handleServerKey(ChannelHandlerContext ctx, FullHttpRequest req) {
|
||||
try {
|
||||
JsonObject ok = new JsonObject();
|
||||
@@ -804,6 +912,74 @@ public class AuthHttpHandler extends ChannelInboundHandlerAdapter {
|
||||
sendJson(ctx, req, HttpResponseStatus.OK, ok);
|
||||
}
|
||||
|
||||
private static final long PERMANENT_BAN_THRESHOLD_SECONDS = 30L * 365L * 24L * 60L * 60L;
|
||||
|
||||
private static final class BanInfo {
|
||||
final String type;
|
||||
final String reason;
|
||||
final int expiresAt;
|
||||
|
||||
BanInfo(String type, String reason, int expiresAt) {
|
||||
this.type = type == null ? "account" : type;
|
||||
this.reason = reason == null ? "" : reason;
|
||||
this.expiresAt = expiresAt;
|
||||
}
|
||||
|
||||
boolean isPermanent() {
|
||||
return (long) expiresAt - Emulator.getIntUnixTimestamp() > PERMANENT_BAN_THRESHOLD_SECONDS;
|
||||
}
|
||||
}
|
||||
|
||||
private static BanInfo lookupAccountBan(Connection conn, int userId) throws SQLException {
|
||||
try (PreparedStatement stmt = conn.prepareStatement(
|
||||
"SELECT ban_expire, ban_reason, type FROM bans " +
|
||||
"WHERE user_id = ? AND ban_expire >= ? AND (type = 'account' OR type = 'super') " +
|
||||
"ORDER BY ban_expire DESC LIMIT 1")) {
|
||||
stmt.setInt(1, userId);
|
||||
stmt.setInt(2, Emulator.getIntUnixTimestamp());
|
||||
try (ResultSet rs = stmt.executeQuery()) {
|
||||
if (rs.next()) {
|
||||
return new BanInfo(rs.getString("type"), rs.getString("ban_reason"), rs.getInt("ban_expire"));
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static BanInfo lookupIpBan(Connection conn, String ip) throws SQLException {
|
||||
try (PreparedStatement stmt = conn.prepareStatement(
|
||||
"SELECT ban_expire, ban_reason, type FROM bans " +
|
||||
"WHERE ip = ? AND ban_expire >= ? AND (type = 'ip' OR type = 'super') " +
|
||||
"ORDER BY ban_expire DESC LIMIT 1")) {
|
||||
stmt.setString(1, ip);
|
||||
stmt.setInt(2, Emulator.getIntUnixTimestamp());
|
||||
try (ResultSet rs = stmt.executeQuery()) {
|
||||
if (rs.next()) {
|
||||
return new BanInfo(rs.getString("type"), rs.getString("ban_reason"), rs.getInt("ban_expire"));
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static JsonObject bannedPayload(BanInfo ban) {
|
||||
boolean permanent = ban.isPermanent();
|
||||
String message = permanent
|
||||
? "Your account has been permanently banned."
|
||||
: "Your account is temporarily banned.";
|
||||
|
||||
JsonObject details = new JsonObject();
|
||||
details.addProperty("type", ban.type);
|
||||
details.addProperty("reason", ban.reason);
|
||||
details.addProperty("permanent", permanent);
|
||||
if (!permanent) details.addProperty("expiresAt", ban.expiresAt);
|
||||
|
||||
JsonObject obj = new JsonObject();
|
||||
obj.addProperty("error", message);
|
||||
obj.add("ban", details);
|
||||
return obj;
|
||||
}
|
||||
|
||||
private static boolean checkPassword(String plain, String stored) {
|
||||
String compatible = stored.startsWith("$2y$") ? "$2a$" + stored.substring(4) : stored;
|
||||
try {
|
||||
|
||||
@@ -39,7 +39,7 @@ public class WsAesDecoder extends MessageToMessageDecoder<ByteBuf> {
|
||||
byte[] plain = WsSessionCrypto.aesGcmDecrypt(key, nonce, ct);
|
||||
out.add(Unpooled.wrappedBuffer(plain));
|
||||
} catch (Exception e) {
|
||||
LOGGER.warn("[ws-crypto] AES-GCM decrypt failed", e);
|
||||
LOGGER.warn("[ws-crypto] AES-GCM decrypt failed ({}), closing channel", e.getClass().getSimpleName());
|
||||
ctx.close();
|
||||
}
|
||||
}
|
||||
|
||||
+15
-2
@@ -3,6 +3,7 @@ package com.eu.habbo.networking.gameserver.crypto;
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.networking.gameserver.GameServerAttributes;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
@@ -118,7 +119,7 @@ public class WsHandshakeHandler extends ChannelInboundHandlerAdapter {
|
||||
|
||||
LOGGER.debug("[ws-crypto] handshake complete for {}", clientAddress(ctx));
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("[ws-crypto] handshake failed from " + clientAddress(ctx), e);
|
||||
LOGGER.warn("[ws-crypto] handshake failed from {} : {}", clientAddress(ctx), friendlyReason(e));
|
||||
ctx.close();
|
||||
} finally {
|
||||
in.release();
|
||||
@@ -131,9 +132,21 @@ public class WsHandshakeHandler extends ChannelInboundHandlerAdapter {
|
||||
return String.valueOf(ctx.channel().remoteAddress());
|
||||
}
|
||||
|
||||
private static String friendlyReason(Throwable t) {
|
||||
if (t == null) return "unknown";
|
||||
String name = t.getClass().getSimpleName();
|
||||
String msg = t.getMessage();
|
||||
return (msg == null || msg.isEmpty()) ? name : name + ": " + msg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
LOGGER.error("[ws-crypto] handshake handler error", cause);
|
||||
if (cause instanceof java.io.IOException) {
|
||||
LOGGER.debug("[ws-crypto] client disconnected during handshake ({}): {}",
|
||||
clientAddress(ctx), friendlyReason(cause));
|
||||
} else {
|
||||
LOGGER.error("[ws-crypto] handshake handler error from " + clientAddress(ctx), cause);
|
||||
}
|
||||
ctx.close();
|
||||
}
|
||||
}
|
||||
|
||||
+1
-3
@@ -2,7 +2,6 @@ package com.eu.habbo.networking.gameserver.decoders;
|
||||
|
||||
import com.eu.habbo.messages.ClientMessage;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
|
||||
@@ -12,8 +11,7 @@ public class GameByteDecoder extends ByteToMessageDecoder {
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
|
||||
short header = in.readShort();
|
||||
ByteBuf body = Unpooled.copiedBuffer(in.readBytes(in.readableBytes()));
|
||||
|
||||
ByteBuf body = in.readBytes(in.readableBytes());
|
||||
out.add(new ClientMessage(header, body));
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Reference in New Issue
Block a user