account refactoring, eros: wip connecting to server

This commit is contained in:
Bixilon 2021-07-24 15:13:44 +02:00
parent 06fc142d51
commit 9f6c2fb6b0
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
48 changed files with 668 additions and 531 deletions

View File

@ -50,6 +50,8 @@ object Minosoft {
val GLOBAL_EVENT_MASTER = GlobalEventMaster()
val LANGUAGE_MANAGER = MultiLanguageManager()
val START_UP_LATCH = CountUpAndDownLatch(1)
@Deprecated("Will be singleton interface")
lateinit var config: Configuration
var initialized: Boolean = false

View File

@ -34,7 +34,7 @@ class Configuration(private val configName: String = RunConfiguration.CONFIG_FIL
init {
if (file.exists()) {
val config = JSONSerializer.MAP_ADAPTER.fromJson(Util.readFile(file.absolutePath))!!
val config = JSONSerializer.MUTABLE_MAP_ADAPTER.fromJson(Util.readFile(file.absolutePath))!!
migrate(config)
var wasMigrated = false

View File

@ -14,12 +14,22 @@
package de.bixilon.minosoft.config.config.account
import com.squareup.moshi.Json
import de.bixilon.minosoft.Minosoft
import de.bixilon.minosoft.data.accounts.Account
import de.bixilon.minosoft.modding.event.events.account.AccountSelectEvent
import java.util.*
data class AccountConfig(
var selected: String = "",
@Json(name = "client_token")
var clientToken: String = UUID.randomUUID().toString(),
@Json(name = "selected") var selectedAccountId: String? = null,
@Json(name = "client_token") var clientToken: String = UUID.randomUUID().toString(),
val entries: MutableMap<String, Account> = mutableMapOf(),
)
) {
@Transient
var selected: Account? = null
get() = entries[selectedAccountId]
set(value) {
Minosoft.GLOBAL_EVENT_MASTER.fireEvent(AccountSelectEvent(selected, value))
field // To allow transient for moshi
selectedAccountId = value?.id
}
}

View File

@ -25,7 +25,7 @@ data class Server(
val id: Int = nextServerId++, // ToDo: Is duplicated in config (first key, then in value)
var address: String,
var name: ChatComponent = ChatComponent.of(address),
@Json(name = "version") var desiredVersion: Int = -1,
@Json(name = "version") var forcedVersion: Int? = null,
@Json(name = "favicon") var faviconHash: String? = null,
var type: ServerTypes = ServerTypes.NORMAL,
) {
@ -56,6 +56,11 @@ data class Server(
if (id > nextServerId) {
nextServerId = id + 1
}
forcedVersion?.let {
if (it < 0) {
forcedVersion = null
}
}
faviconHash?.let { favicon = AssetsUtil.readAsset(it, true) }
}

View File

@ -1,86 +0,0 @@
/*
* Minosoft
* Copyright (C) 2020 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.accounts;
import de.bixilon.minosoft.Minosoft;
import de.bixilon.minosoft.util.logging.Log;
import de.bixilon.minosoft.util.mojang.api.exceptions.MojangJoinServerErrorException;
import de.bixilon.minosoft.util.mojang.api.exceptions.NoNetworkConnectionException;
import java.util.Map;
import java.util.UUID;
public abstract class Account {
protected final String username;
protected final UUID uuid;
protected Account(String username, UUID uuid) {
this.username = username;
this.uuid = uuid;
}
public static void addAccount(Account account) {
Minosoft.config.getConfig().getAccount().getEntries().put(account.getId(), account);
account.saveToConfig();
Log.info(String.format("Added and saved account (type=%s, id=%s, username=%s, uuid=%s)", account.getClass().getSimpleName(), account.getId(), account.getUsername(), account.getUUID()));
}
public String getUsername() {
return this.username;
}
public UUID getUUID() {
return this.uuid;
}
public abstract Map<String, Object> serialize();
public abstract void join(String serverId) throws MojangJoinServerErrorException, NoNetworkConnectionException;
public abstract boolean select();
public abstract void logout();
public abstract String getId();
@Override
public int hashCode() {
return getId().hashCode();
}
@Override
public boolean equals(Object obj) {
if (super.equals(obj)) {
return true;
}
if (obj == null) {
return false;
}
if (hashCode() != obj.hashCode()) {
return false;
}
Account account = (Account) obj;
return getId().equals(account.getId());
}
@Override
public String toString() {
return getId();
}
public void saveToConfig() {
Minosoft.config.getConfig().getAccount().getEntries().put(this.getId(), this);
Minosoft.config.saveToFile();
}
}

View File

@ -1,6 +1,6 @@
/*
* Minosoft
* Copyright (C) 2020 Moritz Zwerger
* Copyright (C) 2021 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
@ -11,19 +11,18 @@
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.util.mojang.api.exceptions;
package de.bixilon.minosoft.data.accounts
public class NoNetworkConnectionException extends Exception {
import de.bixilon.minosoft.data.registries.ResourceLocation
public NoNetworkConnectionException(Exception cause) {
super(cause);
}
abstract class Account(
val username: String,
) {
abstract val id: String
abstract val type: ResourceLocation
public NoNetworkConnectionException(String message) {
super(message);
}
abstract fun join(serverId: String)
public NoNetworkConnectionException() {
}
abstract fun logout()
abstract fun verify()
}

View File

@ -0,0 +1,22 @@
/*
* Minosoft
* Copyright (C) 2021 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.accounts
import de.bixilon.minosoft.data.registries.CompanionResourceLocation
import de.bixilon.minosoft.util.json.JSONSerializer
import kotlin.reflect.KClass
abstract class AccountType(`class`: KClass<out Account>) : CompanionResourceLocation {
val TYPE = JSONSerializer.MOSHI.adapter(`class`.java)
}

View File

@ -0,0 +1,28 @@
/*
* Minosoft
* Copyright (C) 2021 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.accounts
import de.bixilon.minosoft.data.accounts.types.MicrosoftAccount
import de.bixilon.minosoft.data.accounts.types.MojangAccount
import de.bixilon.minosoft.data.accounts.types.OfflineAccount
import de.bixilon.minosoft.data.registries.ResourceLocation
import de.bixilon.minosoft.util.KUtil.asResourceLocationMap
object AccountTypes {
val ACCOUNT_TYPES: Map<ResourceLocation, AccountType> = listOf(
MicrosoftAccount,
MojangAccount,
OfflineAccount,
).asResourceLocationMap()
}

View File

@ -1,38 +0,0 @@
/*
* Minosoft
* Copyright (C) 2020 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.accounts;
import com.google.gson.JsonObject;
import de.bixilon.minosoft.util.Util;
import java.util.Map;
import java.util.UUID;
public class MicrosoftAccount extends MojangAccount {
public MicrosoftAccount(String accessToken, String id, UUID uuid, String username) {
super(accessToken, id, uuid, username, null);
}
public static MicrosoftAccount deserialize(JsonObject json) {
return new MicrosoftAccount(json.get("accessToken").getAsString(), json.get("id").getAsString(), Util.getUUIDFromString(json.get("uuid").getAsString()), json.get("username").getAsString());
}
public Map<String, Object> serialize() {
Map<String, Object> json = super.serialize();
json.put("type", "microsoft");
json.remove("email");
return json;
}
}

View File

@ -1,128 +0,0 @@
/*
* Minosoft
* Copyright (C) 2020 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.accounts;
import com.google.gson.JsonObject;
import de.bixilon.minosoft.Minosoft;
import de.bixilon.minosoft.util.Util;
import de.bixilon.minosoft.util.mojang.api.MojangAuthentication;
import de.bixilon.minosoft.util.mojang.api.exceptions.AuthenticationException;
import de.bixilon.minosoft.util.mojang.api.exceptions.MojangJoinServerErrorException;
import de.bixilon.minosoft.util.mojang.api.exceptions.NoNetworkConnectionException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class MojangAccount extends Account {
protected final String id;
protected final String email;
protected String accessToken;
protected RefreshStates lastRefreshStatus;
protected boolean needsRefresh = true;
public MojangAccount(String username, JsonObject json) {
super(json.getAsJsonObject("selectedProfile").get("name").getAsString(), Util.getUUIDFromString(json.getAsJsonObject("selectedProfile").get("id").getAsString()));
this.accessToken = json.get("accessToken").getAsString();
this.id = json.getAsJsonObject("user").get("id").getAsString();
this.email = username;
}
public MojangAccount(String accessToken, String id, UUID uuid, String username, String email) {
super(username, uuid);
this.accessToken = accessToken;
this.id = id;
this.email = email;
}
public static MojangAccount deserialize(Map<String, Object> json) {
return new MojangAccount((String) json.get("accessToken"), (String) json.get("id"), Util.getUUIDFromString((String) json.get("uuid")), (String) json.get("username"), (String) json.get("email"));
}
public Map<String, Object> serialize() {
Map<String, Object> json = new HashMap<>();
json.put("id", this.id);
json.put("accessToken", this.accessToken);
json.put("uuid", getUUID().toString());
json.put("username", getUsername());
json.put("email", this.email);
json.put("type", "mojang");
return json;
}
public void join(String serverId) throws MojangJoinServerErrorException, NoNetworkConnectionException {
MojangAuthentication.joinServer(this, serverId);
}
@Override
public boolean select() {
if (this.needsRefresh) {
return refreshToken() != RefreshStates.ERROR;
}
return true;
}
@Override
public void logout() {
Minosoft.config.getConfig().getAccount().getEntries().remove(this.getId());
Minosoft.config.saveToFile();
}
@Override
public String getId() {
return this.id;
}
public RefreshStates refreshToken() {
try {
this.accessToken = MojangAuthentication.refresh(this.accessToken);
this.lastRefreshStatus = RefreshStates.SUCCESSFUL;
} catch (NoNetworkConnectionException e) {
e.printStackTrace();
this.lastRefreshStatus = RefreshStates.FAILED;
} catch (AuthenticationException e) {
e.printStackTrace();
this.lastRefreshStatus = RefreshStates.ERROR;
}
return this.lastRefreshStatus;
}
public String getAccessToken() {
return this.accessToken;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
this.needsRefresh = false;
}
public String getEmail() {
return this.email;
}
public boolean needsRefresh() {
return this.needsRefresh;
}
public void setNeedRefresh(boolean needsRefresh) {
this.needsRefresh = needsRefresh;
}
public enum RefreshStates {
SUCCESSFUL,
ERROR, // account not valid anymore
FAILED // error occurred while checking -> Unknown state
}
}

View File

@ -1,62 +0,0 @@
/*
* Minosoft
* Copyright (C) 2020 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.accounts;
import de.bixilon.minosoft.util.Util;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class OfflineAccount extends Account {
public OfflineAccount(String username) {
super(username, UUID.nameUUIDFromBytes(("OfflinePlayer:" + username).getBytes(StandardCharsets.UTF_8)));
}
public OfflineAccount(String username, UUID uuid) {
super(username, uuid);
}
public static OfflineAccount deserialize(Map<String, Object> json) {
return new OfflineAccount((String) json.get("username"), Util.getUUIDFromString((String) json.get("uuid")));
}
@Override
public Map<String, Object> serialize() {
Map<String, Object> json = new HashMap<>();
json.put("username", getUsername());
json.put("uuid", getUUID().toString());
json.put("type", "offline");
return json;
}
@Override
public void join(String serverId) {
}
@Override
public boolean select() {
return true;
}
@Override
public void logout() {
}
@Override
public String getId() {
return getUsername() + ":" + getUUID().toString();
}
}

View File

@ -0,0 +1,47 @@
/*
* Minosoft
* Copyright (C) 2021 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.accounts.types
import com.squareup.moshi.Json
import de.bixilon.minosoft.data.accounts.Account
import de.bixilon.minosoft.data.accounts.AccountType
import de.bixilon.minosoft.data.registries.ResourceLocation
import de.bixilon.minosoft.util.KUtil.asResourceLocation
import java.util.*
class MicrosoftAccount(
override val id: String,
username: String,
val uuid: UUID,
val email: String,
@Json(name = "access_token") private var accessToken: String,
) : Account(username) {
override val type: ResourceLocation = RESOURCE_LOCATION
override fun join(serverId: String) {
TODO()
}
override fun logout() {
TODO()
}
override fun verify() {
TODO()
}
companion object : AccountType(MicrosoftAccount::class) {
override val RESOURCE_LOCATION: ResourceLocation = "minosoft:microsoft_account".asResourceLocation()
}
}

View File

@ -0,0 +1,134 @@
/*
* Minosoft
* Copyright (C) 2021 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.accounts.types
import com.squareup.moshi.Json
import de.bixilon.minosoft.Minosoft
import de.bixilon.minosoft.data.accounts.Account
import de.bixilon.minosoft.data.accounts.AccountType
import de.bixilon.minosoft.data.registries.ResourceLocation
import de.bixilon.minosoft.util.KUtil.asResourceLocation
import de.bixilon.minosoft.util.KUtil.nullCast
import de.bixilon.minosoft.util.KUtil.trim
import de.bixilon.minosoft.util.KUtil.unsafeCast
import de.bixilon.minosoft.util.http.HTTP2.postJson
import de.bixilon.minosoft.util.http.exceptions.AuthenticationException
import de.bixilon.minosoft.util.logging.Log
import de.bixilon.minosoft.util.logging.LogLevels
import de.bixilon.minosoft.util.logging.LogMessageType
import de.bixilon.minosoft.util.nbt.tag.NBTUtil.asCompound
import java.util.*
class MojangAccount(
override val id: String,
username: String,
val uuid: UUID,
val email: String,
@Json(name = "access_token") private var accessToken: String,
) : Account(username) {
private var refreshed: Boolean = false
override val type: ResourceLocation = RESOURCE_LOCATION
override fun join(serverId: String) {
val response = mutableMapOf(
"accessToken" to accessToken,
"selectedProfile" to uuid.trim(),
"serverId" to serverId,
).postJson(MOJANG_URL_JOIN)
response.body!!
if (response.statusCode != 204) {
throw AuthenticationException(response.statusCode, response.body["errorMessage"]?.nullCast())
}
Log.log(LogMessageType.AUTHENTICATION, LogLevels.VERBOSE) { "Mojang server join successful (username=$username, serverId=$serverId)" }
}
override fun logout() {
val response = mutableMapOf(
"accessToken" to accessToken,
"clientToken" to Minosoft.config.config.account.clientToken,
).postJson(MOJANG_URL_INVALIDATE)
if (response.statusCode != 200) {
throw AuthenticationException(response.statusCode)
}
Log.log(LogMessageType.AUTHENTICATION, LogLevels.VERBOSE) { "Mojang account login successful (username=$username)" }
}
override fun verify() {
if (refreshed) {
return
}
refresh()
}
fun refresh() {
val response = mutableMapOf(
"accessToken" to accessToken,
"clientToken" to Minosoft.config.config.account.clientToken,
).postJson(MOJANG_URL_REFRESH)
response.body!!
if (response.statusCode != 200) {
throw AuthenticationException(response.statusCode, response.body["errorMessage"].nullCast())
}
this.accessToken = response.body["accessToken"].unsafeCast()
refreshed = true
Log.log(LogMessageType.AUTHENTICATION, LogLevels.VERBOSE) { "Mojang account refresh successful (username=$username)" }
}
companion object : AccountType(MojangAccount::class) {
private const val MOJANG_URL_LOGIN = "https://authserver.mojang.com/authenticate"
private const val MOJANG_URL_JOIN = "https://sessionserver.mojang.com/session/minecraft/join"
private const val MOJANG_URL_REFRESH = "https://authserver.mojang.com/refresh"
private const val MOJANG_URL_INVALIDATE = "https://authserver.mojang.com/invalidate"
override val RESOURCE_LOCATION: ResourceLocation = "minosoft:mojang_account".asResourceLocation()
fun login(clientToken: String = Minosoft.config.config.account.clientToken, email: String, password: String): MojangAccount {
val response = mutableMapOf(
"agent" to mutableMapOf(
"name" to "Minecraft",
"version" to 1,
),
"username" to email,
"password" to password,
"clientToken" to clientToken,
"requestUser" to true,
).postJson(MOJANG_URL_LOGIN)
response.body!!
if (response.statusCode != 200) {
throw AuthenticationException(response.statusCode, response.body["errorMessage"]?.nullCast())
}
Log.log(LogMessageType.AUTHENTICATION, LogLevels.VERBOSE) { "Mojang login successful (email=$email)" }
return MojangAccount(
id = response.body["user"].asCompound()["id"].unsafeCast(),
username = response.body["selectedProfile"].asCompound()["name"].unsafeCast(),
uuid = response.body["selectedProfile"].asCompound()["id"].unsafeCast(),
email = email,
accessToken = response.body["accessToken"].unsafeCast(),
)
}
}
}

View File

@ -0,0 +1,34 @@
/*
* Minosoft
* Copyright (C) 2021 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.accounts.types
import de.bixilon.minosoft.data.accounts.Account
import de.bixilon.minosoft.data.accounts.AccountType
import de.bixilon.minosoft.data.registries.ResourceLocation
import de.bixilon.minosoft.util.KUtil.asResourceLocation
class OfflineAccount(username: String) : Account(username) {
override val id: String = username
override val type: ResourceLocation = RESOURCE_LOCATION
override fun join(serverId: String) {}
override fun logout() {}
override fun verify() {}
companion object : AccountType(OfflineAccount::class) {
override val RESOURCE_LOCATION: ResourceLocation = "minosoft:offline_account".asResourceLocation()
}
}

View File

@ -25,11 +25,13 @@ object Eros {
private val TITLE = "minosoft:eros_window_title".asResourceLocation()
private val LAYOUT = "minosoft:eros/main/main.fxml".asResourceLocation()
lateinit var mainErosController: MainErosController
init {
Minosoft.GLOBAL_EVENT_MASTER.registerEvent(CallbackEventInvoker.of<FinishInitializingEvent> {
Platform.runLater {
val mainErosController = JavaFXUtil.openModal<MainErosController>(TITLE, LAYOUT)
mainErosController = JavaFXUtil.openModal<MainErosController>(TITLE, LAYOUT)
mainErosController.stage.show()
}
})

View File

@ -35,4 +35,6 @@ abstract class JavaFXController : Initializable {
}
open fun init() {}
open fun postInit() {}
}

View File

@ -17,7 +17,4 @@ import javafx.stage.Stage
abstract class JavaFXWindowController : JavaFXController() {
lateinit var stage: Stage
open fun postInit() {}
}

View File

@ -82,6 +82,7 @@ class ErosCrashReport : JavaFXWindowController() {
}
companion object {
private var alreadyCrashed = false
private val CRASH_REPORT_COMMENTS = listOf(
"Let's blame Bixilon for this",
"But it worked once",
@ -111,22 +112,10 @@ class ErosCrashReport : JavaFXWindowController() {
* Special: Does not use any general functions/translations/..., because when a crash happens, you can't rely on anything.
*/
fun Throwable?.crash() {
if (RunConfiguration.DISABLE_EROS) {
ShutdownManager.shutdown(this?.message, ShutdownReasons.CRITICAL_EXCEPTION)
if (alreadyCrashed) {
return
}
if (!JavaFXInitializer.initializing && !JavaFXInitializer.initialized) {
try {
JavaFXInitializer.start()
} catch (exception: Throwable) {
Log.log(LogMessageType.JAVAFX, LogLevels.WARN) { "Can not show crash report screen!" }
exception.printStackTrace()
return
}
}
JavaFXInitializer.await()
alreadyCrashed = true
// Kill some stuff
tryCatch(executor = { DefaultThreadPool.shutdownNow() })
@ -149,6 +138,23 @@ class ErosCrashReport : JavaFXWindowController() {
crashReportPath = null
}
if (RunConfiguration.DISABLE_EROS) {
ShutdownManager.shutdown(this?.message, ShutdownReasons.CRITICAL_EXCEPTION)
return
}
if (!JavaFXInitializer.initializing && !JavaFXInitializer.initialized) {
try {
JavaFXInitializer.start()
} catch (exception: Throwable) {
Log.log(LogMessageType.JAVAFX, LogLevels.WARN) { "Can not show crash report screen!" }
exception.printStackTrace()
return
}
}
JavaFXInitializer.await()
Platform.runLater {
val fxmlLoader = FXMLLoader(ErosCrashReport::class.java.getResource("/assets/minosoft/eros/crash/crash_screen.fxml"))
val parent = fxmlLoader.load<Parent>()
@ -175,15 +181,16 @@ class ErosCrashReport : JavaFXWindowController() {
// ${CRASH_REPORT_COMMENTS.random()}
Time: ${SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(System.currentTimeMillis())} (${System.currentTimeMillis() / 1000L})
Crash thread: ${Thread.currentThread().name}
${exception?.toStackTrace() ?: ""}
-- Runtime Details --
Start arguments: ${CommandLineArguments.ARGUMENTS}
JVM Flags: ${ManagementFactory.getRuntimeMXBean().inputArguments}
JVM flags: ${ManagementFactory.getRuntimeMXBean().inputArguments}
Home directory: ${RunConfiguration.HOME_DIRECTORY}
Disable Eros: ${RunConfiguration.DISABLE_EROS}
Disable Rendering: ${RunConfiguration.DISABLE_RENDERING}
Disable rendering: ${RunConfiguration.DISABLE_RENDERING}
-- System Details --
Operating system: ${SystemInformation.OS_TEXT}

View File

@ -13,11 +13,15 @@
package de.bixilon.minosoft.gui.eros.main
import de.bixilon.minosoft.Minosoft
import de.bixilon.minosoft.ShutdownReasons
import de.bixilon.minosoft.config.StaticConfiguration
import de.bixilon.minosoft.data.accounts.Account
import de.bixilon.minosoft.gui.eros.controller.JavaFXWindowController
import de.bixilon.minosoft.gui.eros.main.play.PlayMainController
import de.bixilon.minosoft.gui.eros.modding.invoker.JavaFXEventInvoker
import de.bixilon.minosoft.gui.eros.util.JavaFXUtil
import de.bixilon.minosoft.modding.event.events.account.AccountSelectEvent
import de.bixilon.minosoft.util.GitInfo
import de.bixilon.minosoft.util.KUtil.asResourceLocation
import de.bixilon.minosoft.util.KUtil.decide
@ -34,13 +38,11 @@ import org.kordamp.ikonli.javafx.FontIcon
class MainErosController : JavaFXWindowController() {
@FXML
private lateinit var logoFX: ImageView
@FXML
private lateinit var versionTextFX: Text
@FXML
private lateinit var playIconFX: FontIcon
@FXML
private lateinit var settingsIconFX: FontIcon
@ -56,6 +58,12 @@ class MainErosController : JavaFXWindowController() {
@FXML
private lateinit var contentFX: Pane
@FXML
private lateinit var accountImageFX: ImageView
@FXML
private lateinit var accountNameFX: Text
private lateinit var icons: List<FontIcon>
@ -84,6 +92,12 @@ class MainErosController : JavaFXWindowController() {
}
contentFX.children.setAll(JavaFXUtil.loadEmbeddedController<PlayMainController>("minosoft:eros/main/play/play.fxml".asResourceLocation()).root)
Minosoft.GLOBAL_EVENT_MASTER.registerEvent(JavaFXEventInvoker.of<AccountSelectEvent> {
accountImageFX.image = JavaFXUtil.MINOSOFT_LOGO // ToDo
accountNameFX.text = it.account?.username
})
}
override fun postInit() {
@ -91,4 +105,16 @@ class MainErosController : JavaFXWindowController() {
ShutdownManager.shutdown(reason = ShutdownReasons.REQUESTED_BY_USER)
}
}
fun requestAccountSelect() {
TODO("Not yet implemented")
}
fun verifyAccount(account: Account? = Minosoft.config.config.account.selected, onSuccess: (Account) -> Unit) {
if (account == null) {
requestAccountSelect()
return
}
TODO("Not yet implemented")
}
}

View File

@ -16,13 +16,19 @@ package de.bixilon.minosoft.gui.eros.main.play.server
import de.bixilon.minosoft.Minosoft
import de.bixilon.minosoft.config.server.Server
import de.bixilon.minosoft.data.registries.ResourceLocation
import de.bixilon.minosoft.data.registries.versions.Versions
import de.bixilon.minosoft.data.text.ChatComponent
import de.bixilon.minosoft.gui.eros.Eros
import de.bixilon.minosoft.gui.eros.controller.EmbeddedJavaFXController
import de.bixilon.minosoft.gui.eros.main.play.server.card.ServerCard
import de.bixilon.minosoft.gui.eros.main.play.server.card.ServerCardController
import de.bixilon.minosoft.gui.eros.modding.invoker.JavaFXEventInvoker
import de.bixilon.minosoft.modding.event.events.status.StatusConnectionUpdateEvent
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection
import de.bixilon.minosoft.util.DNSUtil
import de.bixilon.minosoft.util.KUtil.asResourceLocation
import de.bixilon.minosoft.util.KUtil.decide
import de.bixilon.minosoft.util.task.pool.DefaultThreadPool
import javafx.fxml.FXML
import javafx.geometry.HPos
import javafx.geometry.Insets
@ -63,13 +69,20 @@ class ServerListController : EmbeddedJavaFXController<Pane>() {
}
}
override fun postInit() {
root.setOnKeyPressed { serverListViewFX.selectionModel.select(null) } // ToDo: Only on escape; not working
}
@FXML
fun refresh() {
val selected = serverListViewFX.selectionModel.selectedItem
serverListViewFX.items.clear()
for (server in Minosoft.config.config.server.entries.values) {
updateServer(server)
}
serverListViewFX.selectionModel.select(serverListViewFX.items.contains(selected).decide(selected, null))
}
private fun updateServer(server: Server) {
@ -121,8 +134,10 @@ class ServerListController : EmbeddedJavaFXController<Pane>() {
var row = 0
for ((key, property) in SERVER_INFO_PROPERTIES) {
val propertyValue = property(serverCard.server) ?: continue
it.add(Minosoft.LANGUAGE_MANAGER.translate(key).textFlow, 0, row)
it.add(ChatComponent.of(property(serverCard.server)).textFlow, 1, row++)
it.add(ChatComponent.of(propertyValue).textFlow, 1, row++)
}
it.columnConstraints += ColumnConstraints(10.0, 100.0, 150.0)
@ -138,7 +153,14 @@ class ServerListController : EmbeddedJavaFXController<Pane>() {
it.add(Button("Delete"), 1, 0)
it.add(Button("Edit"), 2, 0)
it.add(Button("Connect"), 3, 0)
it.add(Button("Connect").apply {
setOnAction {
Eros.mainErosController.verifyAccount { account ->
val connection = PlayConnection(serverCard.server.ping?.realAddress ?: DNSUtil.getServerAddress(serverCard.server.address), account, Versions.getVersionById(serverCard.server.forcedVersion!!)) // ToDo: Get ping version
DefaultThreadPool += { connection.connect() }
}
}
}, 3, 0)
it.hgap = 5.0
@ -153,9 +175,10 @@ class ServerListController : EmbeddedJavaFXController<Pane>() {
private companion object {
private val SERVER_INFO_PROPERTIES: Map<ResourceLocation, (server: Server) -> Any> = mapOf(
"minosoft:server_name".asResourceLocation() to { it.name },
"minosoft:server_address".asResourceLocation() to { it.address },
private val SERVER_INFO_PROPERTIES: Map<ResourceLocation, (server: Server) -> Any?> = mapOf(
"minosoft:server.info.server_name".asResourceLocation() to { it.name },
"minosoft:server.info.server_address".asResourceLocation() to { it.address },
"minosoft:server.info.forced_version".asResourceLocation() to { it.forcedVersion?.let { version -> Versions.getVersionById(version)!! } },
)
}
}

View File

@ -19,6 +19,7 @@ import de.bixilon.minosoft.modding.event.invoker.EventInstantFireable
import de.bixilon.minosoft.modding.event.invoker.EventInvoker
import de.bixilon.minosoft.modding.loading.Priorities
import javafx.application.Platform
import kotlin.reflect.KClass
/**
* Basically a CallbackEventInvoker, bt the callback runs on the java fx ui thread
@ -26,6 +27,7 @@ import javafx.application.Platform
class JavaFXEventInvoker<E : Event> private constructor(
ignoreCancelled: Boolean,
private val callback: (E) -> Unit,
override val kEventType: KClass<out Event>,
override val eventType: Class<out Event>,
override val instantFire: Boolean,
) : EventInvoker(ignoreCancelled, Priorities.NORMAL, null), EventInstantFireable {
@ -46,6 +48,7 @@ class JavaFXEventInvoker<E : Event> private constructor(
return JavaFXEventInvoker(
ignoreCancelled = ignoreCancelled,
callback = callback,
kEventType = E::class,
eventType = E::class.java,
instantFire = instantFire,
)

View File

@ -14,6 +14,7 @@
package de.bixilon.minosoft.gui.eros.util
import de.bixilon.minosoft.Minosoft
import de.bixilon.minosoft.gui.eros.crash.ErosCrashReport.Companion.crash
import de.bixilon.minosoft.util.CountUpAndDownLatch
import de.bixilon.minosoft.util.KUtil.asResourceLocation
import de.bixilon.minosoft.util.logging.Log
@ -49,6 +50,7 @@ class JavaFXInitializer internal constructor() : Application() {
@Synchronized
fun start() {
check(LATCH.count == 2) { "Already initialized!" }
Thread.setDefaultUncaughtExceptionHandler { _, exception -> exception.crash() }
Log.log(LogMessageType.JAVAFX, LogLevels.VERBOSE) { "Initializing JavaFX Toolkit..." }
Thread({ Application.launch(JavaFXInitializer::class.java) }, "JavaFX Toolkit Initializing Thread").start()

View File

@ -1,6 +1,6 @@
/*
* Minosoft
* Copyright (C) 2020 Moritz Zwerger
* Copyright (C) 2021 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
@ -11,14 +11,11 @@
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.util.mojang.api.exceptions;
package de.bixilon.minosoft.modding.event
public class MojangJoinServerErrorException extends Exception {
import de.bixilon.minosoft.modding.event.events.Event
public MojangJoinServerErrorException(String message) {
super(message);
}
interface EventInstantFire<T : Event> {
public MojangJoinServerErrorException() {
}
fun fire(): T
}

View File

@ -0,0 +1,31 @@
/*
* Minosoft
* Copyright (C) 2020 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.modding.event.events.account
import de.bixilon.minosoft.Minosoft
import de.bixilon.minosoft.data.accounts.Account
import de.bixilon.minosoft.modding.event.EventInstantFire
import de.bixilon.minosoft.modding.event.events.Event
class AccountSelectEvent(
val previous: Account?,
val account: Account?,
) : Event() {
companion object : EventInstantFire<AccountSelectEvent> {
override fun fire(): AccountSelectEvent {
return AccountSelectEvent(null, Minosoft.config.config.account.selected)
}
}
}

View File

@ -15,10 +15,12 @@ package de.bixilon.minosoft.modding.event.invoker
import de.bixilon.minosoft.modding.event.events.CancelableEvent
import de.bixilon.minosoft.modding.event.events.Event
import de.bixilon.minosoft.modding.loading.Priorities
import kotlin.reflect.KClass
class CallbackEventInvoker<E : Event> private constructor(
ignoreCancelled: Boolean,
private val callback: (E) -> Unit,
override val kEventType: KClass<out Event>,
override val eventType: Class<out Event>,
override val instantFire: Boolean,
) : EventInvoker(ignoreCancelled, Priorities.NORMAL, null), EventInstantFireable {
@ -37,6 +39,7 @@ class CallbackEventInvoker<E : Event> private constructor(
return CallbackEventInvoker(
ignoreCancelled = ignoreCancelled,
callback = callback,
kEventType = E::class,
eventType = E::class.java,
instantFire = instantFire,
)

View File

@ -15,13 +15,15 @@ package de.bixilon.minosoft.modding.event.invoker
import de.bixilon.minosoft.modding.event.EventListener
import de.bixilon.minosoft.modding.event.events.Event
import de.bixilon.minosoft.modding.loading.Priorities
import kotlin.reflect.KClass
abstract class EventInvoker(
val isIgnoreCancelled: Boolean,
val priority: Priorities,
protected val listener: EventListener?,
) {
abstract val eventType: Class<out Event?>
abstract val kEventType: KClass<out Event>?
abstract val eventType: Class<out Event>
abstract operator fun invoke(event: Event)
}

View File

@ -18,6 +18,7 @@ import de.bixilon.minosoft.modding.event.events.Event
import de.bixilon.minosoft.modding.event.events.annotations.EventHandler
import de.bixilon.minosoft.modding.loading.Priorities
import java.lang.reflect.Method
import kotlin.reflect.KClass
class EventInvokerMethod(
ignoreCancelled: Boolean,
@ -25,6 +26,7 @@ class EventInvokerMethod(
listener: EventListener,
val method: Method,
) : EventInvoker(ignoreCancelled, priority, listener) {
override val kEventType: KClass<out Event>? = null
override val eventType: Class<out Event> = method.parameters[0].type as Class<out Event>
constructor(annotation: EventHandler, listener: EventListener, method: Method) : this(annotation.ignoreCancelled, annotation.priority, listener, method)

View File

@ -13,12 +13,15 @@
package de.bixilon.minosoft.modding.event.master
import de.bixilon.minosoft.modding.event.EventInstantFire
import de.bixilon.minosoft.modding.event.events.CancelableEvent
import de.bixilon.minosoft.modding.event.events.Event
import de.bixilon.minosoft.modding.event.invoker.EventInstantFireable
import de.bixilon.minosoft.modding.event.invoker.EventInvoker
import de.bixilon.minosoft.util.KUtil.synchronizedSetOf
import de.bixilon.minosoft.util.KUtil.toSynchronizedList
import de.bixilon.minosoft.util.KUtil.toSynchronizedSet
import kotlin.reflect.full.companionObjectInstance
open class EventMaster(vararg parents: AbstractEventMaster) : AbstractEventMaster {
val parents: MutableSet<AbstractEventMaster> = synchronizedSetOf(*parents)
@ -62,10 +65,20 @@ open class EventMaster(vararg parents: AbstractEventMaster) : AbstractEventMaste
override fun registerEvent(invoker: EventInvoker) {
eventInvokers += invoker
if (invoker is EventInstantFireable && invoker.instantFire) {
val companion = invoker.kEventType?.companionObjectInstance ?: return
if (companion is EventInstantFire<*>) {
invoker.invoke(companion.fire())
}
}
}
override fun registerEvents(vararg invoker: EventInvoker) {
eventInvokers += invoker
override fun registerEvents(vararg invokers: EventInvoker) {
for (invoker in invokers) {
registerEvent(invoker)
}
}
override fun iterator(): Iterator<EventInvoker> {

View File

@ -175,7 +175,7 @@ class PlayConnection(
}
}
fun connect(latch: CountUpAndDownLatch) {
fun connect(latch: CountUpAndDownLatch = CountUpAndDownLatch(1)) {
// Log.log(LogMessageType.OTHER, LogLevels.VERBOSE){TranslatableComponents.HELLO_WORLD(Minosoft.LANGUAGE_MANAGER, "Moritz", 17)}
try {
fireEvent(RegistriesLoadEvent(this, registries, RegistriesLoadEvent.States.PRE))

View File

@ -64,8 +64,8 @@ class StatusConnection(
field = value
value?.let {
fireEvent(StatusConnectionErrorEvent(this, EventInitiators.UNKNOWN, it))
pingStatus = StatusConnectionStatuses.ERROR
}
pingStatus = StatusConnectionStatuses.ERROR
}

View File

@ -26,7 +26,7 @@ class LoginKickS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket() {
override fun handle(connection: PlayConnection) {
connection.fireEvent(LoginKickEvent(connection, this))
Log.log(LogMessageType.NETWORK_STATUS, level = LogLevels.WARN) { "Kicked from: $reason" }
Log.log(LogMessageType.NETWORK_STATUS, level = LogLevels.WARN) { "Kicked from ${connection.address}: $reason" }
connection.disconnect()
}

View File

@ -249,7 +249,7 @@ open class InByteBuffer {
}
fun readJson(): Map<String, Any> {
return JSONSerializer.MAP_ADAPTER.fromJson(readString())!!
return JSONSerializer.MUTABLE_MAP_ADAPTER.fromJson(readString())!!
}
fun readJsonArray(length: Int = readVarInt()): Array<Map<String, Any>> {

View File

@ -72,10 +72,6 @@ public final class ProtocolDefinition {
public static final String MOJANG_URL_PACKAGES = "https://launchermeta.mojang.com/v1/packages/%s/%s";
public static final String MOJANG_LAUNCHER_URL_PACKAGES = "https://launcher.mojang.com/v1/objects/%s/%s";
public static final String MOJANG_URL_BLOCKED_SERVERS = "https://sessionserver.mojang.com/blockedservers";
public static final String MOJANG_URL_LOGIN = "https://authserver.mojang.com/authenticate";
public static final String MOJANG_URL_JOIN = "https://sessionserver.mojang.com/session/minecraft/join";
public static final String MOJANG_URL_REFRESH = "https://authserver.mojang.com/refresh";
public static final String MICROSOFT_ACCOUNT_APPLICATION_ID = "00000000402b5328"; // ToDo: Should we use our own application id?
// public static final String MICROSOFT_ACCOUNT_APPLICATION_ID = "fe6f0fbf-3038-486a-9c84-6a28b71e0455";

View File

@ -24,6 +24,7 @@ import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.HashMap;
@Deprecated
public final class HTTP {
public static HttpResponse<String> postJson(String url, String json, HashMap<String, String> headers) throws IOException, InterruptedException {

View File

@ -17,6 +17,7 @@ import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonWriter
import de.bixilon.minosoft.data.entities.entities.Entity
import de.bixilon.minosoft.data.registries.ResourceLocation
import de.bixilon.minosoft.data.registries.ResourceLocationAble
import de.bixilon.minosoft.data.text.ChatColors
import de.bixilon.minosoft.data.text.ChatComponent
import de.bixilon.minosoft.data.text.TextComponent
@ -169,6 +170,22 @@ object KUtil {
}
}
fun <T> Boolean.decide(`true`: T, `false`: () -> T): T {
return if (this) {
`true`
} else {
`false`()
}
}
fun <T> Boolean.decide(`true`: () -> T, `false`: T): T {
return if (this) {
`true`()
} else {
`false`
}
}
fun String.asUUID(): UUID {
return Util.getUUIDFromString(this)
}
@ -325,4 +342,19 @@ object KUtil {
val Class<*>.realName: String
get() = this.name.removePrefix(this.packageName).removePrefix(".")
fun UUID.trim(): String {
return this.toString().replace("-", "")
}
fun <T : ResourceLocationAble> List<T>.asResourceLocationMap(): Map<ResourceLocation, T> {
val ret: MutableMap<ResourceLocation, T> = mutableMapOf()
for (value in this) {
ret[value.resourceLocation] = value
}
return ret.toMap()
}
}

View File

@ -0,0 +1,62 @@
/*
* Minosoft
* Copyright (C) 2021 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.util.http
import de.bixilon.minosoft.util.KUtil.extend
import de.bixilon.minosoft.util.json.JSONSerializer
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
object HTTP2 {
fun Map<String, Any>.headers(): Array<String> {
val headers: MutableList<String> = mutableListOf()
for ((key, value) in this) {
headers += key
headers += value.toString()
}
return headers.toTypedArray()
}
fun <Payload, Response> post(url: String, data: Payload, bodyPublisher: (Payload) -> String, bodyBuilder: (String) -> Response, headers: Map<String, Any> = mapOf()): HTTPResponse<Response> {
val client = HttpClient.newHttpClient()
val request = HttpRequest.newBuilder()
.uri(URI.create(url))
.POST(HttpRequest.BodyPublishers.ofString(bodyPublisher(data)))
.headers(*headers.headers())
.build()
val response = client.send(request, HttpResponse.BodyHandlers.ofString())
return HTTPResponse(response.statusCode(), bodyBuilder(response.body()))
}
fun Map<String, Any>.postJson(url: String, headers: Map<String, Any> = mapOf()): HTTPResponse<Map<String, Any>?> {
return post(
url = url,
data = this,
bodyPublisher = { JSONSerializer.MAP_ADAPTER.toJson(it) },
bodyBuilder = { JSONSerializer.MAP_ADAPTER.fromJson(it) },
headers = headers.extend(
"Content-Type" to "application/json",
"Accept" to "application/json",
)
)
}
}

View File

@ -0,0 +1,19 @@
/*
* Minosoft
* Copyright (C) 2021 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.util.http
class HTTPResponse<T>(
val statusCode: Int,
val body: T,
)

View File

@ -10,12 +10,9 @@
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.util.http.exceptions
package de.bixilon.minosoft.util.mojang.api.exceptions;
public class AuthenticationException extends Exception {
public AuthenticationException(String message) {
super(message);
}
}
class AuthenticationException(
statusCode: Int,
message: String? = null,
) : HTTPException(statusCode, message)

View File

@ -0,0 +1,19 @@
/*
* Minosoft
* Copyright (C) 2021 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.util.http.exceptions
open class HTTPException(
val statusCode: Int,
message: String? = null,
) : Exception(message)

View File

@ -14,25 +14,22 @@
package de.bixilon.minosoft.util.json
import com.squareup.moshi.FromJson
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.ToJson
import de.bixilon.minosoft.data.accounts.Account
import de.bixilon.minosoft.data.accounts.MicrosoftAccount
import de.bixilon.minosoft.data.accounts.MojangAccount
import de.bixilon.minosoft.data.accounts.OfflineAccount
import de.bixilon.minosoft.data.accounts.AccountTypes
import de.bixilon.minosoft.util.KUtil.asResourceLocation
import de.bixilon.minosoft.util.KUtil.unsafeCast
import de.bixilon.minosoft.util.nbt.tag.NBTUtil.asCompound
object AccountSerializer {
@FromJson
fun fromJson(json: Map<String, Any>): Account {
return when (json["type"]!!) {
"mojang" -> MojangAccount.deserialize(json)
"offline" -> OfflineAccount.deserialize(json)
"microsoft" -> MicrosoftAccount.deserialize(json)
else -> throw IllegalArgumentException("Invalid account type: ${json["type"]}")
}
return AccountTypes.ACCOUNT_TYPES[json["type"]!!.asResourceLocation()]!!.TYPE.fromJsonValue(json)!!
}
@ToJson
fun toJson(account: Account): Map<String, Any> {
return account.serialize()
return AccountTypes.ACCOUNT_TYPES[account.type]!!.TYPE.unsafeCast<JsonAdapter<Account>>().toJsonValue(account).asCompound()
}
}

View File

@ -21,19 +21,21 @@ import de.bixilon.minosoft.config.config.Config
import de.bixilon.minosoft.gui.rendering.textures.properties.ImageProperties
object JSONSerializer {
private val MOSHI = Moshi.Builder()
val MOSHI = Moshi.Builder()
.add(RGBColorSerializer)
.add(Vec2Serializer)
.add(AccountSerializer)
.add(ChatComponentSerializer)
.add(ServerAddressSerializer)
.add(ResourceLocationSerializer)
.add(UUIDSerializer)
.add(KotlinJsonAdapterFactory())
.build()!!
val ANY_ADAPTER = MOSHI.adapter(Any::class.java)!!
val CONFIG_ADAPTER = MOSHI.adapter(Config::class.java)!!
val MAP_ADAPTER: JsonAdapter<MutableMap<String, Any>> = MOSHI.adapter(Types.newParameterizedType(MutableMap::class.java, String::class.java, Any::class.java))
val MUTABLE_MAP_ADAPTER: JsonAdapter<MutableMap<String, Any>> = MOSHI.adapter(Types.newParameterizedType(MutableMap::class.java, String::class.java, Any::class.java))
val MAP_ADAPTER: JsonAdapter<Map<String, Any>> = MOSHI.adapter(Types.newParameterizedType(Map::class.java, String::class.java, Any::class.java))
val IMAGE_PROPERTIES_ADAPTER = MOSHI.adapter(ImageProperties::class.java)!!
}

View File

@ -0,0 +1,37 @@
/*
* Minosoft
* Copyright (C) 2020 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.util.json
import com.squareup.moshi.*
import de.bixilon.minosoft.util.Util
import java.util.*
object UUIDSerializer : JsonAdapter<UUID>() {
@FromJson
override fun fromJson(jsonReader: JsonReader): UUID? {
if (jsonReader.peek() == JsonReader.Token.NULL) {
return null
}
return Util.getUUIDFromString(jsonReader.nextString())
}
@ToJson
override fun toJson(jsonWriter: JsonWriter, uuid: UUID?) {
if (uuid == null) {
jsonWriter.nullValue()
return
}
jsonWriter.value(uuid.toString())
}
}

View File

@ -30,6 +30,8 @@ enum class LogMessageType(
VERSION_LOADING(ChatColors.YELLOW),
ASSETS(ChatColors.BLACK),
AUTHENTICATION(ChatColors.BLACK),
NETWORK_RESOLVING(ChatColors.DARK_GREEN),
NETWORK_STATUS(ChatColors.DARK_GREEN),
NETWORK_PACKETS_IN(ChatColors.BLUE, mapOf(

View File

@ -14,8 +14,7 @@
package de.bixilon.minosoft.util.microsoft
import com.google.gson.JsonParser
import de.bixilon.minosoft.data.accounts.Account
import de.bixilon.minosoft.data.accounts.MicrosoftAccount
import de.bixilon.minosoft.data.accounts.types.MicrosoftAccount
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
import de.bixilon.minosoft.terminal.RunConfiguration
import de.bixilon.minosoft.util.HTTP
@ -38,7 +37,7 @@ object MicrosoftOAuthUtils {
val xstsToken = getXSTSToken(xboxLiveToken.first)
val microsoftAccount = getMicrosoftAccount(getMinecraftAccessToken(xboxLiveToken.second, xstsToken))
Account.addAccount(microsoftAccount)
// ToDo: Account.addAccount(microsoftAccount)
} catch (exception: Exception) {
Log.warn("Can not login into microsoft account")
exception.printStackTrace()
@ -162,7 +161,8 @@ object MicrosoftOAuthUtils {
}
val body = JsonParser.parseString(response.body()).asJsonObject
return MicrosoftAccount(bearerToken, body["id"].asString!!, Util.getUUIDFromString(body["id"].asString!!), body["name"].asString!!)
// return MicrosoftAccount(bearerToken, body["id"].asString!!, Util.getUUIDFromString(body["id"].asString!!), body["name"].asString!!)
TODO()
}
init {

View File

@ -1,125 +0,0 @@
/*
* Minosoft
* Copyright (C) 2020 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.util.mojang.api;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import de.bixilon.minosoft.Minosoft;
import de.bixilon.minosoft.data.accounts.MojangAccount;
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition;
import de.bixilon.minosoft.util.HTTP;
import de.bixilon.minosoft.util.logging.Log;
import de.bixilon.minosoft.util.logging.LogMessageType;
import de.bixilon.minosoft.util.mojang.api.exceptions.AuthenticationException;
import de.bixilon.minosoft.util.mojang.api.exceptions.MojangJoinServerErrorException;
import de.bixilon.minosoft.util.mojang.api.exceptions.NoNetworkConnectionException;
import java.io.IOException;
import java.net.http.HttpResponse;
public final class MojangAuthentication {
public static MojangAccount login(String username, String password) throws AuthenticationException, NoNetworkConnectionException {
return login(Minosoft.config.getConfig().getAccount().getClientToken(), username, password);
}
public static MojangAccount login(String clientToken, String username, String password) throws NoNetworkConnectionException, AuthenticationException {
JsonObject agent = new JsonObject();
agent.addProperty("name", "Minecraft");
agent.addProperty("version", 1);
JsonObject payload = new JsonObject();
payload.add("agent", agent);
payload.addProperty("username", username);
payload.addProperty("password", password);
payload.addProperty("clientToken", clientToken);
payload.addProperty("requestUser", true);
HttpResponse<String> response;
try {
response = HTTP.postJson(ProtocolDefinition.MOJANG_URL_LOGIN, payload);
} catch (IOException | InterruptedException e) {
Log.printException(e, LogMessageType.OTHER);
throw new NoNetworkConnectionException(e);
}
if (response == null) {
Log.mojang(String.format("Failed to login with username %s", username));
throw new NoNetworkConnectionException("Unknown error, check your Internet connection");
}
JsonObject jsonResponse = JsonParser.parseString(response.body()).getAsJsonObject();
if (response.statusCode() != 200) {
Log.mojang(String.format("Failed to login with error code %d: %s", response.statusCode(), jsonResponse.get("errorMessage").getAsString()));
throw new AuthenticationException(jsonResponse.get("errorMessage").getAsString());
}
// now it is okay
return new MojangAccount(username, jsonResponse);
}
public static void joinServer(MojangAccount account, String serverId) throws NoNetworkConnectionException, MojangJoinServerErrorException {
JsonObject payload = new JsonObject();
payload.addProperty("accessToken", account.getAccessToken());
payload.addProperty("selectedProfile", account.getUUID().toString().replace("-", ""));
payload.addProperty("serverId", serverId);
HttpResponse<String> response;
try {
response = HTTP.postJson(ProtocolDefinition.MOJANG_URL_JOIN, payload);
} catch (IOException | InterruptedException e) {
throw new NoNetworkConnectionException(e);
}
if (response == null) {
Log.mojang(String.format("Failed to join server: %s", serverId));
throw new MojangJoinServerErrorException();
}
if (response.statusCode() != 204) {
JsonObject jsonResponse = JsonParser.parseString(response.body()).getAsJsonObject();
Log.mojang(String.format("Failed to join server with error code %d: %s", response.statusCode(), jsonResponse.has("errorMessage") ? jsonResponse.get("errorMessage").getAsString() : "null"));
throw new MojangJoinServerErrorException(jsonResponse.get("errorMessage").getAsString());
}
// joined
Log.mojang("Joined server successfully");
}
public static String refresh(String accessToken) throws NoNetworkConnectionException, AuthenticationException {
return refresh(Minosoft.config.getConfig().getAccount().getClientToken(), accessToken);
}
public static String refresh(String clientToken, String accessToken) throws NoNetworkConnectionException, AuthenticationException {
JsonObject payload = new JsonObject();
payload.addProperty("accessToken", accessToken);
payload.addProperty("clientToken", clientToken);
HttpResponse<String> response;
try {
response = HTTP.postJson(ProtocolDefinition.MOJANG_URL_REFRESH, payload);
} catch (IOException | InterruptedException e) {
Log.mojang(String.format("Could not connect to mojang server: %s", e.getCause().toString()));
throw new NoNetworkConnectionException(e);
}
if (response == null) {
Log.mojang("Failed to refresh session");
throw new NoNetworkConnectionException();
}
JsonObject jsonResponse = JsonParser.parseString(response.body()).getAsJsonObject();
if (response.statusCode() != 200) {
Log.mojang(String.format("Failed to refresh session with error code %d: %s", response.statusCode(), jsonResponse.get("errorMessage").getAsString()));
throw new AuthenticationException(jsonResponse.get("errorMessage").getAsString());
}
// now it is okay
Log.mojang("Refreshed 1 session token");
return jsonResponse.get("accessToken").getAsString();
}
}

View File

@ -14,6 +14,11 @@
package de.bixilon.minosoft.util.nbt.tag
object NBTUtil {
fun compound(): MutableMap<String, Any> {
return mutableMapOf()
}
fun MutableMap<String, Any>.getAndRemove(key: String): Any? {
val value = this[key]
this.remove(key)

View File

@ -5,7 +5,7 @@
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.Text?>
<?import org.kordamp.ikonli.javafx.*?>
<HBox xmlns:fx="http://javafx.com/fxml/1" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="600.0" prefWidth="1000.0" fx:controller="de.bixilon.minosoft.gui.eros.main.MainErosController">
<HBox xmlns:fx="http://javafx.com/fxml/1" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="600.0" prefWidth="1000.0" xmlns="http://javafx.com/javafx/16" fx:controller="de.bixilon.minosoft.gui.eros.main.MainErosController">
<GridPane HBox.hgrow="ALWAYS">
<columnConstraints>
<ColumnConstraints hgrow="ALWAYS" minWidth="10.0" prefWidth="100.0"/>
@ -77,7 +77,21 @@
</GridPane.margin>
</FontIcon>
</GridPane>
<Text text="Account" GridPane.columnIndex="3"/>
<GridPane GridPane.columnIndex="3">
<columnConstraints>
<ColumnConstraints hgrow="NEVER" minWidth="10.0"/>
<ColumnConstraints hgrow="NEVER"/>
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" vgrow="ALWAYS"/>
</rowConstraints>
<ImageView fx:id="accountImageFX" fitHeight="30.0" fitWidth="30.0" pickOnBounds="true" preserveRatio="true" GridPane.hgrow="ALWAYS" GridPane.vgrow="ALWAYS">
<GridPane.margin>
<Insets right="5.0"/>
</GridPane.margin>
</ImageView>
<Text fx:id="accountNameFX" text="Account" GridPane.columnIndex="1"/>
</GridPane>
<GridPane.margin>
<Insets bottom="3.0" left="5.0" right="5.0" top="3.0"/>
</GridPane.margin>

View File

@ -1,7 +1,9 @@
minosoft:hello.world=§aHi, my name is §e%1$s§a. I am §e%2$s §ayears old and I want to say the following: §cHello world!
minosoft:eros_window_title=Minosoft
minosoft:server_name=Server name
minosoft:server_address=Server address
minosoft:server.info.server_name=Server name
minosoft:server.info.server_address=Server address
minosoft:server.info.forced_version=Forced version
minosoft:status.connection.state.waiting=Waiting...
minosoft:status.connection.state.resolving=Resolving hostname...