RECENT NEWS
📢 𝟑𝟎% Discount for all ads only this month ❄️

[Kronos] Removing Forum Login, Fixing Friends Chat and Clan Chat, Password Saving

Eric Gold
power_settings_new
Seen 4 months ago
Steel Warrior (12/15)
Steel Warrior
0
0
0
12 Posts
Posts
0
Warning level
0
Likes
0
Dislikes
Joined: 2022-04-24

As the title says, this is for Removing Forum Login, Fixing Friends Chat and Clan Chat and adding Password Saving.

Lots of people asked for this so I thought I would release.

Do not use the old thread I made about removing forum login and UNDO any changes that you made.
It is recommended that you re-download Kronos if you used that tutorial.

Server must have offline mode set to FALSE

The purpose of this is to use central server (friends/clan chat) without using forum settings.

If you don't have mysql databases set up you must comment out or delete these lines in [io.ruin > Server.java] (kronos-server files)

Code:
            siteDb = new Database(properties.getProperty("database_host"), "kronos", properties.getProperty("database_user"), properties.getProperty("database_password"));
            gameDb = new Database(properties.getProperty("database_host"), "game", properties.getProperty("database_user"), properties.getProperty("database_password"));
            forumDb = new Database(properties.getProperty("database_host"), "community", properties.getProperty("database_user"), properties.getProperty("database_password"));

            DatabaseUtils.connect(new Database[]{gameDb, forumDb, siteDb}, errors -> {
                if (!errors.isEmpty()) {
                    for (Throwable t : errors)
                        logError("Database error", t);
                    System.exit(1);
                }
            });

Step One: Removing Forum Login

REQUIRED FOR FIXING FRIENDS CHAT AND CLAN CHAT

This is for removing forum login so you can use the central server or else you wouldn't be able to use the central server features (Clan/Friends chat).

Go to io.ruin.central.model.world > WorldLogin.java (kronos-central-server)

Replace everything with this:

Code:
package io.ruin.central.model.world;

import com.google.gson.JsonObject;
import io.ruin.api.protocol.Protocol;
import io.ruin.api.protocol.Response;
import io.ruin.api.protocol.login.LoginInfo;
import io.ruin.api.protocol.login.LoginRequest;
import io.ruin.api.utils.*;
import io.ruin.central.Server;
import io.ruin.central.model.Player;
import io.ruin.central.utility.XenforoUtils;
import io.ruin.model.entity.player.PlayerGroup;
import lombok.extern.slf4j.Slf4j;
import org.json.JSONArray;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.concurrent.CompletableFuture;

@Slf4j
public class WorldLogin extends LoginRequest {

    private World world;

    public static String capitalize(String s) {
        s = s.toLowerCase();
        s = s.replaceAll("_", " ");
        for (int i = 0; i < s.length(); i++) {
            if (i == 0) {
                s = String.format("%s%s", Character.toUpperCase(s.charAt(0)), s.substring(1));
            }
            if (!Character.isLetterOrDigit(s.charAt(i))) {
                if (i + 1 < s.length()) {
                    s = String.format("%s%s%s", s.subSequence(0, i+1), Character.toUpperCase(s.charAt(i + 1)), s.substring(i+2));
                }
            }
        }
        return s;
    }

    public WorldLogin(World world, LoginInfo info) {
        super(info);
        this.world = world;

        if (UUIDBan.isUUIDBanned(info.uuid) || IPBans.isIPBanned(info.ipAddress) || MACBan.isMACBanned(info.macAddress)) {
            this.deny(Response.DISABLED_ACCOUNT);
            return;
        }

      /*  CompletableFuture.runAsync(() -> {

                System.err.println(info.name + " attempting to login.");

                JSONObject result = XenforoUtils.attemptLogin(info.name, info.password, info.ipAddress);

                //Should never happen unless website is down
                if (result == null) {
                    this.deny(Response.LOGIN_SERVER_NO_REPLY);
                    return;
                }

                String login_code = result.getString("login_code");
                System.err.println(info.name + " : " + login_code);

                if (login_code.endsWith("could not be found.")) {
                    this.deny(Response.EMAIL_REQUIRED);
                    return;
                }
                switch (login_code) {
                    case "Unregistered account.": {
                        this.deny(Response.UNREGISTERED_ACCOUNT);
                        return;
                    }
                    case "Email required.": {
                        this.deny(Response.EMAIL_REQUIRED);
                        return;
                    }
                    case "Email in use.": {
                        this.deny(Response.EMAIL_IN_USE);
                        return;
                    }
                    case "Username too long.": {
                        this.deny(Response.USERNAME_TOO_LONG);
                        return;
                    }
                    case "Username contained disallowed words.": {
                        this.deny(Response.USERNAME_BAD_WORDS);
                        return;
                    }
                    case "Username contains incorrect characters.": {
                        this.deny(Response.USERNAME_BAD_LETTERS);
                        return;
                    }
                    case "Incorrect password. Please try again.": {
                        this.deny(Response.INVALID_LOGIN);
                        return;
                    }
                    case "Your account has temporarily been locked due to failed login attempts.": {
                        this.deny(Response.LOGIN_LIMIT);
                        return;
                    }
                    case "Two-factor authentication required.": {
                        this.deny(Response.TWO_FACTOR);
                        return;
                    }
                    case "Two-factor authentication limit reached!": {
                        this.deny(Response.LOGIN_LIMIT);
                        return;
                    }
                    case "Two-factor authentication invalid!": {
                        this.deny(Response.TWO_FACTOR_RETRY);
                        return;
                    }
                    case "Banned": {
                        this.deny(Response.DISABLED_ACCOUNT);
                        return;
                    }
                    case "IP Banned": {
                        this.deny(Response.DISABLED_ACCOUNT);
                        return;
                    }
                    case "Username on hold": {
                        this.deny(Response.USERNAME_ON_HOLD);
                        return;
                    }
                    case "Proxy login attempt": {
                        this.deny(Response.PROXY_LOGIN_ATTEMPT);
                        return;
                    }
                }
                try {
                    JSONObject user = result.getJSONObject("user");
                    int userId = user.getInt("user_id");
                    String username = user.getString("username");
                    int primaryGroupId = user.getInt("user_group_id");
                    String secondaryGroupIds = String.valueOf(user.getJSONArray("secondary_group_ids"));
                    //int unreadPms = user.get("conversations_unread").getAsInt();
                    if (Protocol.method360(username) == null) {
                        this.deny(Response.CHANGE_DISPLAY_NAME);
                        return;
                    }
                    ArrayList<Integer> groupIds = new ArrayList<Integer>();
                    groupIds.add(primaryGroupId);
                    for (String id : secondaryGroupIds.replace("[", "").replace("]", "").replace("\"", "").split(",")) {  //["10", "10"]
                        try {
                            groupIds.add(Integer.valueOf(id));
                        } catch (Exception exception) {
                            // empty catch block
                        }
                    }
                    for (int i : groupIds) {
                        if (i == 18) {
                            this.deny(Response.DISABLED_ACCOUNT);
                            return;
                        }
                    }*/
                    try {
                        String username = capitalize(info.name);
                        int index = -1;
                        for(int i = 1; i < 2048; i++) {
                            Player player = Server.getPlayer(i);
                            if(player == null) {
                                index = i;
                                break;
                            }
                        }
                        if(index != -1) {
                            String saved = Player.load(username, world);
                            info.update(index, username, saved, ListUtils.toList(PlayerGroup.ADMINISTRATOR.id), 0);
                            this.success();
                        } else {
                            this.deny(Response.WORLD_FULL);
                        }
                    } catch (Exception e) {
                        Server.logError(e.getMessage());
                        this.deny(Response.ERROR_LOADING_ACCOUNT);
                    }
          //  }
      //  );
    }

    @Override
    public void success() {
        this.world.logins.offer(this.info);
    }

    @Override
    public void deny(Response response) {
        this.world.sendLoginFailed(this.info.name, response);
        super.deny(response);
    }
}

Now go to io.ruin.model.entity.player > PlayerLogin.java

Find this:

Code:
  private void send() {
        if(OfflineMode.loadPlayer(this))
            return;
        CentralClient.sendLogin(info.ipAddress, info.name, info.password, info.email, info.macAddress, info.uuid, info.tfaCode, info.tfaTrust, info.tfaTrustValue & 0xff);
    }

Replace with:

Code:
 private void send() {
        info.name = info.name.replaceAll("_", " ");
        if(OfflineMode.loadPlayer(this))
            return;
        CentralClient.sendLogin(info.ipAddress, info.name, info.password, info.email, info.macAddress, info.uuid, info.tfaCode, info.tfaTrust, info.tfaTrustValue & 0xff);
    }

Now go to io.ruin.central > Server.java

Find:

Code:
    
     public static boolean isOnline(int userId, String name) {
        int limboWorld = Limbo.get(userId);
        if (limboWorld != -1) {
            System.err.println(name + " is in limbo on world " + limboWorld + "!");
            return true;
        }
        for (World world : worlds) {
            if (!world.hasPlayer(userId)) continue;
            System.err.println(name + " is online in world " + world.id + "!");
            world.sendOnlineCheck(userId);
            return true;
        }
        return false;
    }

Replace it with:

Code:
public static boolean isOnline(int userId, String name) {
        int limboWorld = Limbo.get(userId);
        if (limboWorld != -1) {
            System.err.println(name + " is in limbo on world " + limboWorld + "!");
            return true;
        }
        for (World world : worlds) {
            if (!world.hasPlayer(name)) continue;
            System.err.println(name + " is online in world " + world.id + "!");
            world.sendOnlineCheck(name);
            return true;
        }
        return false;
    }

Now go to io.ruin.central.model.world > World.java

Add these 2 methods:

Code:
    public Player get(String name) {
        for (Player player : this.players) {
            if (!player.name.equalsIgnoreCase(name)) continue;
            return player;
        }
        return null;
    }

    public boolean hasPlayer(String name) {
        return this.get(name) != null;
    }

Now go to io.ruin.central.network > WorldSender.java

Replace sendOnlineCheck method with this

Code:
    public void sendOnlineCheck(String name) {
        OutBuffer out = new OutBuffer(5).sendFixedPacket(4).addString(name);
        this.write(out);
    }

Now for changing player saving usernames instead of userids (You must do this):

Go to io.ruin.central.model > Player.java

Replace load, save, and getSaveFile methods with these:

Code:
    public static String load(String username, World world) throws IOException {
        File file = Player.getSaveFile(username, world);
        return file.exists() ? new String(Files.readAllBytes(file.toPath())) : "";
    }

    public static boolean save(String username, World world, String json) {
        try {
            File file = Player.getSaveFile(username, world);
            Files.write(file.toPath(), json.getBytes(), new OpenOption[0]);
            return true;
        }
        catch (IOException e) {
            Server.logError(e.getMessage());
            return false;
        }
    }

    private static File getSaveFile(String username, World world) throws IOException {
        File folder = new File(System.getProperty("user.home") + "/Desktop/Kronos/_saved/players/" + world.stage.name().toLowerCase() + "/" + world.type.name().toLowerCase());
        if (!(folder.exists() || folder.mkdir() || folder.mkdirs())) {
            throw new IOException("Failed to make player saves folder for world: " + world.id + " (" + world.activity + ")");
        }
        return new File(System.getProperty("user.home") + "/Desktop/Kronos/_saved/players/" + world.stage.name().toLowerCase() + "/" + world.type.name().toLowerCase() + "/" + username + ".json");
    }

Now go to io.ruin.central.network > WorldDecoder.java

Find:

Code:
if (opcode == 3) {

Replace that whole if statement with this:

Code:
         if (opcode == 3) {
            int userId = in.readInt();
            int attempt = in.readUnsignedByte();
            String json = in.readString();
            Player player = Server.getPlayer(userId);
            if(player == null) {
               return;
            }
            if (!Player.save(player.name, world, json)) {
                return;
            }
            if (attempt != -1) {
                world.sendSaveResponse(userId, attempt);
            }
            return;
        }

Forums should now be fully removed and you can use the central server freely without any issues.

Step Two: Fixing Friends and Clan Chat

To save time we are just going to replace everything in these 7 class files:

io.ruin.central.model.social > SocialList.java

Code:
package io.ruin.central.model.social;

import com.google.gson.annotations.Expose;
import io.ruin.api.utils.JsonUtils;
import io.ruin.api.utils.StringUtils;
import io.ruin.central.Server;
import io.ruin.central.model.Player;
import io.ruin.central.model.social.clan.ClanChat;
import io.ruin.central.utility.XenUser;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.StandardOpenOption;
import java.util.HashMap;

public class SocialList extends SocialContainer {

    public String username;
    private boolean sent;
       @ Expose public int privacy;
       @ Expose public ClanChat cc;
    private static final HashMap<String, SocialList> LOADED = new HashMap();
    private static final File social_folder = new File("_saved/social/");

    public static String capitalize(String s) {
        s = s.toLowerCase();
        s = s.replaceAll("_", " ");
        for (int i = 0; i < s.length(); i++) {
            if (i == 0) {
                s = String.format("%s%s", Character.toUpperCase(s.charAt(0)), s.substring(1));
            }
            if (!Character.isLetterOrDigit(s.charAt(i))) {
                if (i + 1 < s.length()) {
                    s = String.format("%s%s%s", s.subSequence(0, i+1), Character.toUpperCase(s.charAt(i + 1)), s.substring(i+2));
                }
            }
        }
        return s;
    }

    public void offline(Player player) {
        this.sent = false;
        this.cc.leave(player, true);
        SocialList.save(this);
    }

    public void process(Player player) {
        if (!this.sent) {
            this.sent = true;
            this.update(null);
            player.sendSocial(true, this.friends);
            player.sendSocial(false, this.ignores);
            player.sendPrivacy(this.privacy);
            this.cc.init(player);
            return;
        }
        this.update(player);
    }

    private void update(Player sendFor) {
        if (this.friends != null) {
            for (SocialMember friend : this.friends) {
                if (friend == null) continue;
                int updatedWorldId = 0;
                Player pFriend = Server.getPlayer(friend.name);
                if (pFriend != null) {
                    boolean hidden;
                    friend.checkName(pFriend);
                    SocialList fList = SocialList.get(friend.name);
                    boolean bl = hidden = fList.privacy == 2 || fList.privacy == 1 && !fList.isFriend(this.username) || fList.isIgnored(this.username);
                    if (!hidden) {
                        updatedWorldId = pFriend.world.id;
                    }
                }
                if (friend.worldId == updatedWorldId && !friend.newName) continue;
                friend.worldId = updatedWorldId;
                if (sendFor == null) continue;
                sendFor.sendSocial(true, friend);
            }
        }
        if (this.ignores != null) {
            for (SocialMember ignore : this.ignores) {
                if (ignore == null) continue;
                Player pIgnore = Server.getPlayer(ignore.name);
                if (pIgnore != null) {
                    ignore.checkName(pIgnore);
                }
                if (ignore.worldId == 0 && !ignore.newName) continue;
                ignore.worldId = 0;
                if (sendFor == null) continue;
                sendFor.sendSocial(false, ignore);
            }
        }
    }

    private void add(Player player, String name, boolean ignore) {
        name = capitalize(name);
        String type = ignore ? "ignore" : "friend";
       // XenUser.forObj(name, user -> {
             /*       if (user == null) {
                        player.sendMessage("Unable to add " + type + " - social server offline.");
                        return;
                    }
                    if (user.name == null) {
                        player.sendMessage("Unable to add " + type + " - unknown player.");
                        return;
                    }*/
                    SocialMember member = new SocialMember(name, ignore ? null : SocialRank.FRIEND);
                    if (ignore) {
                        this.addIgnore(member);
                    } else if (this.addFriend(member) && this.cc.inClan(member.name)) {
                        this.cc.update(false);
                    }
                //}
      //  );
    }

    private void delete(String name) {
        name = capitalize(name);
        String username = this.deleteFriend(name);
        if (!username.equals("") && this.cc.inClan(name)) {
            this.cc.update(false);
        }
    }

    public static void handle(Player player, String name, int requestType) {
        name = capitalize(name);
        if (requestType == 1) {
            player.socialList.add(player, name, false);
        } else if (requestType == 2) {
            player.socialList.delete(name);
        } else if (requestType == 3) {
            player.socialList.add(player, name, true);
        } else if (requestType == 4) {
            player.socialList.deleteIgnore(name);
        }
    }

    public static void sendPrivateMessage(Player player, int rankId, String name, String message) {
        Player receiver = Server.getPlayer(name);
        if (receiver == null) {
            return;
        }
        message = StringUtils.fixCaps(message);
        player.sendPM(name, message);
        receiver.sendReceivePM(player.name, rankId, message);
    }

    public static SocialList get(String username) {
        SocialList loaded = LOADED.get(username);
        if (loaded == null) {
            File file = new File(social_folder, username + ".json");
            if (file.exists()) {
                try {
                    byte[] bytes = Files.readAllBytes(file.toPath());
                    String json = new String(bytes);
                    loaded = (SocialList) JsonUtils.GSON_EXPOSE.fromJson(json, SocialList.class);
                } catch (Exception e) {
                    Server.logError(e.getMessage());
                }
            }
            if (loaded == null) {
                loaded = new SocialList();
            }
        }
        if (loaded.cc == null) {
            loaded.cc = new ClanChat();
        }
        loaded.cc.parent = loaded;
        loaded.username = username;
        LOADED.put(loaded.username, loaded);
        return loaded;
    }

    private static void save(SocialList list) {
        try {
            String json = JsonUtils.GSON_EXPOSE.toJson(list);
            if(!social_folder.exists() && !social_folder.mkdirs())
                throw new IOException("social directory could not be created!");
            Files.write(new File(social_folder, list.username + ".json").toPath(), json.getBytes(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE);
        } catch (Exception e) {
            Server.logError(e.getMessage());
        }
    }
}

io.ruin.central.model.social.clan > ClanContainer.java

Code:
package io.ruin.central.model.social.clan;

import io.ruin.central.model.social.SocialMember;

public class ClanContainer {
    public int ccMembersCount;
    public SocialMember[] ccMembers;

    public boolean addClanMember(SocialMember member) {
        if (this.ccMembers == null) {
            this.ccMembers = new SocialMember[100];
        }
        if (this.ccMembersCount >= this.ccMembers.length || this.inClan(member.name)) {
            return false;
        }
        this.ccMembers[this.ccMembersCount++] = member;
        return true;
    }

    public void removeClanMember(String name) {
        for (int index = 0; index < this.ccMembersCount; ++index) {
            SocialMember member = this.ccMembers[index];
            if (!name.equalsIgnoreCase(member.name)) continue;
            --this.ccMembersCount;
            for (int i = index; i < this.ccMembersCount; ++i) {
                this.ccMembers[i] = this.ccMembers[i + 1];
            }
            this.ccMembers[this.ccMembersCount] = null;
            return;
        }
    }

    public SocialMember getClanMember(String username) {
        if (this.ccMembers == null) {
            return null;
        }
        for (SocialMember member : this.ccMembers) {
            if (member == null || !member.name.equalsIgnoreCase(username)) continue;
            return member;
        }
        return null;
    }

    public boolean inClan(String name) {
        return this.getClanMember(name) != null;
    }
}

io.ruin.central.model > Player.java

Code:
package io.ruin.central.model;

import io.ruin.api.buffer.OutBuffer;
import io.ruin.api.filestore.utility.Huffman;
import io.ruin.api.protocol.Protocol;
import io.ruin.api.utils.FileUtils;
import io.ruin.api.utils.Random;
import io.ruin.central.Server;
import io.ruin.central.model.social.SocialList;
import io.ruin.central.model.social.SocialMember;
import io.ruin.central.model.social.clan.ClanChat;
import io.ruin.central.model.world.World;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.util.List;

public class Player {

    public final int userId;
    public final String name;
    public final World world;
    public final boolean admin;
    public final SocialList socialList;

    public Player(int userId, String name, List<Integer> groupIds, World world) {
        this.userId = userId;
        this.name = name;
        this.admin = groupIds.contains(3);
        this.world = world;
        this.socialList = SocialList.get(name);
    }

    public ClanChat getClanChat() {
        return this.socialList.cc;
    }

    public ClanChat getActiveClanChat() {
        return this.socialList.cc.active;
    }

    public void destroy() {
        this.socialList.offline(this);
    }

    public void process() {
        this.socialList.process(this);
    }

    public void write(OutBuffer out) {
        out.encode(null);
        this.world.sendClientPacket(this.userId, out.toByteArray());
    }

    public void sendMessage(String message) {
        this.sendMessage(message, 0);
    }

    public void sendMessage(String message, int type) {
        this.write(Protocol.messagePacket(message, null, type));
    }

    public void sendPrivacy(int privacy) {
        OutBuffer out = new OutBuffer(2).sendFixedPacket(17).addByte(privacy);
        this.write(out);
    }

    public void sendSocial(boolean friendType, SocialMember... list) {
        if (friendType) {
            this.sendFriends(list);
        } else {
            this.sendIgnores(list);
        }
    }

    private void sendFriends(SocialMember ... friends) {
        OutBuffer out = new OutBuffer(friends == null ? 3 : 255).sendVarShortPacket(61);
        if (friends != null) {
            for (SocialMember friend : friends) {
                if (friend == null) continue;
                out.addByte(friend.sendNewName() ? 1 : 0);
                out.addString(friend.name);
                out.addString(friend.lastName);
                out.addShort(friend.worldId);
                out.addByte(friend.rank == null ? -1 : friend.rank.id);
                out.addByte(0);
                if (friend.worldId > 0) {
                    out.skip(6);
                }
                out.skip(1);
            }
        }
        this.write(out);
    }

    private void sendIgnores(SocialMember ... ignores) {
        OutBuffer out = new OutBuffer(255).sendVarShortPacket(59);
        if (ignores != null) {
            for (SocialMember ignore : ignores) {
                if (ignore == null) continue;
                out.addByte(ignore.sendNewName() ? 1 : 0);
                out.addString(ignore.name);
                out.addString(ignore.lastName);
                out.skip(1);
            }
        }
       this.write(out);
    }

    public void sendPM(String toUsername, String message) {
        this.write(Protocol.outgoingPm(toUsername, message));
    }

    public void sendReceivePM(String fromName, int fromRank, String message) {
        OutBuffer out = new OutBuffer(255).sendVarShortPacket(45)
                .addString(fromName);
        for (int i = 0; i < 5; ++i) {
            out.addByte(Random.get(255));
        }
        out.addByte(fromRank);
        Huffman.encrypt(out, message);
       this.write(out);
    }

    public static String load(String username, World world) throws IOException {
        File file = Player.getSaveFile(username, world);
        return file.exists() ? new String(Files.readAllBytes(file.toPath())) : "";
    }

    public static boolean save(String username, World world, String json) {
        try {
            File file = Player.getSaveFile(username, world);
            Files.write(file.toPath(), json.getBytes(), new OpenOption[0]);
            return true;
        }
        catch (IOException e) {
            Server.logError(e.getMessage());
            return false;
        }
    }

    private static File getSaveFile(String username, World world) throws IOException {
        File folder = new File(System.getProperty("user.home") + "/Desktop/Kronos/_saved/players/" + world.stage.name().toLowerCase() + "/" + world.type.name().toLowerCase());
        if (!(folder.exists() || folder.mkdir() || folder.mkdirs())) {
            throw new IOException("Failed to make player saves folder for world: " + world.id + " (" + world.activity + ")");
        }
        return new File(System.getProperty("user.home") + "/Desktop/Kronos/_saved/players/" + world.stage.name().toLowerCase() + "/" + world.type.name().toLowerCase() + "/" + username + ".json");
    }
}

io.ruin.central.model.social > SocialContainer.java

Code:
package io.ruin.central.model.social;

import com.google.gson.annotations.Expose;

public class SocialContainer {

       @ Expose protected int friendsCount;
       @ Expose public SocialMember[] friends;
       @ Expose protected int ignoresCount;
       @ Expose public SocialMember[] ignores;

    public boolean addFriend(SocialMember friend) {
        if (this.friends == null) {
            this.friends = new SocialMember[400];
        }
        if (this.friendsCount >= this.friends.length || this.isFriend(friend.name)) {
            return false;
        }
        this.friends[this.friendsCount++] = friend;
        return true;
    }

    public String deleteFriend(String name) {
        for (int index = 0; index < this.friendsCount; ++index) {
            SocialMember friend = this.friends[index];
            if (!name.equalsIgnoreCase(friend.name)) continue;
            --this.friendsCount;
            for (int i = index; i < this.friendsCount; ++i) {
                this.friends[i] = this.friends[i + 1];
            }
            this.friends[this.friendsCount] = null;
            return friend.name;
        }
        return "";
    }

    public SocialMember getFriend(String name) {
        if (this.friends == null) {
            return null;
        }
        for (SocialMember friend : this.friends) {
            if (friend == null || !friend.name.equalsIgnoreCase(name)) continue;
            return friend;
        }
        return null;
    }

    public boolean isFriend(String name) {
        return this.getFriend(name) != null;
    }

    public void addIgnore(SocialMember ignore) {
        if (this.ignores == null) {
            this.ignores = new SocialMember[400];
        }
        if (this.ignoresCount >= this.ignores.length || this.isIgnored(ignore.name)) {
            return;
        }
        this.ignores[this.ignoresCount++] = ignore;
    }

    public String deleteIgnore(String name) {
        for (int index = 0; index < this.ignoresCount; ++index) {
            SocialMember ignore = this.ignores[index];
            if (!name.equalsIgnoreCase(ignore.name)) continue;
            --this.ignoresCount;
            for (int i = index; i < this.ignoresCount; ++i) {
                this.ignores[i] = this.ignores[i + 1];
            }
            this.ignores[this.ignoresCount] = null;
            return ignore.name;
        }
        return "";
    }

    public SocialMember getIgnore(String username) {
        if (this.ignores == null) {
            return null;
        }
        for (SocialMember ignore : this.ignores) {
            if (ignore == null || !username.equalsIgnoreCase(ignore.name)) continue;
            return ignore;
        }
        return null;
    }

    public boolean isIgnored(String username) {
        return this.getIgnore(username) != null;
    }
}

io.ruin.central.model.social.clan > ClanChat.java

Code:
package io.ruin.central.model.social.clan;

import com.google.gson.annotations.Expose;
import io.ruin.api.buffer.OutBuffer;
import io.ruin.api.filestore.utility.Huffman;
import io.ruin.api.utils.Random;
import io.ruin.api.utils.StringUtils;
import io.ruin.central.Server;
import io.ruin.central.model.Player;
import io.ruin.central.model.social.SocialList;
import io.ruin.central.model.social.SocialMember;
import io.ruin.central.model.social.SocialRank;
import io.ruin.central.utility.XenUser;

import java.util.HashMap;

public class ClanChat extends ClanContainer {

    public SocialList parent;
    private String owner;
       @ Expose private String lastName = "";
       @ Expose public String name;
       @ Expose public SocialRank enterRank = null;
       @ Expose public SocialRank talkRank = null;
       @ Expose public SocialRank kickRank = SocialRank.CORPORAL;
    public ClanChat active;
    private boolean joining;
    private HashMap<Integer, Long> kicked;

    public void init(Player player) {
        this.sendSettings(player);
        if (!this.lastName.equals("")) {
            this.join(player, this.lastName);
        }
    }

    private void sendSettings(Player player) {
        String name = this.name == null ? "Chat disabled" : this.name;
        int enterRank = this.enterRank == null ? -1 : this.enterRank.id;
        int talkRank = this.talkRank == null ? -1 : this.talkRank.id;
        int kickRank = this.kickRank.id;
        OutBuffer out = new OutBuffer(2 + (name.length() + 1) + 3).sendVarBytePacket(86).addString(name).addByte(enterRank).addByte(talkRank).addByte(kickRank);
        player.write(out);
    }

    public void update(boolean settingsOnly) {
        if (this.ccMembersCount == 0) {
            return;
        }
        OutBuffer out = settingsOnly ? this.getSettingsBuffer() : this.getChannelBuffer();
        for (int i = 0; i < this.ccMembersCount; ++i) {
            SocialMember member = this.ccMembers[i];
            Player pMember = Server.getPlayer(member.name);
            if (pMember == null) continue;
            pMember.write(out);
        }
    }

    public void disable() {
        for (int i = 0; i < this.ccMembersCount; ++i) {
            SocialMember member = this.ccMembers[i];
            Player pMember = Server.getPlayer(member.name);
            if (pMember == null) continue;
            pMember.getClanChat().setActive(pMember, null);
            pMember.sendMessage("The clan chat channel you were in has been disabled.", 11);
        }
        if (this.kicked != null) {
            this.kicked.clear();
            this.kicked = null;
        }
    }

    public boolean isDisabled() {
        return this.name == null;
    }

    private void setActive(Player player, ClanChat newActive) {
        if (this.active == newActive) {
            return;
        }
        if (newActive == null) {
            this.active.removeClanMember(player.name);
            player.write(this.active.getLeaveBuffer());
            if (!this.active.isDisabled()) {
                this.active.update(false);
            }
        } else {
            this.lastName = newActive.parent.username;
            newActive.update(false);
        }
        this.active = newActive;
    }

    public static String capitalize(String s) {
        s = s.toLowerCase();
        s = s.replaceAll("_", " ");
        for (int i = 0; i < s.length(); i++) {
            if (i == 0) {
                s = String.format("%s%s", Character.toUpperCase(s.charAt(0)), s.substring(1));
            }
            if (!Character.isLetterOrDigit(s.charAt(i))) {
                if (i + 1 < s.length()) {
                    s = String.format("%s%s%s", s.subSequence(0, i+1), Character.toUpperCase(s.charAt(i + 1)), s.substring(i+2));
                }
            }
        }
        return s;
    }


    public void join(Player player, String name) {
        name = capitalize(name);
      /*  if (user == null) {
            player.sendMessage("Unable to join channel - social server offline.", 11);
            return;
        }
        if (user.name == null) {
            player.sendMessage("The channel you tried to join does not exist.", 11);
            return;
        }*/
        if (this.active != null) {
            player.sendMessage("You are already in a channel!", 11);
            return;
        }
        SocialList ownerList = SocialList.get(name);
        if (ownerList.isIgnored(player.name)) {
            player.sendMessage("You are blocked from joining this channel.");
            return;
        }
        ClanChat joinCc = ownerList.cc;
        joinCc.owner = name;
        if (joinCc.addMember(player)) {
            this.setActive(player, joinCc);
            player.sendMessage("Now talking in clan chat channel " + joinCc.name + ".", 11);
            player.sendMessage("To talk, start each line of chat with the / symbol.", 11);
        }
    }

    public void leave(Player player, boolean logout) {
        if (this.active == null) {
            return;
        }
        this.lastName = logout ? this.active.parent.username : "";
        this.setActive(player, null);
    }

    public void kick(Player kickedBy, String kickName) {
        if (this.isDisabled()) {
            return;
        }
        SocialRank kickerRank = this.getRank(kickedBy);
        if (!ClanChat.meetsRank(this.kickRank, kickerRank)) {
            kickedBy.sendMessage("You are not a high enough rank to kick from this channel.", 11);
            return;
        }
        Player toKick = Server.getPlayer(kickName);
        if (toKick == null || toKick.getActiveClanChat() != this) {
            kickedBy.sendMessage(kickName + " is not active in this channel.", 11);
            return;
        }
        SocialRank toKickRank = this.getRank(toKick);
        if (toKickRank == SocialRank.OWNER) {
            kickedBy.sendMessage("You can't kick this channel's owner!", 11);
            return;
        }
        if (!ClanChat.meetsRank(toKickRank, kickerRank)) {
            kickedBy.sendMessage("You are not a high enough rank to kick this member.", 11);
            return;
        }
        if (this.kicked == null) {
            this.kicked = new HashMap();
        }
        this.kicked.put(toKick.userId, System.currentTimeMillis() + 3600000L);
        toKick.getClanChat().leave(toKick, false);
        toKick.sendMessage("You have been kicked from the channel.", 11);
    }

    private boolean addMember(Player pMember) {
        Long kickedUntil;
        if (this.isDisabled()) {
            pMember.sendMessage("The channel you tried to join is currently disabled.", 11);
            return false;
        }
        if (this.kicked != null && (kickedUntil = this.kicked.get(pMember.userId)) != null) {
            if (kickedUntil > System.currentTimeMillis()) {
                pMember.sendMessage("You cannot join this channel because you are currently banned from it.", 11);
                return false;
            }
            this.kicked.remove(pMember.userId);
        }
        if (!ClanChat.meetsRank(this.enterRank, this.getRank(pMember))) {
            pMember.sendMessage("You are not a high enough rank to enter this channel.", 11);
            return false;
        }
        if (!this.addClanMember(new SocialMember(pMember.userId, pMember.name, pMember.world.id))) {
            pMember.sendMessage("The channel you tried to join is full.", 11);
            return false;
        }
        return true;
    }

    public void message(Player sender, int rankId, String message) {
        if (this.isDisabled()) {
            return;
        }
        SocialRank senderRank = this.getRank(sender);
        if (!ClanChat.meetsRank(this.talkRank, senderRank)) {
            sender.sendMessage("You are not a high enough rank to talk in this channel.", 11);
            return;
        }
        message = StringUtils.fixCaps(message);
        for (int i = 0; i < this.ccMembersCount; ++i) {
            SocialMember member = this.ccMembers[i];
            Player pMember = Server.getPlayer(member.name);
            if (pMember == null) continue;
            pMember.write(this.getMessageBuffer(sender.name, rankId, message));
        }
    }

    private OutBuffer getMessageBuffer(String senderName, int rankId, String message) {
        OutBuffer out = new OutBuffer(255).sendVarBytePacket(22);
        out.addString(senderName);
        out.addString(this.name);
        for (int i = 0; i < 5; ++i) {
            out.addByte(Random.get(255));
        }
        out.addByte(rankId);
        Huffman.encrypt(out, message);
        return out;
    }

    private OutBuffer getBuffer(int type) {
        if (type == 0) {
            return new OutBuffer(3).sendVarShortPacket(48);
        }
        OutBuffer out = new OutBuffer(255).sendVarShortPacket(48).
                addString(this.owner).
                addString(this.name).
                addByte(ClanChat.getRankId(this.kickRank));
        if (type == 2) {
            out.addByte(255);
            return out;
        }
        out.addByte(this.ccMembersCount);
        for (int i = 0; i < this.ccMembersCount; ++i) {
            SocialMember member = this.ccMembers[i];
            out.addString(member.name);
            out.addShort(member.worldId);
            out.addByte(this.getRankId(member.name));
            out.addByte(0);
        }
        return out;
    }

    private OutBuffer getLeaveBuffer() {
        return this.getBuffer(0);
    }

    private OutBuffer getChannelBuffer() {
        return this.getBuffer(1);
    }

    private OutBuffer getSettingsBuffer() {
        return this.getBuffer(2);
    }

    private SocialRank getRank(Player player) {
        if (player.name.equalsIgnoreCase(this.parent.username)) {
            return SocialRank.OWNER;
        }
        if (player.admin) {
            return SocialRank.ADMIN;
        }
        SocialMember friend = this.parent.getFriend(player.name);
        return friend == null ? null : friend.rank;
    }

    private int getRankId(String username) {
        if (username.equalsIgnoreCase(this.parent.username)) {
            return SocialRank.OWNER.id;
        }
        Player player = Server.getPlayer(username);
        if (player != null && player.admin) {
            return SocialRank.ADMIN.id;
        }
        SocialMember friend = this.parent.getFriend(player.name);
        return friend == null ? -1 : friend.rank.id;
    }

    private static int getRankId(SocialRank rank) {
        return rank == null ? -1 : rank.id;
    }

    private static boolean meetsRank(SocialRank reqRank, SocialRank checkRank) {
        return reqRank == null || checkRank != null && checkRank.id >= reqRank.id;
    }
}

io.ruin.central.network > WorldDecoder.java

Code:
package io.ruin.central.network;

import io.ruin.api.buffer.InBuffer;
import io.ruin.api.netty.MessageDecoder;
import io.ruin.api.protocol.login.LoginInfo;
import io.ruin.api.utils.IPBans;
import io.ruin.api.utils.MACBan;
import io.ruin.api.utils.UUIDBan;
import io.ruin.central.Server;
import io.ruin.central.model.Player;
import io.ruin.central.model.social.SocialList;
import io.ruin.central.model.social.SocialMember;
import io.ruin.central.model.social.SocialRank;
import io.ruin.central.model.social.clan.ClanChat;
import io.ruin.central.model.world.World;
import io.ruin.central.model.world.WorldLogin;

public class WorldDecoder extends MessageDecoder<World> {
    public long lastMessageAt = System.currentTimeMillis();

    public WorldDecoder() {
        super(null, true);
    }

    @Override
    public void handle(World world, InBuffer in, int opcode) {
        this.lastMessageAt = System.currentTimeMillis();
        if (opcode == 0) {
            return;
        }
        if (opcode == 1) {
            String ip = in.readString();
            String name = in.readString();
            String password = in.readString();
            String email = in.readString();
            String macAddress = in.readString();
            String uuid = in.readString();
            int tfaCode = in.readMedium();
            boolean tfaTrust = in.readByte() == 1;
            int tfaTrustKey = in.readUnsignedByte();
            new WorldLogin(world, new LoginInfo(ip, name, password, email, macAddress, uuid, tfaCode, tfaTrust, tfaTrustKey));
            return;
        }
        if (opcode == 2) {
            int userId = in.readInt();
            world.removePlayer(userId);
            return;
        }
        if (opcode == 3) {
            int userId = in.readInt();
            int attempt = in.readUnsignedByte();
            String json = in.readString();
            Player player = Server.getPlayer(userId);
            if(player == null) {
               return;
            }
            if (!Player.save(player.name, world, json)) {
                return;
            }
            if (attempt != -1) {
                world.sendSaveResponse(userId, attempt);
            }
            return;
        }
        if (opcode == 4) {
            int userId = in.readInt();
            String message = in.readString();
            if (userId == -1) {
                for (World w : Server.worlds) {
                    for (Player p : w.players) {
                        p.sendMessage(message);
                    }
                }
            } else {
                Player player = Server.getPlayer(userId);
                if (player == null) {
                    return;
                }
                player.sendMessage(message);
                for (World w : Server.worlds) {
                    for (Player p : w.players) {
                        if (!p.socialList.isFriend(player.name)) continue;
                        p.sendMessage(message);
                    }
                }
            }
            return;
        }
        if (opcode == 5) {
            int userId = in.readInt();
            byte privacy = in.readByte();
            Player player = Server.getPlayer(userId);
            if (player == null) {
                return;
            }
            player.socialList.privacy = privacy;
            return;
        }
        if (opcode == 6) {
            int userId = in.readInt();
            String username = in.readString();
            byte requestType = in.readByte();
            Player player = Server.getPlayer(userId);
            if (player == null) {
                return;
            }
            SocialList.handle(player, username, requestType);
            return;
        }
        if (opcode == 7) {
            int senderId = in.readInt();
            byte rankId = in.readByte();
            String username = in.readString();
            String message = in.readString();
            Player sender = Server.getPlayer(senderId);
            if (sender == null) {
                return;
            }
            SocialList.sendPrivateMessage(sender, rankId, username, message);
            return;
        }
        if (opcode == 8) {
            int userId = in.readInt();
            String name = in.readString();
            Player player = Server.getPlayer(userId);
            if (player == null) {
                return;
            }
            ClanChat cc = player.getClanChat();
            if (name.isEmpty()) {
                cc.name = null;
                cc.disable();
            } else {
                cc.name = name;
                cc.update(true);
            }
            return;
        }
        if (opcode == 9) {
            int userId = in.readInt();
            byte settingId = in.readByte();
            byte value = in.readByte();
            Player player = Server.getPlayer(userId);
            if (player == null) {
                return;
            }
            ClanChat cc = player.getClanChat();
            if (settingId == 0) {
                cc.enterRank = SocialRank.get(value, null);
            } else if (settingId == 1) {
                cc.talkRank = SocialRank.get(value, null);
            } else {
                cc.kickRank = SocialRank.get(value, SocialRank.CORPORAL);
                cc.update(true);
            }
            return;
        }
        if (opcode == 10) {
            int userId = in.readInt();
            String friendName = in.readString();
            SocialRank rank = SocialRank.get(in.readByte(), null);
            if (rank == null) {
                return;
            }
            Player player = Server.getPlayer(userId);
            if (player == null) {
                return;
            }
            SocialMember friend = player.socialList.getFriend(friendName);
            if (friend == null || friend.rank == rank) {
                return;
            }
            friend.rank = rank;
            friend.resend();
            ClanChat cc = player.getClanChat();
            if (cc.inClan(friend.name)) {
                cc.update(false);
            }
            return;
        }
        if (opcode == 11) {
            int userId = in.readInt();
            String ownerName = in.readString();
            Player player = Server.getPlayer(userId);
            if (player == null) {
                return;
            }
            if (ownerName.isEmpty()) {
                player.getClanChat().leave(player, false);
            } else {
                player.getClanChat().join(player, ownerName);
            }
            return;
        }
        if (opcode == 12) {
            int userId = in.readInt();
            String kickName = in.readString();
            Player player = Server.getPlayer(userId);
            if (player == null) {
                return;
            }
            ClanChat active = player.getActiveClanChat();
            if (active == null) {
                return;
            }
            active.kick(player, kickName);
            return;
        }
        if (opcode == 13) {
            int userId = in.readInt();
            byte rankId = in.readByte();
            String message = in.readString();
            Player player = Server.getPlayer(userId);
            if (player == null) {
                return;
            }
            ClanChat active = player.getActiveClanChat();
            if (active == null) {
                return;
            }
            active.message(player, rankId, message);
            return;
        }
        if (opcode == 14) {
            IPBans.refreshBans();
            MACBan.refreshBans();
            UUIDBan.refreshBans();
            return;
        }
        if (opcode == 15) {
            String uuid = in.readString();
            UUIDBan.requestBan(uuid);
            return;
        }
        if (opcode == 16) {
            String ip = in.readString();
            int userId = in.readInt();
            Player player = Server.getPlayer(userId);
            if (player == null) {
                return;
            }
            IPBans.requestBan(player.name, ip);
            return;
        }
        if (opcode == 17) {
            String mac = in.readString();
            int userId = in.readInt();
            Player player = Server.getPlayer(userId);
            if (player == null) {
                return;
            }
            MACBan.requestBan(player.name, mac);
            return;
        }
    }

    @Override
    public int getSize(int opcode) {
        switch (opcode) {
            case 0:
            case 14: {
                return 0;
            }
            case 1: {
                return -1;
            }
            case 2: {
                return 4;
            }
            case 3: {
                return -4;
            }
            case 4: {
                return -1;
            }
            case 5: {
                return 5;
            }
            case 6: {
                return -1;
            }
            case 7: {
                return -1;
            }
            case 8: {
                return -1;
            }
            case 9: {
                return 6;
            }
            case 10: {
                return -1;
            }
            case 11: {
                return -1;
            }
            case 12: {
                return -1;
            }
            case 13: {
                return -1;
            }
            case 15: {
                return -1;
            }
            case 16: {
                return -1;
            }
            case 17: {
                return -1;
            }
        }
        return -128;
    }
}

io.ruin.central.model.social > SocialMember.java

Code:
package io.ruin.central.model.social;

import com.google.gson.annotations.Expose;
import io.ruin.central.model.Player;
import io.ruin.central.utility.XenUser;

import java.util.Objects;

public class SocialMember {

       @ Expose public String name;
       @ Expose public String lastName;
    protected boolean newName;
       @ Expose public SocialRank rank;
    public int worldId = -1;

    public SocialMember(XenUser user, SocialRank rank) {
        this.name = user.name;
        this.lastName = (user.lastName == null || user.lastName.equalsIgnoreCase("null")) ? "" : user.lastName;
        this.rank = rank;
    }

    public SocialMember(String username, SocialRank rank) {
        this.name = username;
        this.lastName = "";
        this.rank = rank;
    }


    public SocialMember(int userId, String name, int worldId) {
        this.name = name;
        this.lastName = "";
        this.worldId = worldId;
    }

    public void resend() {
        this.worldId = -1;
    }

    protected void checkName(Player player) {
        if (!Objects.equals(this.name, player.name)) {
            System.err.print(this.name);
            this.lastName = this.name;
            this.name = player.name;
            this.newName = true;
        }
    }

    public boolean sendNewName() {
        if (this.newName) {
            this.newName = false;
            this.resend();
            return true;
        }
        return false;
    }
}

And that's all you need to do for Friends and Clan chat to work.

Step Three: Password Saving (Do This AFTER Friends and Clan Chat fix to avoid bugs/errors)

Go to io.ruin.model.entity.player > PlayerLogin.java

Find:

Code:
        if (player == null) {
                deny(Response.ERROR_LOADING_ACCOUNT);
                return;
            }

Right below that add:

Code:
            if(player.getPassword() != null) {
                if (!info.password.equalsIgnoreCase(player.getPassword())) {
                    deny(Response.INVALID_LOGIN);
                    return;
                }
            }

Now go to io.ruin.model.entity.player > Player.java

Find

Code:
    private String password;

Change it to

Code:
    @ Expose private String password;

Your accounts will now be password protected

If you ever get this error, ignore it, it won't impact anything:

Code:
[ForkJoinPool.commonPool-worker-2] ERROR rollingErrorFileLogger - Failed to post: https://community.kronos.rip/integration/index.php
java.io.IOException: Server returned HTTP response code: 522 for URL: https://community.kronos.rip/integration/index.php
    at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1900)
    at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1498)
    at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:268)
    at io.ruin.api.utils.PostWorker.post(PostWorker.java:65)
    at io.ruin.api.utils.PostWorker.postArray(PostWorker.java:82)
    at io.ruin.api.utils.XenPost.post(XenPost.java:15)
    at io.ruin.model.entity.player.PlayerGroup.lambda$sync$0(PlayerGroup.java:72)
    at java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1640)
    at java.util.concurrent.CompletableFuture$AsyncRun.exec(CompletableFuture.java:1632)
    at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
    at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
    at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
    at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
00
  • Like
Reactions: