diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..aaa5f11 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,262 @@ +root = true + +[*] +charset = utf-8 +end_of_line = crlf +indent_size = 4 +indent_style = space +insert_final_newline = true +tab_width = 4 +trim_trailing_whitespace = false +ij_continuation_indent_size = 8 + +[*.properties] +ij_any_keep_blank_lines_in_code = unset + +[*.java] +ij_java_align_consecutive_assignments = false +ij_java_align_consecutive_variable_declarations = false +ij_java_align_group_field_declarations = false +ij_java_align_multiline_annotation_parameters = true +ij_java_align_multiline_array_initializer_expression = false +ij_java_align_multiline_assignment = false +ij_java_align_multiline_binary_operation = true +ij_java_align_multiline_chained_methods = false +ij_java_align_multiline_extends_list = false +ij_java_align_multiline_for = true +ij_java_align_multiline_method_parentheses = false +ij_java_align_multiline_parameters = false +ij_java_align_multiline_parameters_in_calls = false +ij_java_align_multiline_parenthesized_expression = false +ij_java_align_multiline_records = true +ij_java_align_multiline_resources = true +ij_java_align_multiline_ternary_operation = false +ij_java_align_multiline_text_blocks = false +ij_java_align_multiline_throws_list = false +ij_java_align_subsequent_simple_methods = false +ij_java_align_throws_keyword = false +ij_java_annotation_parameter_wrap = normal +ij_java_array_initializer_new_line_after_left_brace = false +ij_java_array_initializer_right_brace_on_new_line = false +ij_java_array_initializer_wrap = off +ij_java_assert_statement_colon_on_next_line = false +ij_java_assert_statement_wrap = off +ij_java_assignment_wrap = off +ij_java_binary_operation_sign_on_next_line = false +ij_java_binary_operation_wrap = off +ij_java_blank_lines_after_anonymous_class_header = 0 +ij_java_blank_lines_after_class_header = 0 +ij_java_blank_lines_after_imports = 1 +ij_java_blank_lines_after_package = 1 +ij_java_blank_lines_around_class = 1 +ij_java_blank_lines_around_field = 0 +ij_java_blank_lines_around_field_in_interface = 0 +ij_java_blank_lines_around_initializer = 1 +ij_java_blank_lines_around_method = 1 +ij_java_blank_lines_around_method_in_interface = 1 +ij_java_blank_lines_before_class_end = 0 +ij_java_blank_lines_before_imports = 1 +ij_java_blank_lines_before_method_body = 0 +ij_java_blank_lines_before_package = 0 +ij_java_block_brace_style = end_of_line +ij_java_block_comment_at_first_column = true +ij_java_call_parameters_new_line_after_left_paren = false +ij_java_call_parameters_right_paren_on_new_line = false +ij_java_call_parameters_wrap = off +ij_java_case_statement_on_separate_line = true +ij_java_catch_on_new_line = false +ij_java_class_annotation_wrap = split_into_lines +ij_java_class_brace_style = end_of_line +ij_java_class_count_to_use_import_on_demand = 5 +ij_java_class_names_in_javadoc = 1 +ij_java_do_not_indent_top_level_class_members = false +ij_java_do_not_wrap_after_single_annotation = false +ij_java_do_while_brace_force = never +ij_java_doc_add_blank_line_after_description = true +ij_java_doc_add_blank_line_after_param_comments = false +ij_java_doc_add_blank_line_after_return = false +ij_java_doc_add_p_tag_on_empty_lines = true +ij_java_doc_align_exception_comments = true +ij_java_doc_align_param_comments = true +ij_java_doc_do_not_wrap_if_one_line = false +ij_java_doc_enable_formatting = true +ij_java_doc_enable_leading_asterisks = true +ij_java_doc_indent_on_continuation = false +ij_java_doc_keep_empty_lines = true +ij_java_doc_keep_empty_parameter_tag = true +ij_java_doc_keep_empty_return_tag = true +ij_java_doc_keep_empty_throws_tag = true +ij_java_doc_keep_invalid_tags = true +ij_java_doc_param_description_on_new_line = false +ij_java_doc_preserve_line_breaks = false +ij_java_doc_use_throws_not_exception_tag = true +ij_java_else_on_new_line = false +ij_java_entity_dd_suffix = EJB +ij_java_entity_eb_suffix = Bean +ij_java_entity_hi_suffix = Home +ij_java_entity_lhi_prefix = Local +ij_java_entity_lhi_suffix = Home +ij_java_entity_li_prefix = Local +ij_java_entity_pk_class = java.lang.String +ij_java_entity_vo_suffix = VO +ij_java_enum_constants_wrap = split_into_lines +ij_java_extends_keyword_wrap = off +ij_java_extends_list_wrap = off +ij_java_field_annotation_wrap = on_every_item +ij_java_finally_on_new_line = false +ij_java_for_brace_force = never +ij_java_for_statement_new_line_after_left_paren = false +ij_java_for_statement_right_paren_on_new_line = false +ij_java_for_statement_wrap = off +ij_java_generate_final_locals = false +ij_java_generate_final_parameters = false +ij_java_if_brace_force = never +ij_java_imports_layout = *, |, javax.**, java.**, |, $* +ij_java_indent_case_from_switch = true +ij_java_insert_inner_class_imports = false +ij_java_insert_override_annotation = true +ij_java_keep_blank_lines_before_right_brace = 2 +ij_java_keep_blank_lines_between_package_declaration_and_header = 2 +ij_java_keep_blank_lines_in_code = 2 +ij_java_keep_blank_lines_in_declarations = 2 +ij_java_keep_control_statement_in_one_line = true +ij_java_keep_first_column_comment = true +ij_java_keep_indents_on_empty_lines = true +ij_java_keep_line_breaks = true +ij_java_keep_multiple_expressions_in_one_line = false +ij_java_keep_simple_blocks_in_one_line = false +ij_java_keep_simple_classes_in_one_line = true +ij_java_keep_simple_lambdas_in_one_line = true +ij_java_keep_simple_methods_in_one_line = true +ij_java_label_indent_absolute = false +ij_java_label_indent_size = 0 +ij_java_lambda_brace_style = end_of_line +ij_java_layout_static_imports_separately = true +ij_java_line_comment_add_space = false +ij_java_line_comment_at_first_column = true +ij_java_message_dd_suffix = EJB +ij_java_message_eb_suffix = Bean +ij_java_method_annotation_wrap = split_into_lines +ij_java_method_brace_style = end_of_line +ij_java_method_call_chain_wrap = off +ij_java_method_parameters_new_line_after_left_paren = false +ij_java_method_parameters_right_paren_on_new_line = false +ij_java_method_parameters_wrap = off +ij_java_modifier_list_wrap = false +ij_java_names_count_to_use_import_on_demand = 3 +ij_java_new_line_after_lparen_in_record_header = false +ij_java_parameter_annotation_wrap = normal +ij_java_parentheses_expression_new_line_after_left_paren = false +ij_java_parentheses_expression_right_paren_on_new_line = false +ij_java_place_assignment_sign_on_next_line = false +ij_java_prefer_longer_names = true +ij_java_prefer_parameters_wrap = false +ij_java_record_components_wrap = normal +ij_java_repeat_synchronized = true +ij_java_replace_instanceof_and_cast = false +ij_java_replace_null_check = true +ij_java_replace_sum_lambda_with_method_ref = true +ij_java_resource_list_new_line_after_left_paren = false +ij_java_resource_list_right_paren_on_new_line = false +ij_java_resource_list_wrap = off +ij_java_rparen_on_new_line_in_record_header = false +ij_java_session_dd_suffix = EJB +ij_java_session_eb_suffix = Bean +ij_java_session_hi_suffix = Home +ij_java_session_lhi_prefix = Local +ij_java_session_lhi_suffix = Home +ij_java_session_li_prefix = Local +ij_java_session_si_suffix = Service +ij_java_space_after_closing_angle_bracket_in_type_argument = false +ij_java_space_after_colon = true +ij_java_space_after_comma = true +ij_java_space_after_comma_in_type_arguments = true +ij_java_space_after_for_semicolon = true +ij_java_space_after_quest = true +ij_java_space_after_type_cast = true +ij_java_space_before_annotation_array_initializer_left_brace = false +ij_java_space_before_annotation_parameter_list = false +ij_java_space_before_array_initializer_left_brace = false +ij_java_space_before_catch_keyword = true +ij_java_space_before_catch_left_brace = true +ij_java_space_before_catch_parentheses = true +ij_java_space_before_class_left_brace = true +ij_java_space_before_colon = true +ij_java_space_before_colon_in_foreach = true +ij_java_space_before_comma = false +ij_java_space_before_do_left_brace = true +ij_java_space_before_else_keyword = true +ij_java_space_before_else_left_brace = true +ij_java_space_before_finally_keyword = true +ij_java_space_before_finally_left_brace = true +ij_java_space_before_for_left_brace = true +ij_java_space_before_for_parentheses = true +ij_java_space_before_for_semicolon = false +ij_java_space_before_if_left_brace = true +ij_java_space_before_if_parentheses = true +ij_java_space_before_method_call_parentheses = false +ij_java_space_before_method_left_brace = true +ij_java_space_before_method_parentheses = false +ij_java_space_before_opening_angle_bracket_in_type_parameter = false +ij_java_space_before_quest = true +ij_java_space_before_switch_left_brace = true +ij_java_space_before_switch_parentheses = true +ij_java_space_before_synchronized_left_brace = true +ij_java_space_before_synchronized_parentheses = true +ij_java_space_before_try_left_brace = true +ij_java_space_before_try_parentheses = true +ij_java_space_before_type_parameter_list = false +ij_java_space_before_while_keyword = true +ij_java_space_before_while_left_brace = true +ij_java_space_before_while_parentheses = true +ij_java_space_inside_one_line_enum_braces = false +ij_java_space_within_empty_array_initializer_braces = false +ij_java_space_within_empty_method_call_parentheses = false +ij_java_space_within_empty_method_parentheses = false +ij_java_spaces_around_additive_operators = true +ij_java_spaces_around_assignment_operators = true +ij_java_spaces_around_bitwise_operators = true +ij_java_spaces_around_equality_operators = true +ij_java_spaces_around_lambda_arrow = true +ij_java_spaces_around_logical_operators = true +ij_java_spaces_around_method_ref_dbl_colon = false +ij_java_spaces_around_multiplicative_operators = true +ij_java_spaces_around_relational_operators = true +ij_java_spaces_around_shift_operators = true +ij_java_spaces_around_type_bounds_in_type_parameters = true +ij_java_spaces_around_unary_operator = false +ij_java_spaces_within_angle_brackets = false +ij_java_spaces_within_annotation_parentheses = false +ij_java_spaces_within_array_initializer_braces = false +ij_java_spaces_within_braces = false +ij_java_spaces_within_brackets = false +ij_java_spaces_within_cast_parentheses = false +ij_java_spaces_within_catch_parentheses = false +ij_java_spaces_within_for_parentheses = false +ij_java_spaces_within_if_parentheses = false +ij_java_spaces_within_method_call_parentheses = false +ij_java_spaces_within_method_parentheses = false +ij_java_spaces_within_parentheses = false +ij_java_spaces_within_record_header = false +ij_java_spaces_within_switch_parentheses = false +ij_java_spaces_within_synchronized_parentheses = false +ij_java_spaces_within_try_parentheses = false +ij_java_spaces_within_while_parentheses = false +ij_java_special_else_if_treatment = true +ij_java_subclass_name_suffix = Impl +ij_java_ternary_operation_signs_on_next_line = false +ij_java_ternary_operation_wrap = off +ij_java_test_name_suffix = Test +ij_java_throws_keyword_wrap = normal +ij_java_throws_list_wrap = off +ij_java_use_external_annotations = false +ij_java_use_fq_class_names = false +ij_java_use_relative_indents = false +ij_java_use_single_class_imports = true +ij_java_variable_annotation_wrap = normal +ij_java_visibility = public +ij_java_while_brace_force = never +ij_java_while_on_new_line = false +ij_java_wrap_comments = false +ij_java_wrap_first_method_in_call_chain = false +ij_java_wrap_long_lines = false \ No newline at end of file diff --git a/README.md b/README.md index 748ef97..b23874c 100644 --- a/README.md +++ b/README.md @@ -1,49 +1,59 @@ # SkinRestorer + ![GitHub License](https://img.shields.io/github/license/Suiranoil/SkinRestorer) ![Build](https://github.com/Suiranoil/SkinRestorer/actions/workflows/build.yml/badge.svg) ![Enviroment](https://img.shields.io/badge/enviroment-server-orangered) [![CurseForge Downloads](https://cf.way2muchnoise.eu/skinrestorer.svg)](https://www.curseforge.com/minecraft/mc-mods/skinrestorer) [![Modrinth Downloads](https://img.shields.io/modrinth/dt/skinrestorer?logo=modrinth)](https://modrinth.com/mod/skinrestorer) - -SkinRestorer is a **server-side** only mod for Fabric that allows players to use and change skins on servers running in offline/insecure mode. +SkinRestorer is a **server-side** only mod for Fabric that allows players to use and change skins on servers running in +offline/insecure mode. ## Features + - **Set Skins from Mojang Account**: Fetch and apply skins using a valid Minecraft account name. - **Set Skins from URL**: Apply skins from any image URL, supporting both classic (Steve) and slim (Alex) skin models. ## Command Usage Guide ### Set Mojang Skin + ``` /skin set mojang [] ``` + - **Parameters:** - - ``: Minecraft account name to fetch the skin from. - - `[]`: (Optional, server operators only) Player(s) to apply the skin to. + - ``: Minecraft account name to fetch the skin from. + - `[]`: (Optional, server operators only) Player(s) to apply the skin to. ### Set Web Skin + ``` /skin set web (classic|slim) "" [] ``` + - **Parameters:** - - `(classic|slim)`: Type of the skin model (`classic` for Steve model, `slim` for Alex model). - - `""`: URL pointing to the skin image file (ensure it follows Minecraft's skin size and format requirements). - - `[]`: (Optional, server operators only) Player(s) to apply the skin to. + - `(classic|slim)`: Type of the skin model (`classic` for Steve model, `slim` for Alex model). + - `""`: URL pointing to the skin image file (ensure it follows Minecraft's skin size and format requirements). + - `[]`: (Optional, server operators only) Player(s) to apply the skin to. ### Clear Skin + ``` /skin clear [] ``` + - **Parameters:** - - `[]`: (Optional, server operators only) Player(s) to clear the skin for. + - `[]`: (Optional, server operators only) Player(s) to clear the skin for. ### Notes: + - If `targets` is not specified, the command will apply to the player executing the command. ### Examples: + ``` /skin set mojang Notch -/skin set web classic "http://example.com/skin.png" +/skin set web classic "https://example.com/skin.png" /skin clear @a ``` diff --git a/src/main/java/net/lionarius/skinrestorer/MineskinSkinProvider.java b/src/main/java/net/lionarius/skinrestorer/MineskinSkinProvider.java index da9089f..df35974 100644 --- a/src/main/java/net/lionarius/skinrestorer/MineskinSkinProvider.java +++ b/src/main/java/net/lionarius/skinrestorer/MineskinSkinProvider.java @@ -10,19 +10,19 @@ import java.io.IOException; import java.net.URL; public class MineskinSkinProvider { - + private static final String API = "https://api.mineskin.org/generate/url"; private static final String USER_AGENT = "SkinRestorer"; private static final String TYPE = "application/json"; - + public static SkinResult getSkin(String url, SkinVariant variant) { try { String input = ("{\"variant\":\"%s\",\"name\":\"%s\",\"visibility\":%d,\"url\":\"%s\"}") .formatted(variant.toString(), "none", 1, url); - + JsonObject texture = JsonUtils.parseJson(WebUtils.POSTRequest(new URL(API), USER_AGENT, TYPE, TYPE, input)) .getAsJsonObject("data").getAsJsonObject("texture"); - + return SkinResult.success(new Property("textures", texture.get("value").getAsString(), texture.get("signature").getAsString())); } catch (IOException e) { return SkinResult.error(e); diff --git a/src/main/java/net/lionarius/skinrestorer/MojangSkinProvider.java b/src/main/java/net/lionarius/skinrestorer/MojangSkinProvider.java index 8f58f07..c73a95b 100644 --- a/src/main/java/net/lionarius/skinrestorer/MojangSkinProvider.java +++ b/src/main/java/net/lionarius/skinrestorer/MojangSkinProvider.java @@ -10,22 +10,22 @@ import java.net.URL; import java.util.UUID; public class MojangSkinProvider { - + private static final String API = "https://api.mojang.com/users/profiles/minecraft/"; private static final String SESSION_SERVER = "https://sessionserver.mojang.com/session/minecraft/profile/"; - + public static SkinResult getSkin(String name) { try { UUID uuid = getUUID(name); JsonObject texture = JsonUtils.parseJson(WebUtils.GETRequest(new URL(SESSION_SERVER + uuid + "?unsigned=false"))) .getAsJsonArray("properties").get(0).getAsJsonObject(); - + return SkinResult.success(new Property("textures", texture.get("value").getAsString(), texture.get("signature").getAsString())); } catch (Exception e) { return SkinResult.error(e); } } - + private static UUID getUUID(String name) throws IOException { return UUID.fromString(JsonUtils.parseJson(WebUtils.GETRequest(new URL(API + name))).get("id").getAsString() .replaceFirst("(\\p{XDigit}{8})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}+)", "$1-$2-$3-$4-$5")); diff --git a/src/main/java/net/lionarius/skinrestorer/SkinIO.java b/src/main/java/net/lionarius/skinrestorer/SkinIO.java index c4ee0be..0c7fa27 100644 --- a/src/main/java/net/lionarius/skinrestorer/SkinIO.java +++ b/src/main/java/net/lionarius/skinrestorer/SkinIO.java @@ -8,23 +8,23 @@ import java.nio.file.Path; import java.util.UUID; public class SkinIO { - + private static final String FILE_EXTENSION = ".json"; - + private final Path savePath; - + public SkinIO(Path savePath) { this.savePath = savePath; } - + public boolean skinExists(UUID uuid) { return savePath.resolve(uuid + FILE_EXTENSION).toFile().exists(); } - + public Property loadSkin(UUID uuid) { return JsonUtils.fromJson(FileUtils.readFile(savePath.resolve(uuid + FILE_EXTENSION).toFile()), Property.class); } - + public void saveSkin(UUID uuid, Property skin) { FileUtils.writeFile(savePath.toFile(), uuid + FILE_EXTENSION, JsonUtils.toJson(skin)); } diff --git a/src/main/java/net/lionarius/skinrestorer/SkinRestorer.java b/src/main/java/net/lionarius/skinrestorer/SkinRestorer.java index 2326bb8..4c33c9f 100644 --- a/src/main/java/net/lionarius/skinrestorer/SkinRestorer.java +++ b/src/main/java/net/lionarius/skinrestorer/SkinRestorer.java @@ -23,32 +23,32 @@ import java.util.concurrent.TimeUnit; import java.util.function.Supplier; public class SkinRestorer implements DedicatedServerModInitializer { - + private static SkinStorage skinStorage; - + public static final Logger LOGGER = LoggerFactory.getLogger("SkinRestorer"); - + public static SkinStorage getSkinStorage() { return skinStorage; } - + @Override public void onInitializeServer() { skinStorage = new SkinStorage(new SkinIO(FabricLoader.getInstance().getConfigDir().resolve("skinrestorer"))); } - + public static void refreshPlayer(ServerPlayerEntity player) { ServerWorld serverWorld = player.getServerWorld(); PlayerManager playerManager = serverWorld.getServer().getPlayerManager(); ServerChunkManager chunkManager = serverWorld.getChunkManager(); - + playerManager.sendToAll(new BundleS2CPacket( List.of( new PlayerRemoveS2CPacket(List.of(player.getUuid())), PlayerListS2CPacket.entryFromPlayer(Collections.singleton(player)) ) )); - + if (!player.isDead()) { chunkManager.unloadEntity(player); chunkManager.loadEntity(player); @@ -68,7 +68,7 @@ public class SkinRestorer implements DedicatedServerModInitializer { playerManager.sendStatusEffects(player); } } - + public static CompletableFuture, Collection>> setSkinAsync(MinecraftServer server, Collection targets, Supplier skinSupplier) { return CompletableFuture.>>supplyAsync(() -> { SkinResult result = skinSupplier.get(); @@ -76,28 +76,28 @@ public class SkinRestorer implements DedicatedServerModInitializer { SkinRestorer.LOGGER.error("Could not get skin", result.getError()); return Pair.of(null, Collections.emptySet()); } - + Property skin = result.getSkin(); - + for (GameProfile profile : targets) { SkinRestorer.getSkinStorage().setSkin(profile.getId(), skin); } - + HashSet acceptedProfiles = new HashSet<>(targets); - + return Pair.of(skin, acceptedProfiles); })., Collection>>thenApplyAsync(pair -> { Property skin = pair.left(); // NullPtrException will be caught by 'exceptionally' - + Collection acceptedProfiles = pair.right(); HashSet acceptedPlayers = new HashSet<>(); - + for (GameProfile profile : acceptedProfiles) { ServerPlayerEntity player = server.getPlayerManager().getPlayer(profile.getId()); - + if (player == null || areSkinPropertiesEquals(skin, getPlayerSkin(player))) continue; - + applyRestoredSkin(player.getGameProfile(), skin); refreshPlayer(player); acceptedPlayers.add(player); @@ -107,48 +107,48 @@ public class SkinRestorer implements DedicatedServerModInitializer { .orTimeout(10, TimeUnit.SECONDS) .exceptionally(e -> Pair.of(Collections.emptySet(), Collections.emptySet())); } - + public static void applyRestoredSkin(GameProfile profile, Property skin) { profile.getProperties().removeAll("textures"); - + if (skin != null) profile.getProperties().put("textures", skin); } - + private static Property getPlayerSkin(ServerPlayerEntity player) { return player.getGameProfile().getProperties().get("textures").stream().findFirst().orElse(null); } - + private static final Gson gson = new Gson(); - + private static JsonObject skinPropertyToJson(Property property) { try { JsonObject json = gson.fromJson(new String(Base64.getDecoder().decode(property.value()), StandardCharsets.UTF_8), JsonObject.class); if (json != null) json.remove("timestamp"); - + return json; } catch (Exception ex) { return null; } } - + private static boolean areSkinPropertiesEquals(Property x, Property y) { if (x == y) return true; - + if (x == null || y == null) return false; - + if (x.equals(y)) return true; - + JsonObject xJson = skinPropertyToJson(x); JsonObject yJson = skinPropertyToJson(y); - + if (xJson == null || yJson == null) return false; - + return xJson.equals(yJson); } } diff --git a/src/main/java/net/lionarius/skinrestorer/SkinResult.java b/src/main/java/net/lionarius/skinrestorer/SkinResult.java index fbfed06..a0c96d5 100644 --- a/src/main/java/net/lionarius/skinrestorer/SkinResult.java +++ b/src/main/java/net/lionarius/skinrestorer/SkinResult.java @@ -6,40 +6,40 @@ import org.jetbrains.annotations.NotNull; public class SkinResult { private final Property skin; private final Exception exception; - + private SkinResult(Property skin, Exception exception) { this.skin = skin; this.exception = exception; } - + public Property getSkin() { return this.skin; } - + public Exception getError() { return this.exception; } - + public boolean isError() { return this.exception != null; } - + public static SkinResult empty() { return new SkinResult(null, null); } - + public static SkinResult error(Exception e) { return new SkinResult(null, e); } - + public static SkinResult success(@NotNull Property skin) { return new SkinResult(skin, null); } - + public static SkinResult ofNullable(Property skin) { if (skin == null) return SkinResult.empty(); - + return SkinResult.success(skin); } } diff --git a/src/main/java/net/lionarius/skinrestorer/SkinStorage.java b/src/main/java/net/lionarius/skinrestorer/SkinStorage.java index d56d85a..5c719d6 100644 --- a/src/main/java/net/lionarius/skinrestorer/SkinStorage.java +++ b/src/main/java/net/lionarius/skinrestorer/SkinStorage.java @@ -8,32 +8,32 @@ import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; public class SkinStorage { - + private final Map> skinMap = new ConcurrentHashMap<>(); private final SkinIO skinIO; - + public SkinStorage(SkinIO skinIO) { this.skinIO = skinIO; } - + public boolean hasSavedSkin(UUID uuid) { return this.skinIO.skinExists(uuid); } - + public Property getSkin(UUID uuid) { if (!skinMap.containsKey(uuid)) { Property skin = skinIO.loadSkin(uuid); setSkin(uuid, skin); } - + return skinMap.get(uuid).orElse(null); } - + public void removeSkin(UUID uuid) { if (skinMap.containsKey(uuid)) skinIO.saveSkin(uuid, skinMap.get(uuid).orElse(null)); } - + public void setSkin(UUID uuid, Property skin) { skinMap.put(uuid, Optional.ofNullable(skin)); } diff --git a/src/main/java/net/lionarius/skinrestorer/command/SkinCommand.java b/src/main/java/net/lionarius/skinrestorer/command/SkinCommand.java index 2b957f4..eeb108d 100644 --- a/src/main/java/net/lionarius/skinrestorer/command/SkinCommand.java +++ b/src/main/java/net/lionarius/skinrestorer/command/SkinCommand.java @@ -22,7 +22,7 @@ import static net.minecraft.server.command.CommandManager.argument; import static net.minecraft.server.command.CommandManager.literal; public class SkinCommand { - + public static void register(CommandDispatcher dispatcher) { dispatcher.register(literal("skin") .then(literal("set") @@ -63,22 +63,22 @@ public class SkinCommand { SkinResult::empty)))) ); } - + private static int skinAction(ServerCommandSource src, Collection targets, boolean setByOperator, Supplier skinSupplier) { SkinRestorer.setSkinAsync(src.getServer(), targets, skinSupplier).thenAccept(pair -> { Collection profiles = pair.right(); Collection players = pair.left(); - + if (profiles.isEmpty()) { src.sendError(Text.of(TranslationUtils.translation.skinActionFailed)); return; } - + if (setByOperator) { src.sendFeedback(() -> Text.of( String.format(TranslationUtils.translation.skinActionAffectedProfile, String.join(", ", profiles.stream().map(GameProfile::getName).toList()))), true); - + if (!players.isEmpty()) { src.sendFeedback(() -> Text.of( String.format(TranslationUtils.translation.skinActionAffectedPlayer, @@ -90,11 +90,11 @@ public class SkinCommand { }); return targets.size(); } - + private static int skinAction(ServerCommandSource src, Supplier skinSupplier) { if (src.getPlayer() == null) return 0; - + return skinAction(src, Collections.singleton(src.getPlayer().getGameProfile()), false, skinSupplier); } } diff --git a/src/main/java/net/lionarius/skinrestorer/enums/SkinVariant.java b/src/main/java/net/lionarius/skinrestorer/enums/SkinVariant.java index 01a4ecf..9d85775 100644 --- a/src/main/java/net/lionarius/skinrestorer/enums/SkinVariant.java +++ b/src/main/java/net/lionarius/skinrestorer/enums/SkinVariant.java @@ -1,16 +1,16 @@ package net.lionarius.skinrestorer.enums; public enum SkinVariant { - + CLASSIC("classic"), SLIM("slim"); - + private final String name; - + SkinVariant(String name) { this.name = name; } - + @Override public String toString() { return name; diff --git a/src/main/java/net/lionarius/skinrestorer/mixin/CommandManagerMixin.java b/src/main/java/net/lionarius/skinrestorer/mixin/CommandManagerMixin.java index 790ada7..d300656 100644 --- a/src/main/java/net/lionarius/skinrestorer/mixin/CommandManagerMixin.java +++ b/src/main/java/net/lionarius/skinrestorer/mixin/CommandManagerMixin.java @@ -14,11 +14,12 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(CommandManager.class) public abstract class CommandManagerMixin { - + @Final @Shadow private CommandDispatcher dispatcher; - - @Inject(method = "", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/command/AdvancementCommand;register(Lcom/mojang/brigadier/CommandDispatcher;)V")) + + @Inject(method = "", at = @At(value = "INVOKE", + target = "Lnet/minecraft/server/command/AdvancementCommand;register(Lcom/mojang/brigadier/CommandDispatcher;)V")) private void init(CommandManager.RegistrationEnvironment environment, CommandRegistryAccess commandRegistryAccess, CallbackInfo ci) { SkinCommand.register(dispatcher); } diff --git a/src/main/java/net/lionarius/skinrestorer/mixin/PlayerManagerMixin.java b/src/main/java/net/lionarius/skinrestorer/mixin/PlayerManagerMixin.java index d3c09f8..7567ea8 100644 --- a/src/main/java/net/lionarius/skinrestorer/mixin/PlayerManagerMixin.java +++ b/src/main/java/net/lionarius/skinrestorer/mixin/PlayerManagerMixin.java @@ -19,25 +19,25 @@ import java.util.List; @Mixin(PlayerManager.class) public abstract class PlayerManagerMixin { - + @Shadow public abstract List getPlayerList(); - + @Shadow @Final private MinecraftServer server; - + @Inject(method = "remove", at = @At("TAIL")) private void remove(ServerPlayerEntity player, CallbackInfo ci) { SkinRestorer.getSkinStorage().removeSkin(player.getUuid()); } - + @Inject(method = "disconnectAllPlayers", at = @At("HEAD")) private void disconnectAllPlayers(CallbackInfo ci) { for (ServerPlayerEntity player : getPlayerList()) { SkinRestorer.getSkinStorage().removeSkin(player.getUuid()); } } - + @Inject(method = "onPlayerConnect", at = @At("HEAD")) private void onPlayerConnected(ClientConnection connection, ServerPlayerEntity player, ConnectedClientData clientData, CallbackInfo ci) { SkinRestorer.setSkinAsync(server, Collections.singleton(player.getGameProfile()), () -> SkinResult.ofNullable(SkinRestorer.getSkinStorage().getSkin(player.getUuid()))); diff --git a/src/main/java/net/lionarius/skinrestorer/mixin/ServerLoginNetworkHandlerMixin.java b/src/main/java/net/lionarius/skinrestorer/mixin/ServerLoginNetworkHandlerMixin.java index e043d89..49c8871 100644 --- a/src/main/java/net/lionarius/skinrestorer/mixin/ServerLoginNetworkHandlerMixin.java +++ b/src/main/java/net/lionarius/skinrestorer/mixin/ServerLoginNetworkHandlerMixin.java @@ -17,30 +17,32 @@ import java.util.concurrent.CompletableFuture; @Mixin(ServerLoginNetworkHandler.class) public abstract class ServerLoginNetworkHandlerMixin { - + @Shadow @Nullable private GameProfile profile; - + @Unique private CompletableFuture skinrestorer_pendingSkin; - - @Inject(method = "tickVerify", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/PlayerManager;checkCanJoin(Ljava/net/SocketAddress;Lcom/mojang/authlib/GameProfile;)Lnet/minecraft/text/Text;"), cancellable = true) + + @Inject(method = "tickVerify", at = @At(value = "INVOKE", + target = "Lnet/minecraft/server/PlayerManager;checkCanJoin(Ljava/net/SocketAddress;Lcom/mojang/authlib/GameProfile;)Lnet/minecraft/text/Text;"), + cancellable = true) public void waitForSkin(CallbackInfo ci) { if (skinrestorer_pendingSkin == null) { skinrestorer_pendingSkin = CompletableFuture.supplyAsync(() -> { SkinRestorer.LOGGER.debug("Fetching {}'s skin", profile.getName()); - + if (!SkinRestorer.getSkinStorage().hasSavedSkin(profile.getId())) { // when player joins for the first time fetch Mojang skin by his username SkinResult result = MojangSkinProvider.getSkin(profile.getName()); - + if (!result.isError()) SkinRestorer.getSkinStorage().setSkin(profile.getId(), result.getSkin()); } - + return SkinResult.ofNullable(SkinRestorer.getSkinStorage().getSkin(profile.getId())); }); } - + if (!skinrestorer_pendingSkin.isDone()) ci.cancel(); } diff --git a/src/main/java/net/lionarius/skinrestorer/util/FileUtils.java b/src/main/java/net/lionarius/skinrestorer/util/FileUtils.java index 800f8c8..30d91a0 100644 --- a/src/main/java/net/lionarius/skinrestorer/util/FileUtils.java +++ b/src/main/java/net/lionarius/skinrestorer/util/FileUtils.java @@ -4,7 +4,7 @@ import java.io.*; import java.nio.charset.StandardCharsets; public class FileUtils { - + public static String readFile(File file) { try (BufferedReader reader = new BufferedReader(new FileReader(file, StandardCharsets.UTF_8))) { return StringUtils.readString(reader); @@ -12,16 +12,16 @@ public class FileUtils { return null; } } - + public static boolean writeFile(File path, String fileName, String content) { try { if (!path.exists()) path.mkdirs(); - + File file = new File(path, fileName); if (!file.exists()) file.createNewFile(); - + try (FileWriter writer = new FileWriter(file, StandardCharsets.UTF_8)) { writer.write(content); } diff --git a/src/main/java/net/lionarius/skinrestorer/util/JsonUtils.java b/src/main/java/net/lionarius/skinrestorer/util/JsonUtils.java index 270d7ee..f138efa 100644 --- a/src/main/java/net/lionarius/skinrestorer/util/JsonUtils.java +++ b/src/main/java/net/lionarius/skinrestorer/util/JsonUtils.java @@ -6,17 +6,17 @@ import com.google.gson.JsonObject; import com.google.gson.JsonParser; public class JsonUtils { - + private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); - + public static T fromJson(String json, Class clazz) { return GSON.fromJson(json, clazz); } - + public static String toJson(Object obj) { return GSON.toJson(obj); } - + public static JsonObject parseJson(String json) { return JsonParser.parseString(json).getAsJsonObject(); } diff --git a/src/main/java/net/lionarius/skinrestorer/util/PlayerUtils.java b/src/main/java/net/lionarius/skinrestorer/util/PlayerUtils.java index 6728cb2..d915db2 100644 --- a/src/main/java/net/lionarius/skinrestorer/util/PlayerUtils.java +++ b/src/main/java/net/lionarius/skinrestorer/util/PlayerUtils.java @@ -3,7 +3,7 @@ package net.lionarius.skinrestorer.util; import net.minecraft.server.network.ServerPlayerEntity; public class PlayerUtils { - + public static boolean isFakePlayer(ServerPlayerEntity player) { return player.getClass() != ServerPlayerEntity.class; // if the player isn't a server player entity, it must be someone's fake player } diff --git a/src/main/java/net/lionarius/skinrestorer/util/StringUtils.java b/src/main/java/net/lionarius/skinrestorer/util/StringUtils.java index 1d68c43..87bf974 100644 --- a/src/main/java/net/lionarius/skinrestorer/util/StringUtils.java +++ b/src/main/java/net/lionarius/skinrestorer/util/StringUtils.java @@ -4,15 +4,15 @@ import java.io.BufferedReader; import java.io.IOException; public class StringUtils { - + public static String readString(BufferedReader reader) throws IOException { String inputLine; StringBuilder response = new StringBuilder(); - + while ((inputLine = reader.readLine()) != null) { response.append(inputLine); } - + return response.toString(); } } diff --git a/src/main/java/net/lionarius/skinrestorer/util/TranslationUtils.java b/src/main/java/net/lionarius/skinrestorer/util/TranslationUtils.java index a51a485..8e2baf6 100644 --- a/src/main/java/net/lionarius/skinrestorer/util/TranslationUtils.java +++ b/src/main/java/net/lionarius/skinrestorer/util/TranslationUtils.java @@ -14,12 +14,12 @@ public class TranslationUtils { public String skinActionFailed = "Failed to set skin"; public String skinActionOk = "Skin changed"; } - + public static Translation translation = new Translation(); - + static { Path path = FabricLoader.getInstance().getConfigDir().resolve("skinrestorer").resolve("translation.json"); - + if (Files.exists(path)) { try { translation = JsonUtils.fromJson(Objects.requireNonNull(FileUtils.readFile(path.toFile())), Translation.class); diff --git a/src/main/java/net/lionarius/skinrestorer/util/WebUtils.java b/src/main/java/net/lionarius/skinrestorer/util/WebUtils.java index 9435ebf..f88afad 100644 --- a/src/main/java/net/lionarius/skinrestorer/util/WebUtils.java +++ b/src/main/java/net/lionarius/skinrestorer/util/WebUtils.java @@ -9,32 +9,33 @@ import java.net.URL; import java.nio.charset.StandardCharsets; public class WebUtils { - - public static String POSTRequest(URL url, String userAgent, String contentType, String responseType, String input) throws IOException { + + public static String POSTRequest(URL url, String userAgent, String contentType, String responseType, String input) + throws IOException { HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - + connection.setRequestMethod("POST"); connection.setRequestProperty("Content-Type", contentType); connection.setRequestProperty("Accept", responseType); connection.setRequestProperty("User-Agent", userAgent); connection.setDoOutput(true); connection.setDoInput(true); - + try (OutputStream os = connection.getOutputStream()) { os.write(input.getBytes(StandardCharsets.UTF_8), 0, input.length()); } - + try (BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) { return StringUtils.readString(br); } } - + public static String GETRequest(URL url) throws IOException { HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - + connection.setRequestMethod("GET"); connection.setDoOutput(true); - + try (BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) { return StringUtils.readString(br); } diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 19a70de..e30ec88 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -1,29 +1,29 @@ { - "schemaVersion": 1, - "id": "skinrestorer", - "version": "${version}", - "name": "Skin Restorer", - "description": "A server-side mod for restoring skins on offline servers.", - "authors": [ - "Lionarius" - ], - "contact": { - "homepage": "https://modrinth.com/mod/skinrestorer", - "sources": "https://github.com/Suiranoil/SkinRestorer", - "issues": "https://github.com/Suiranoil/SkinRestorer/issues" - }, - "license": "MIT", - "environment": "server", - "entrypoints": { - "server": [ - "net.lionarius.skinrestorer.SkinRestorer" - ] - }, - "mixins": [ - "skinrestorer.mixins.json" - ], - "depends": { - "fabricloader": ">=0.15.10", - "minecraft": "*" - } + "schemaVersion": 1, + "id": "skinrestorer", + "version": "${version}", + "name": "Skin Restorer", + "description": "A server-side mod for restoring skins on offline servers.", + "authors": [ + "Lionarius" + ], + "contact": { + "homepage": "https://modrinth.com/mod/skinrestorer", + "sources": "https://github.com/Suiranoil/SkinRestorer", + "issues": "https://github.com/Suiranoil/SkinRestorer/issues" + }, + "license": "MIT", + "environment": "server", + "entrypoints": { + "server": [ + "net.lionarius.skinrestorer.SkinRestorer" + ] + }, + "mixins": [ + "skinrestorer.mixins.json" + ], + "depends": { + "fabricloader": ">=0.15.10", + "minecraft": "*" + } } diff --git a/src/main/resources/skinrestorer.mixins.json b/src/main/resources/skinrestorer.mixins.json index d03e075..ede775a 100644 --- a/src/main/resources/skinrestorer.mixins.json +++ b/src/main/resources/skinrestorer.mixins.json @@ -1,14 +1,14 @@ { - "required": true, - "minVersion": "0.8", - "package": "net.lionarius.skinrestorer.mixin", - "compatibilityLevel": "JAVA_8", - "mixins": [ - "CommandManagerMixin", - "PlayerManagerMixin", - "ServerLoginNetworkHandlerMixin" - ], - "injectors": { - "defaultRequire": 1 - } + "required": true, + "minVersion": "0.8", + "package": "net.lionarius.skinrestorer.mixin", + "compatibilityLevel": "JAVA_8", + "mixins": [ + "CommandManagerMixin", + "PlayerManagerMixin", + "ServerLoginNetworkHandlerMixin" + ], + "injectors": { + "defaultRequire": 1 + } }