From 639387cc38241b88c63893750da59f59a8419d6d Mon Sep 17 00:00:00 2001
From: Griefed <griefed@griefed.de>
Date: Tue, 24 Oct 2023 18:51:13 +0200
Subject: [PATCH 1/7] chore: Add more clientside mods. Thanks to @nvb-uy

---
 .../kotlin/de/griefed/serverpackcreator/api/ApiProperties.kt    | 2 ++
 .../src/jvmMain/resources/serverpackcreator.properties          | 2 +-
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/serverpackcreator-api/src/jvmMain/kotlin/de/griefed/serverpackcreator/api/ApiProperties.kt b/serverpackcreator-api/src/jvmMain/kotlin/de/griefed/serverpackcreator/api/ApiProperties.kt
index ac217a006..e7af9e4f4 100644
--- a/serverpackcreator-api/src/jvmMain/kotlin/de/griefed/serverpackcreator/api/ApiProperties.kt
+++ b/serverpackcreator-api/src/jvmMain/kotlin/de/griefed/serverpackcreator/api/ApiProperties.kt
@@ -257,6 +257,7 @@ actual class ApiProperties(
             "Reforgium-",
             "ResourceLoader-",
             "ResourcePackOrganizer",
+            "Ryoamiclights-",
             "ShoulderSurfing-",
             "ShulkerTooltip-",
             "SimpleDiscordRichPresence-",
@@ -417,6 +418,7 @@ actual class ApiProperties(
             "rubidium-",
             "rubidium_extras-",
             "screenshot-to-clipboard-",
+            "servercountryflags-",
             "shutupexperimentalsettings-",
             "shutupmodelloader-",
             "signtools-",
diff --git a/serverpackcreator-api/src/jvmMain/resources/serverpackcreator.properties b/serverpackcreator-api/src/jvmMain/resources/serverpackcreator.properties
index 47aa0b6fc..660084d62 100644
--- a/serverpackcreator-api/src/jvmMain/resources/serverpackcreator.properties
+++ b/serverpackcreator-api/src/jvmMain/resources/serverpackcreator.properties
@@ -2,7 +2,7 @@
 de.griefed.serverpackcreator.versioncheck.prerelease=false
 de.griefed.serverpackcreator.language=en_GB
 de.griefed.serverpackcreator.configuration.fallback.updateurl=https://raw.githubusercontent.com/Griefed/ServerPackCreator/main/serverpackcreator-api/src/jvmMain/resources/serverpackcreator.properties
-de.griefed.serverpackcreator.configuration.fallbackmodslist=3dskinlayers-,Absolutely-Not-A-Zoom-Mod-,AdvancedChat-,AdvancedChatCore-,AdvancedChatHUD-,AdvancedCompas-,Ambience,AmbientEnvironment-,AmbientSounds_,AreYouBlind-,Armor Status HUD-,ArmorSoundTweak-,BH-Menu-,Batty's Coordinates PLUS Mod,BetterAdvancements-,BetterAnimationsCollection-,BetterModsButton-,BetterDarkMode-,BetterF3-,BetterFog-,BetterFoliage-,BetterPingDisplay-,BetterPlacement-,BetterTaskbar-,BetterThirdPerson,BetterTitleScreen-,Blur-,BorderlessWindow-,CTM-,ChunkAnimator-,ClientTweaks_,CompletionistsIndex-,Controller Support-,Controlling-,CraftPresence-,Create_Questing-,CullLessLeaves-Reforged-,CustomCursorMod-,CustomMainMenu-,DefaultOptions_,DefaultSettings-,DeleteWorldsToTrash-,DetailArmorBar-,Ding-,DistantHorizons-,DripSounds-,Durability101-,DurabilityNotifier-,DynamicSurroundings-,DynamicSurroundingsHuds-,EffectsLeft-,EiraMoticons_,EnchantmentDescriptions-,EnhancedVisuals_,EquipmentCompare-,FPS-Monitor-,FabricCustomCursorMod-,Fallingleaves-,FancySpawnEggs,FancyVideo-API-,FirstPersonMod,FogTweaker-,ForgeCustomCursorMod-,FpsReducer-,FpsReducer2-,FullscreenWindowed-,GameMenuModOption-,HealthOverlay-,HeldItemTooltips-,HorseStatsMod-,ImmediatelyFastReforged-,InventoryEssentials_,InventoryHud_[1.17.1].forge-,InventorySpam-,InventoryTweaks-,ItemBorders-,ItemPhysicLite_,ItemStitchingFix-,JBRA-Client-,JustEnoughCalculation-,JustEnoughEffects-,JustEnoughProfessions-,LeaveMyBarsAlone-,LLOverlayReloaded-,LOTRDRP-,LegendaryTooltips,LegendaryTooltips-,LightOverlay-,MoBends,MouseTweaks-,MyServerIsCompatible-,Neat ,Neat-,NekosEnchantedBooks-,NoAutoJump-,NoFog-,Notes-,NotifMod-,OldJavaWarning-,OptiFine,OptiFine_,OptiForge,OptiForge-,OverflowingBars-,PackMenu-,PackModeMenu-,PickUpNotifier-,Ping-,PingHUD-,PresenceFootsteps-,RPG-HUD-,ReAuth-,Reforgium-,ResourceLoader-,ResourcePackOrganizer,ShoulderSurfing-,ShulkerTooltip-,SimpleDiscordRichPresence-,SimpleWorldTimer-,SoundFilters-,SpawnerFix-,StylishEffects-,TextruesRubidiumOptions-,TRansliterationLib-,TipTheScales-,Tips-,Toast Control-,Toast-Control-,ToastControl-,TravelersTitles-,VoidFog-,VR-Combat_,WindowedFullscreen-,WorldNameRandomizer-,[1.12.2]DamageIndicatorsMod-,[1.12.2]bspkrscore-,antighost-,anviltooltipmod-,appleskin-,armorchroma-,armorpointspp-,auditory-,authme-,auto-reconnect-,autojoin-,autoreconnect-,axolotl-item-fix-,backtools-,bannerunlimited-,beenfo-1.19-,better-recipe-book-,betterbiomeblend-,bhmenu-,blur-,borderless-mining-,catalogue-,charmonium-,chat_heads-,cherishedworlds-,cirback-1.0-,classicbar-,clickadv-,clienttweaks-,combat_music-,connectedness-,controllable-,cullleaves-,cullparticles-,custom-crosshair-mod-,customdiscordrpc-,darkness-,dashloader-,defaultoptions-,desiredservers-,discordrpc-,drippyloadingscreen-,drippyloadingscreen_,durabilitytooltip-,dynamic-fps-,dynamic-music-,dynamiclights-,dynmus-,effective-,eggtab-,eguilib-,eiramoticons-,embeddium-,enchantment-lore-,entity-texture-features-,entityculling-,essential_,exhaustedstamina-,extremesoundmuffler-,fabricemotes-,fancymenu_,fancymenu_video_extension,flickerfix-,fm_audio_extension_,forgemod_VoxelMap-,freelook-,galacticraft-rpc-,gamestagesviewer-,gpumemleakfix-,grid-,helium-,hiddenrecipebook_,hiddenrecipebook-,infinitemusic-,inventoryprofiles,invtweaks-,itemzoom,itlt-,jeed-,jehc-,jeiintegration_,just-enough-harvestcraft-,justenoughbeacons-,justenoughdrags-,justzoom_,keymap-,keywizard-,lazydfu-,lib39-,light-overlay-,lightfallclient-,lightspeed-,loadmyresources_,lock_minecart_view-,lootbeams-,lwl-,magnesium_extras-,maptooltip-,massunbind,mcbindtype-,mcwifipnp-,medievalmusic-,memoryusagescreen-,mightyarchitect-,mindful-eating-,minetogether-,mobplusplus-,modcredits-,modernworldcreation_,modnametooltip-,modnametooltip_,moreoverlays-,mousewheelie-,movement-vision-,multihotbar-,music-duration-reducer-,musicdr-,neiRecipeHandlers-,ngrok-lan-expose-mod-,no_nv_flash-,nopotionshift_,notenoughanimations-,oculus-,ornaments-,overloadedarmorbar-,panorama-,paperdoll-,physics-mod-,phosphor-,preciseblockplacing-,radon-,realm-of-lost-souls-,rebind_narrator-,rebind-narrator-,rebindnarrator-,rebrand-,reforgium-,replanter-,rubidium-,rubidium_extras-,screenshot-to-clipboard-,shutupexperimentalsettings-,shutupmodelloader-,signtools-,simple-rpc-,simpleautorun-,smartcursor-,smoothboot-,smoothfocus-,sodium-fabric-,sounddeviceoptions-,soundreloader-,spoticraft-,^textrues_embeddium_options-.*$,tconplanner-,textrues_embeddium_options-,timestamps-,tooltipscroller-,torchoptimizer-,torohealth-,totaldarkness,toughnessbar-,whats-that-slot-forge-,wisla-,xlifeheartcolors-,yisthereautojump-
+de.griefed.serverpackcreator.configuration.fallbackmodslist=3dskinlayers-,Absolutely-Not-A-Zoom-Mod-,AdvancedChat-,AdvancedChatCore-,AdvancedChatHUD-,AdvancedCompas-,Ambience,AmbientEnvironment-,AmbientSounds_,AreYouBlind-,Armor Status HUD-,ArmorSoundTweak-,BH-Menu-,Batty's Coordinates PLUS Mod,BetterAdvancements-,BetterAnimationsCollection-,BetterModsButton-,BetterDarkMode-,BetterF3-,BetterFog-,BetterFoliage-,BetterPingDisplay-,BetterPlacement-,BetterTaskbar-,BetterThirdPerson,BetterTitleScreen-,Blur-,BorderlessWindow-,CTM-,ChunkAnimator-,ClientTweaks_,CompletionistsIndex-,Controller Support-,Controlling-,CraftPresence-,Create_Questing-,CullLessLeaves-Reforged-,CustomCursorMod-,CustomMainMenu-,DefaultOptions_,DefaultSettings-,DeleteWorldsToTrash-,DetailArmorBar-,Ding-,DistantHorizons-,DripSounds-,Durability101-,DurabilityNotifier-,DynamicSurroundings-,DynamicSurroundingsHuds-,EffectsLeft-,EiraMoticons_,EnchantmentDescriptions-,EnhancedVisuals_,EquipmentCompare-,FPS-Monitor-,FabricCustomCursorMod-,Fallingleaves-,FancySpawnEggs,FancyVideo-API-,FirstPersonMod,FogTweaker-,ForgeCustomCursorMod-,FpsReducer-,FpsReducer2-,FullscreenWindowed-,GameMenuModOption-,HealthOverlay-,HeldItemTooltips-,HorseStatsMod-,ImmediatelyFastReforged-,InventoryEssentials_,InventoryHud_[1.17.1].forge-,InventorySpam-,InventoryTweaks-,ItemBorders-,ItemPhysicLite_,ItemStitchingFix-,JBRA-Client-,JustEnoughCalculation-,JustEnoughEffects-,JustEnoughProfessions-,LeaveMyBarsAlone-,LLOverlayReloaded-,LOTRDRP-,LegendaryTooltips,LegendaryTooltips-,LightOverlay-,MoBends,MouseTweaks-,MyServerIsCompatible-,Neat ,Neat-,NekosEnchantedBooks-,NoAutoJump-,NoFog-,Notes-,NotifMod-,OldJavaWarning-,OptiFine,OptiFine_,OptiForge,OptiForge-,OverflowingBars-,PackMenu-,PackModeMenu-,PickUpNotifier-,Ping-,PingHUD-,PresenceFootsteps-,RPG-HUD-,ReAuth-,Reforgium-,ResourceLoader-,ResourcePackOrganizer,Ryoamiclights-,ShoulderSurfing-,ShulkerTooltip-,SimpleDiscordRichPresence-,SimpleWorldTimer-,SoundFilters-,SpawnerFix-,StylishEffects-,TextruesRubidiumOptions-,TRansliterationLib-,TipTheScales-,Tips-,Toast Control-,Toast-Control-,ToastControl-,TravelersTitles-,VoidFog-,VR-Combat_,WindowedFullscreen-,WorldNameRandomizer-,[1.12.2]DamageIndicatorsMod-,[1.12.2]bspkrscore-,antighost-,anviltooltipmod-,appleskin-,armorchroma-,armorpointspp-,auditory-,authme-,auto-reconnect-,autojoin-,autoreconnect-,axolotl-item-fix-,backtools-,bannerunlimited-,beenfo-1.19-,better-recipe-book-,betterbiomeblend-,bhmenu-,blur-,borderless-mining-,catalogue-,charmonium-,chat_heads-,cherishedworlds-,cirback-1.0-,classicbar-,clickadv-,clienttweaks-,combat_music-,connectedness-,controllable-,cullleaves-,cullparticles-,custom-crosshair-mod-,customdiscordrpc-,darkness-,dashloader-,defaultoptions-,desiredservers-,discordrpc-,drippyloadingscreen-,drippyloadingscreen_,durabilitytooltip-,dynamic-fps-,dynamic-music-,dynamiclights-,dynmus-,effective-,eggtab-,eguilib-,eiramoticons-,embeddium-,enchantment-lore-,entity-texture-features-,entityculling-,essential_,exhaustedstamina-,extremesoundmuffler-,fabricemotes-,fancymenu_,fancymenu_video_extension,flickerfix-,fm_audio_extension_,forgemod_VoxelMap-,freelook-,galacticraft-rpc-,gamestagesviewer-,gpumemleakfix-,grid-,helium-,hiddenrecipebook_,hiddenrecipebook-,infinitemusic-,inventoryprofiles,invtweaks-,itemzoom,itlt-,jeed-,jehc-,jeiintegration_,just-enough-harvestcraft-,justenoughbeacons-,justenoughdrags-,justzoom_,keymap-,keywizard-,lazydfu-,lib39-,light-overlay-,lightfallclient-,lightspeed-,loadmyresources_,lock_minecart_view-,lootbeams-,lwl-,magnesium_extras-,maptooltip-,massunbind,mcbindtype-,mcwifipnp-,medievalmusic-,memoryusagescreen-,mightyarchitect-,mindful-eating-,minetogether-,mobplusplus-,modcredits-,modernworldcreation_,modnametooltip-,modnametooltip_,moreoverlays-,mousewheelie-,movement-vision-,multihotbar-,music-duration-reducer-,musicdr-,neiRecipeHandlers-,ngrok-lan-expose-mod-,no_nv_flash-,nopotionshift_,notenoughanimations-,oculus-,ornaments-,overloadedarmorbar-,panorama-,paperdoll-,physics-mod-,phosphor-,preciseblockplacing-,radon-,realm-of-lost-souls-,rebind_narrator-,rebind-narrator-,rebindnarrator-,rebrand-,reforgium-,replanter-,rubidium-,rubidium_extras-,screenshot-to-clipboard-,servercountryflags-,shutupexperimentalsettings-,shutupmodelloader-,signtools-,simple-rpc-,simpleautorun-,smartcursor-,smoothboot-,smoothfocus-,sodium-fabric-,sounddeviceoptions-,soundreloader-,spoticraft-,^textrues_embeddium_options-.*$,tconplanner-,textrues_embeddium_options-,timestamps-,tooltipscroller-,torchoptimizer-,torohealth-,totaldarkness,toughnessbar-,whats-that-slot-forge-,wisla-,xlifeheartcolors-,yisthereautojump-
 de.griefed.serverpackcreator.configuration.hastebinserver=https://haste.zneix.eu/documents
 de.griefed.serverpackcreator.configuration.aikar=-Xms4G -Xmx4G -XX:+UseG1GC -XX:+ParallelRefProcEnabled -XX:MaxGCPauseMillis=200 -XX:+UnlockExperimentalVMOptions -XX:+DisableExplicitGC -XX:+AlwaysPreTouch -XX:G1NewSizePercent=30 -XX:G1MaxNewSizePercent=40 -XX:G1HeapRegionSize=8M -XX:G1ReservePercent=20 -XX:G1HeapWastePercent=5 -XX:G1MixedGCCountTarget=4 -XX:InitiatingHeapOccupancyPercent=15 -XX:G1MixedGCLiveThresholdPercent=90 -XX:G1RSetUpdatingPauseTimePercent=5 -XX:SurvivorRatio=32 -XX:+PerfDisableSharedMem -XX:MaxTenuringThreshold=1 -Dusing.aikars.flags=https://mcflags.emc.gs -Daikars.new.flags=true
 de.griefed.serverpackcreator.serverpack.autodiscovery.enabled=true
-- 
GitLab


From 6de391a3c482d7eed5075224fc4531dee02951ad Mon Sep 17 00:00:00 2001
From: Griefed <griefed@griefed.de>
Date: Wed, 25 Oct 2023 21:17:05 +0200
Subject: [PATCH 2/7] build: Add desktop definitions to improve menu and
 desktop entries

---
 .../jpackagerResources/ServerPackCreator.desktop      |  8 ++++++--
 .../jpackagerResources/launcher.desktop               | 11 +++++++++++
 2 files changed, 17 insertions(+), 2 deletions(-)
 create mode 100644 serverpackcreator-app/jpackagerResources/launcher.desktop

diff --git a/serverpackcreator-app/jpackagerResources/ServerPackCreator.desktop b/serverpackcreator-app/jpackagerResources/ServerPackCreator.desktop
index 3c1128cd2..360537334 100644
--- a/serverpackcreator-app/jpackagerResources/ServerPackCreator.desktop
+++ b/serverpackcreator-app/jpackagerResources/ServerPackCreator.desktop
@@ -1,7 +1,11 @@
 [Desktop Entry]
 Name=ServerPackCreator
 Comment=Create server packs from Minecraft Forge, Fabric, Quilt or LegacyFabric modpacks.
-Exec=ServerPackCreator
-Icon=ServerPackCreator
+GenericName[en_US]=Create server packs from Minecraft Forge, Fabric, Quilt or LegacyFabric modpacks.
+GenericName[en_GB]=Create server packs from Minecraft Forge, Fabric, Quilt or LegacyFabric modpacks.
+Exec=/opt/serverpackcreator/bin/ServerPackCreator
+Icon=/opt/serverpackcreator/lib/ServerPackCreator.png
+Terminal=false
 Type=Application
 Categories=Utility;FileTools;Java;
+MimeType=
\ No newline at end of file
diff --git a/serverpackcreator-app/jpackagerResources/launcher.desktop b/serverpackcreator-app/jpackagerResources/launcher.desktop
new file mode 100644
index 000000000..360537334
--- /dev/null
+++ b/serverpackcreator-app/jpackagerResources/launcher.desktop
@@ -0,0 +1,11 @@
+[Desktop Entry]
+Name=ServerPackCreator
+Comment=Create server packs from Minecraft Forge, Fabric, Quilt or LegacyFabric modpacks.
+GenericName[en_US]=Create server packs from Minecraft Forge, Fabric, Quilt or LegacyFabric modpacks.
+GenericName[en_GB]=Create server packs from Minecraft Forge, Fabric, Quilt or LegacyFabric modpacks.
+Exec=/opt/serverpackcreator/bin/ServerPackCreator
+Icon=/opt/serverpackcreator/lib/ServerPackCreator.png
+Terminal=false
+Type=Application
+Categories=Utility;FileTools;Java;
+MimeType=
\ No newline at end of file
-- 
GitLab


From 9d2f239391c818f688fb4c1a651339c8645b8e47 Mon Sep 17 00:00:00 2001
From: Griefed <griefed@griefed.de>
Date: Wed, 25 Oct 2023 21:17:26 +0200
Subject: [PATCH 3/7] chore: Remove test from jpackager run config

---
 .runConfigurations/Build JPackager Installer.run.xml | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/.runConfigurations/Build JPackager Installer.run.xml b/.runConfigurations/Build JPackager Installer.run.xml
index fa370cee0..8ff6f807b 100644
--- a/.runConfigurations/Build JPackager Installer.run.xml	
+++ b/.runConfigurations/Build JPackager Installer.run.xml	
@@ -4,7 +4,7 @@
       <option name="executionName" />
       <option name="externalProjectPath" value="$PROJECT_DIR$/serverpackcreator-app" />
       <option name="externalSystemIdString" value="GRADLE" />
-      <option name="scriptParameters" value="--full-stacktrace --info" />
+      <option name="scriptParameters" value="--full-stacktrace --info -x :serverpackcreator-api:test" />
       <option name="taskDescriptions">
         <list />
       </option>
@@ -18,6 +18,7 @@
     <ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
     <ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
     <DebugAllEnabled>false</DebugAllEnabled>
+    <RunAsTest>false</RunAsTest>
     <method v="2" />
   </configuration>
 </component>
\ No newline at end of file
-- 
GitLab


From d961e8d7bdf29124e60c7bc9ebcaaf3294b11bf7 Mon Sep 17 00:00:00 2001
From: Griefed <griefed@griefed.de>
Date: Wed, 25 Oct 2023 21:17:34 +0200
Subject: [PATCH 4/7] chore: Remove comment

---
 serverpackcreator-app/build.gradle.kts | 1 -
 1 file changed, 1 deletion(-)

diff --git a/serverpackcreator-app/build.gradle.kts b/serverpackcreator-app/build.gradle.kts
index bd2942cb6..9e973bba0 100644
--- a/serverpackcreator-app/build.gradle.kts
+++ b/serverpackcreator-app/build.gradle.kts
@@ -22,7 +22,6 @@ dependencies {
     api(project(":serverpackcreator-gui"))
     api(project(":serverpackcreator-web"))
     api(project(":serverpackcreator-updater"))
-    //api("de.griefed:versionchecker:1.1.9")
     testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.0")
     testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.0")
 }
-- 
GitLab


From 2b75716cbbaa9be1799d94fd53da2355f551a078 Mon Sep 17 00:00:00 2001
From: Griefed <griefed@griefed.de>
Date: Mon, 30 Oct 2023 19:16:38 +0100
Subject: [PATCH 5/7] improv: Add note regarding running the bash script

---
 .../resources/server_files/default_template.sh      | 13 ++++++++++---
 1 file changed, 10 insertions(+), 3 deletions(-)

diff --git a/serverpackcreator-api/src/jvmMain/resources/de/griefed/resources/server_files/default_template.sh b/serverpackcreator-api/src/jvmMain/resources/de/griefed/resources/server_files/default_template.sh
index 1e874e0eb..57c37c242 100644
--- a/serverpackcreator-api/src/jvmMain/resources/de/griefed/resources/server_files/default_template.sh
+++ b/serverpackcreator-api/src/jvmMain/resources/de/griefed/resources/server_files/default_template.sh
@@ -1,11 +1,18 @@
 #!/usr/bin/env bash
-
+# NOTES AND GENERAL INFO
+#
 # Start script generated by ServerPackCreator SPC_SERVERPACKCREATOR_VERSION_SPC.
-
+#
+# The Linux scripts are intended to be run using bash (indicated by the `#!/usr/bin/env bash` at the top),
+# i.e. by simply calling `./start.sh` or `bash start.sh`.
+# Using any other method may work, but can also lead to unexpected behavior.
+# Running the Linux scripts on MacOS has been done before, but is not tested by the developers of ServerPackCreator.
+# Results may wary, no guarantees.
+#
 # Depending on which modloader is set, different checks are run to ensure the server will start accordingly.
 # If the modloader checks and setup are passed, Minecraft and EULA checks are run.
 # If everything is in order, the server is started.
-
+#
 # Depending on the Minecraft version you will require a different Java version to run the server.
 #   1.16.5 and older requires Java 8 (Java 11 will run better and work with 99% of mods, give it a try)
 #     Linux:
-- 
GitLab


From 010368a26ae989b63c9cda0e890a694f7ec1918d Mon Sep 17 00:00:00 2001
From: Griefed <griefed@griefed.de>
Date: Wed, 1 Nov 2023 21:56:57 +0100
Subject: [PATCH 6/7] perf: Reduce paint actions to save on CPU

---
 .../gui/window/control/components/LarsonScanner.kt            | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/serverpackcreator-gui/src/main/kotlin/de/griefed/serverpackcreator/gui/window/control/components/LarsonScanner.kt b/serverpackcreator-gui/src/main/kotlin/de/griefed/serverpackcreator/gui/window/control/components/LarsonScanner.kt
index 121df0487..387cca440 100644
--- a/serverpackcreator-gui/src/main/kotlin/de/griefed/serverpackcreator/gui/window/control/components/LarsonScanner.kt
+++ b/serverpackcreator-gui/src/main/kotlin/de/griefed/serverpackcreator/gui/window/control/components/LarsonScanner.kt
@@ -1302,12 +1302,14 @@ class LarsonScanner : JPanel {
          * @author Griefed
          */
         fun run() {
-            timer = Timer(40) {
+            val delay = 60
+            timer = Timer(delay) {
                 if (this.isRunning) {
                     eye.updatePosition()
                     eye.repaint()
                 }
             }
+            timer.delay = delay
             timer.isRepeats = true
             timer.isCoalesce = true
             timer.start()
-- 
GitLab


From 8b9eb7ae6dfc71d73949814e556da948d96a6e6a Mon Sep 17 00:00:00 2001
From: Griefed <griefed@griefed.de>
Date: Tue, 7 Nov 2023 21:10:05 +0100
Subject: [PATCH 7/7] feat: Whitelist to prevent false positives via
 clientside-mods list

---
 .../serverpackcreator/api/Configuration.kt    |  32 ++--
 .../api/ConfigurationHandler.kt               |  12 +-
 .../de/griefed/serverpackcreator/api/Pack.kt  |  14 +-
 .../serverpackcreator/api/PackConfig.kt       |   3 +
 .../serverpackcreator/api/ServerPack.kt       |  14 +-
 .../plugins/swinggui/ServerPackConfigTab.kt   |   3 +
 .../serverpackcreator/api/ApiProperties.kt    |  82 ++++++++-
 .../api/ConfigurationHandler.kt               |   8 +-
 .../serverpackcreator/api/PackConfig.kt       |  15 +-
 .../api/ServerPackHandler.kt                  |  37 ++--
 .../resources/serverpackcreator.properties    |   1 +
 .../api/ConfigurationHandlerTest.kt           |   8 +
 .../src/main/i18n/Gui_en_GB.properties        |  11 +-
 .../gui/window/configs/ConfigEditor.kt        | 159 ++++++++++++------
 .../configs/components/ConfigCheckTimer.kt    |   3 +
 .../advanced/AdvancedSettingsPanel.kt         | 140 +++++++++++++--
 .../components/advanced/WhitelistChooser.kt   |  48 ++++++
 .../components/inclusions/InclusionsEditor.kt |   1 +
 18 files changed, 471 insertions(+), 120 deletions(-)
 create mode 100644 serverpackcreator-gui/src/main/kotlin/de/griefed/serverpackcreator/gui/window/configs/components/advanced/WhitelistChooser.kt

diff --git a/serverpackcreator-api/src/commonMain/kotlin/de/griefed/serverpackcreator/api/Configuration.kt b/serverpackcreator-api/src/commonMain/kotlin/de/griefed/serverpackcreator/api/Configuration.kt
index 801bf50c3..72b1ce081 100644
--- a/serverpackcreator-api/src/commonMain/kotlin/de/griefed/serverpackcreator/api/Configuration.kt
+++ b/serverpackcreator-api/src/commonMain/kotlin/de/griefed/serverpackcreator/api/Configuration.kt
@@ -60,9 +60,9 @@ abstract class Configuration<F, P> {
      */
     abstract fun checkConfiguration(
         configFile: F,
-        packConfig: PackConfig = PackConfig(),
-        configCheck: ConfigCheck = ConfigCheck(),
-        quietCheck: Boolean = false
+        packConfig: PackConfig,
+        configCheck: ConfigCheck,
+        quietCheck: Boolean
     ): ConfigCheck
 
     /**
@@ -84,8 +84,8 @@ abstract class Configuration<F, P> {
      */
     abstract fun checkConfiguration(
         packConfig: PackConfig,
-        configCheck: ConfigCheck = ConfigCheck(),
-        quietCheck: Boolean = false
+        configCheck: ConfigCheck,
+        quietCheck: Boolean
     ): ConfigCheck
 
     /**
@@ -109,7 +109,7 @@ abstract class Configuration<F, P> {
      * specified, but the file was not found.
      * @author Griefed
      */
-    abstract fun checkIconAndProperties(iconOrPropertiesPath: String = ""): Boolean
+    abstract fun checkIconAndProperties(iconOrPropertiesPath: String): Boolean
 
     /**
      * If the in the configuration specified modpack dir is an existing directory, checks are made for
@@ -123,7 +123,7 @@ abstract class Configuration<F, P> {
      * @return `true` if an error is found during configuration check.
      * @author Griefed
      */
-    abstract fun isDir(packConfig: PackConfig, configCheck: ConfigCheck = ConfigCheck()): ConfigCheck
+    abstract fun isDir(packConfig: PackConfig, configCheck: ConfigCheck): ConfigCheck
 
     /**
      * Checks the specified ZIP-archive for validity. In order for a modpack ZIP-archive to be
@@ -137,7 +137,7 @@ abstract class Configuration<F, P> {
      * @return `false` when no errors were encountered.
      * @author Griefed
      */
-    abstract fun isZip(packConfig: PackConfig, configCheck: ConfigCheck = ConfigCheck()): ConfigCheck
+    abstract fun isZip(packConfig: PackConfig, configCheck: ConfigCheck): ConfigCheck
 
     /**
      * Checks whether either Forge or Fabric were specified as the modloader.
@@ -146,7 +146,7 @@ abstract class Configuration<F, P> {
      * @return `true` if the specified modloader is either Forge or Fabric. False if neither.
      * @author Griefed
      */
-    abstract fun checkModloader(modloader: String, configCheck: ConfigCheck = ConfigCheck()): ConfigCheck
+    abstract fun checkModloader(modloader: String, configCheck: ConfigCheck): ConfigCheck
 
     /**
      * Check the given Minecraft and modloader versions for the specified modloader.
@@ -161,7 +161,7 @@ abstract class Configuration<F, P> {
      * @author Griefed
      */
     abstract fun checkModloaderVersion(
-        modloader: String, modloaderVersion: String, minecraftVersion: String, configCheck: ConfigCheck = ConfigCheck()
+        modloader: String, modloaderVersion: String, minecraftVersion: String, configCheck: ConfigCheck
     ): ConfigCheck
 
     /**
@@ -248,8 +248,8 @@ abstract class Configuration<F, P> {
     abstract fun checkInclusions(
         inclusions: MutableList<InclusionSpecification>,
         modpackDir: String,
-        configCheck: ConfigCheck = ConfigCheck(),
-        printLog: Boolean = true
+        configCheck: ConfigCheck,
+        printLog: Boolean
     ): ConfigCheck
 
     /**
@@ -259,7 +259,7 @@ abstract class Configuration<F, P> {
      * @param pathToZip         Path to the ZIP-file to check.
      * @author Griefed
      */
-    abstract fun checkZipArchive(pathToZip: String, configCheck: ConfigCheck = ConfigCheck()): ConfigCheck
+    abstract fun checkZipArchive(pathToZip: String, configCheck: ConfigCheck): ConfigCheck
 
     /**
      * Update the destination to which the ZIP-archive will the extracted to, based on whether a
@@ -298,7 +298,7 @@ abstract class Configuration<F, P> {
     abstract fun checkManifests(
         destination: String,
         packConfig: PackConfig,
-        configCheck: ConfigCheck = ConfigCheck()
+        configCheck: ConfigCheck
     ): String?
 
     /**
@@ -505,8 +505,8 @@ abstract class Configuration<F, P> {
      */
     abstract fun checkModpackDir(
         modpackDir: String,
-        configCheck: ConfigCheck = ConfigCheck(),
-        printLog: Boolean = true
+        configCheck: ConfigCheck,
+        printLog: Boolean
     ): ConfigCheck
 
     /**
diff --git a/serverpackcreator-api/src/commonMain/kotlin/de/griefed/serverpackcreator/api/ConfigurationHandler.kt b/serverpackcreator-api/src/commonMain/kotlin/de/griefed/serverpackcreator/api/ConfigurationHandler.kt
index 3ffb1496e..360d862cf 100644
--- a/serverpackcreator-api/src/commonMain/kotlin/de/griefed/serverpackcreator/api/ConfigurationHandler.kt
+++ b/serverpackcreator-api/src/commonMain/kotlin/de/griefed/serverpackcreator/api/ConfigurationHandler.kt
@@ -32,18 +32,18 @@ import de.griefed.serverpackcreator.api.utilities.File
  * @author Griefed
  */
 expect class ConfigurationHandler {
-    fun checkConfiguration(configFile: File, packConfig: PackConfig, configCheck: ConfigCheck = ConfigCheck(), quietCheck: Boolean = false): ConfigCheck
-    fun checkConfiguration(packConfig: PackConfig, configCheck: ConfigCheck = ConfigCheck(), quietCheck: Boolean = false): ConfigCheck
+    fun checkConfiguration(configFile: File, packConfig: PackConfig = PackConfig(), configCheck: ConfigCheck = ConfigCheck(), quietCheck: Boolean = false): ConfigCheck
+    fun checkConfiguration(packConfig: PackConfig = PackConfig(), configCheck: ConfigCheck = ConfigCheck(), quietCheck: Boolean = false): ConfigCheck
     fun isDir(packConfig: PackConfig, configCheck: ConfigCheck = ConfigCheck()): ConfigCheck
     fun isZip(packConfig: PackConfig, configCheck: ConfigCheck = ConfigCheck()): ConfigCheck
     fun checkModloaderVersion(modloader: String, modloaderVersion: String, minecraftVersion: String, configCheck: ConfigCheck = ConfigCheck()): ConfigCheck
-    fun checkInclusions(inclusions: MutableList<InclusionSpecification>,modpackDir: String, configCheck: ConfigCheck = ConfigCheck(),printLog: Boolean = true): ConfigCheck
+    fun checkInclusions(inclusions: MutableList<InclusionSpecification>, modpackDir: String, configCheck: ConfigCheck = ConfigCheck(), printLog: Boolean = true): ConfigCheck
     fun checkZipArchive(pathToZip: String, configCheck: ConfigCheck = ConfigCheck()): ConfigCheck
     fun checkManifests(destination: String, packConfig: PackConfig, configCheck: ConfigCheck = ConfigCheck()): String?
-    fun checkModpackDir(modpackDir: String, configCheck: ConfigCheck = ConfigCheck(), printLog: Boolean): ConfigCheck
-    fun checkModloader(modloader: String,configCheck: ConfigCheck): ConfigCheck
+    fun checkModpackDir(modpackDir: String, configCheck: ConfigCheck = ConfigCheck(), printLog: Boolean = true): ConfigCheck
+    fun checkModloader(modloader: String,configCheck: ConfigCheck = ConfigCheck()): ConfigCheck
 
-    fun checkIconAndProperties(iconOrPropertiesPath: String): Boolean
+    fun checkIconAndProperties(iconOrPropertiesPath: String = ""): Boolean
 
     fun ensureScriptSettingsDefaults(packConfig: PackConfig)
     fun sanitizeLinks(packConfig: PackConfig)
diff --git a/serverpackcreator-api/src/commonMain/kotlin/de/griefed/serverpackcreator/api/Pack.kt b/serverpackcreator-api/src/commonMain/kotlin/de/griefed/serverpackcreator/api/Pack.kt
index 6cdd0e8bf..d819598d9 100644
--- a/serverpackcreator-api/src/commonMain/kotlin/de/griefed/serverpackcreator/api/Pack.kt
+++ b/serverpackcreator-api/src/commonMain/kotlin/de/griefed/serverpackcreator/api/Pack.kt
@@ -33,9 +33,10 @@ abstract class Pack<F, J, out P> {
     protected val fabric = "^fabric$".toRegex()
     protected val quilt = "^quilt$".toRegex()
     protected val legacyFabric = "^legacyfabric$".toRegex()
-    protected val whitespace = "\\s+".toRegex()
+    protected val whitespace = "^\\s+$".toRegex()
 
     val clientMods: ArrayList<String> = ArrayList(1000)
+    val modsWhitelist: ArrayList<String> = ArrayList(1000)
     val inclusions: ArrayList<InclusionSpecification> = ArrayList(100)
     val scriptSettings = HashMap<String, String>(100)
     val pluginsConfigs = HashMap<String, ArrayList<CommentedConfig>>(20)
@@ -92,12 +93,18 @@ abstract class Pack<F, J, out P> {
         return pluginsConfigs[pluginId]!!
     }
 
-    fun setClientMods(newClientMods: ArrayList<String>) {
+    fun setClientMods(newClientMods: MutableList<String>) {
         clientMods.clear()
-        newClientMods.removeIf { entry: String -> entry.matches(whitespace) || entry.isEmpty() }
+        newClientMods.removeIf { entry: String -> entry.isBlank() || entry.matches(whitespace) }
         clientMods.addAll(newClientMods)
     }
 
+    fun setModsWhitelist(newModsWhitelist: MutableList<String>) {
+        modsWhitelist.clear()
+        newModsWhitelist.removeIf { entry: String -> entry.isBlank() || entry.matches(whitespace) }
+        modsWhitelist.addAll(newModsWhitelist)
+    }
+
     fun setInclusions(newCopyDirs: ArrayList<InclusionSpecification>) {
         inclusions.clear()
         inclusions.addAll(newCopyDirs)
@@ -111,6 +118,7 @@ abstract class Pack<F, J, out P> {
     override fun toString(): String {
         return "Pack(" +
                 " clientMods=$clientMods," +
+                " whiteList=$modsWhitelist," +
                 " copyDirs=$inclusions," +
                 " scriptSettings=$scriptSettings," +
                 " pluginsConfigs=$pluginsConfigs," +
diff --git a/serverpackcreator-api/src/commonMain/kotlin/de/griefed/serverpackcreator/api/PackConfig.kt b/serverpackcreator-api/src/commonMain/kotlin/de/griefed/serverpackcreator/api/PackConfig.kt
index 8de7403c5..ae5694ce1 100644
--- a/serverpackcreator-api/src/commonMain/kotlin/de/griefed/serverpackcreator/api/PackConfig.kt
+++ b/serverpackcreator-api/src/commonMain/kotlin/de/griefed/serverpackcreator/api/PackConfig.kt
@@ -43,6 +43,7 @@ import de.griefed.serverpackcreator.api.utilities.File
 expect open class PackConfig {
 
     val clientMods: ArrayList<String>
+    val modsWhitelist: ArrayList<String>
     val inclusions: ArrayList<InclusionSpecification>
     val scriptSettings: HashMap<String, String>
     val pluginsConfigs: HashMap<String, ArrayList<CommentedConfig>>
@@ -65,6 +66,7 @@ expect open class PackConfig {
      * Construct a new configuration model with custom values.
      *
      * @param clientMods                List of clientside mods to exclude from the server pack.
+     * @param whitelist                 List of mods to include if present, regardless whether a match was found through [clientMods]
      * @param copyDirs                  List of directories and/or files to include in the server pack.
      * @param modpackDir                The path to the modpack.
      * @param minecraftVersion          The Minecraft version the modpack uses.
@@ -84,6 +86,7 @@ expect open class PackConfig {
      */
     constructor(
         clientMods: List<String>,
+        whitelist: List<String>,
         copyDirs: List<InclusionSpecification>,
         modpackDir: String,
         minecraftVersion: String,
diff --git a/serverpackcreator-api/src/commonMain/kotlin/de/griefed/serverpackcreator/api/ServerPack.kt b/serverpackcreator-api/src/commonMain/kotlin/de/griefed/serverpackcreator/api/ServerPack.kt
index 7838a835c..facd12eec 100644
--- a/serverpackcreator-api/src/commonMain/kotlin/de/griefed/serverpackcreator/api/ServerPack.kt
+++ b/serverpackcreator-api/src/commonMain/kotlin/de/griefed/serverpackcreator/api/ServerPack.kt
@@ -101,6 +101,7 @@ abstract class ServerPack<F, TS, TF> {
         packConfig.modpackDir,
         packConfig.inclusions,
         packConfig.clientMods,
+        packConfig.modsWhitelist,
         packConfig.minecraftVersion,
         getServerPackDestination(packConfig),
         packConfig.modloader
@@ -211,6 +212,7 @@ abstract class ServerPack<F, TS, TF> {
         modpackDir: String,
         inclusions: ArrayList<InclusionSpecification>,
         clientMods: List<String>,
+        whitelist: List<String>,
         minecraftVersion: String,
         destination: String,
         modloader: String
@@ -314,6 +316,7 @@ abstract class ServerPack<F, TS, TF> {
      * @param modsDir                 The mods-directory of the modpack of which to generate a list of
      * all its contents.
      * @param userSpecifiedClientMods A list of all clientside-only mods.
+     * @param userSpecifiedWhitelist  A list of mods to include regardless if a match was found in [userSpecifiedClientMods].
      * @param minecraftVersion        The Minecraft version the modpack uses. When the modloader is
      * Forge, this determines whether Annotations or Tomls are
      * scanned.
@@ -322,7 +325,11 @@ abstract class ServerPack<F, TS, TF> {
      * @author Griefed
      */
     abstract fun getModsToInclude(
-        modsDir: String, userSpecifiedClientMods: List<String>, minecraftVersion: String, modloader: String
+        modsDir: String,
+        userSpecifiedClientMods: List<String>,
+        userSpecifiedModsWhitelist: List<String>,
+        minecraftVersion: String,
+        modloader: String
     ): List<F>
 
     /**
@@ -367,6 +374,7 @@ abstract class ServerPack<F, TS, TF> {
     fun getModsToInclude(packConfig: Pack<*, *, *>) = getModsToInclude(
         "${packConfig.modpackDir}${File.separator}mods",
         packConfig.clientMods,
+        packConfig.modsWhitelist,
         packConfig.minecraftVersion,
         packConfig.modloader
     )
@@ -457,7 +465,7 @@ abstract class ServerPack<F, TS, TF> {
      * modpack.
      * @author Griefed
      */
-    abstract fun excludeUserSpecifiedMod(userSpecifiedExclusions: List<String>, modsInModpack: TF)
+    abstract fun excludeUserSpecifiedMod(userSpecifiedExclusions: List<String>, userSpecifiedModsWhitelist: List<String>, modsInModpack: TF)
 
     /**
      * Walk through the specified directory and add a [ServerPackFile] for every file/folder
@@ -484,7 +492,7 @@ abstract class ServerPack<F, TS, TF> {
      *
      * @author Griefed
      */
-    abstract fun exclude(userSpecifiedExclusion: String, modsInModpack: TF)
+    abstract fun exclude(userSpecifiedExclusion: String, userSpecifiedModsWhitelist: List<String>, modsInModpack: TF)
 
     /**
      * Cleans up the server_pack directory by deleting left-over files from modloader installations
diff --git a/serverpackcreator-api/src/commonMain/kotlin/de/griefed/serverpackcreator/api/plugins/swinggui/ServerPackConfigTab.kt b/serverpackcreator-api/src/commonMain/kotlin/de/griefed/serverpackcreator/api/plugins/swinggui/ServerPackConfigTab.kt
index b9cf48591..b60961359 100644
--- a/serverpackcreator-api/src/commonMain/kotlin/de/griefed/serverpackcreator/api/plugins/swinggui/ServerPackConfigTab.kt
+++ b/serverpackcreator-api/src/commonMain/kotlin/de/griefed/serverpackcreator/api/plugins/swinggui/ServerPackConfigTab.kt
@@ -35,6 +35,7 @@ import de.griefed.serverpackcreator.api.utilities.File
 @Suppress("unused")
 interface ServerPackConfigTab {
     fun setClientSideMods(entries: MutableList<String>)
+    fun setWhitelist(entries: MutableList<String>)
     fun setInclusions(entries: MutableList<InclusionSpecification>)
     fun setIconInclusionTicked(ticked: Boolean)
     fun setJavaArguments(javaArguments: String)
@@ -52,6 +53,8 @@ interface ServerPackConfigTab {
 
     fun getClientSideMods(): String
     fun getClientSideModsList(): MutableList<String>
+    fun getWhitelist(): String
+    fun getWhitelistList(): MutableList<String>
     fun getInclusions(): List<InclusionSpecification>
     fun getCurrentConfiguration(): PackConfig
     fun saveCurrentConfiguration(): File
diff --git a/serverpackcreator-api/src/jvmMain/kotlin/de/griefed/serverpackcreator/api/ApiProperties.kt b/serverpackcreator-api/src/jvmMain/kotlin/de/griefed/serverpackcreator/api/ApiProperties.kt
index e7af9e4f4..a2cf2ff6f 100644
--- a/serverpackcreator-api/src/jvmMain/kotlin/de/griefed/serverpackcreator/api/ApiProperties.kt
+++ b/serverpackcreator-api/src/jvmMain/kotlin/de/griefed/serverpackcreator/api/ApiProperties.kt
@@ -32,6 +32,7 @@ import java.io.InputStreamReader
 import java.net.URL
 import java.util.*
 import java.util.prefs.Preferences
+import kotlin.collections.ArrayList
 
 /**
  * Base settings of ServerPackCreator, such as working directories, default list of clientside-only
@@ -72,6 +73,8 @@ actual class ApiProperties(
         "de.griefed.serverpackcreator.configuration.fallbackmodslist"
     private val pConfigurationFallbackModsListRegex =
         "de.griefed.serverpackcreator.configuration.fallbackmodslist.regex"
+    private val pConfigurationFallbackModsWhiteList =
+        "de.griefed.serverpackcreator.configuration.modswhitelist"
     private val pConfigurationHasteBinServerUrl =
         "de.griefed.serverpackcreator.configuration.hastebinserver"
     private val pConfigurationAikarsFlags =
@@ -139,6 +142,13 @@ actual class ApiProperties(
         jarInformation.jarFolder.absoluteFile
     }
 
+    @Suppress("SpellCheckingInspection")
+    private var fallbackModsWhitelist = TreeSet(
+        listOf(
+            "Ping-Wheel-"
+        )
+    )
+
     @Suppress("SpellCheckingInspection")
     private var fallbackMods = TreeSet(
         listOf(
@@ -563,6 +573,13 @@ actual class ApiProperties(
     var clientsideMods = fallbackMods
         private set
 
+    /**
+     * String-list of mods to include if present, regardless whether a match was found through [clientsideMods].
+     */
+    @Suppress("MemberVisibilityCanBePrivate")
+    var modsWhitelist = fallbackModsWhitelist
+        private set
+
     /**
      * Regex-list of clientside-only mods to exclude from server packs.
      */
@@ -577,6 +594,20 @@ actual class ApiProperties(
         }
         private set
 
+    /**
+     * Regex-list of mods to include if present, regardless whether a match was found throug [clientsideModsRegex].
+     */
+    var modsWhitelistRegex: TreeSet<String> = TreeSet()
+        get() {
+            field.clear()
+            for (mod in modsWhitelist) {
+                field.add("^$mod.*$")
+            }
+            return field
+        }
+        private set
+
+
     /**
      * Modloaders supported by ServerPackCreator.
      */
@@ -1869,6 +1900,7 @@ actual class ApiProperties(
             log.info("Fallback lists updated.")
         } else {
             setFallbackModsList()
+            setFallbackWhitelist()
         }
         if (saveProps) {
             //Store properties in the configured SPC home-directory
@@ -1893,10 +1925,24 @@ actual class ApiProperties(
             pConfigurationFallbackModsList,
             clientsideMods.joinToString(",")
         )
+    }
 
+    /**
+     * Set up our fallback list of clientside-only mods.
+     *
+     * @author Griefed
+     */
+    private fun setFallbackWhitelist() {
+        // Regular list
+        modsWhitelist.addAll(
+            getListProperty(
+                pConfigurationFallbackModsWhiteList,
+                fallbackModsWhitelist.joinToString(",")
+            )
+        )
         internalProps.setProperty(
-            pConfigurationFallbackModsListRegex,
-            clientsideModsRegex.joinToString(",")
+            pConfigurationFallbackModsWhiteList,
+            fallbackModsWhitelist.joinToString(",")
         )
     }
 
@@ -2219,9 +2265,24 @@ actual class ApiProperties(
      */
     fun clientSideMods() =
         if (exclusionFilter == ExclusionFilter.REGEX) {
-            clientsideModsRegex.toList() as ArrayList<String>
+            clientsideModsRegex.toList()
         } else {
-            clientsideMods.toList() as ArrayList<String>
+            clientsideMods.toList()
+        }
+
+    /**
+     * Acquire the default fallback list of whitelisted mods. If
+     * `de.griefed.serverpackcreator.serverpack.autodiscovery.filter` is set to
+     * [ExclusionFilter.REGEX], a regex fallback list is returned.
+     *
+     * @return The fallback list of whitelisted mods.
+     * @author Griefed
+     */
+    fun whitelistedMods() =
+        if (exclusionFilter == ExclusionFilter.REGEX) {
+            modsWhitelistRegex.toList()
+        } else {
+            modsWhitelist.toList()
         }
 
     /**
@@ -2259,6 +2320,19 @@ actual class ApiProperties(
                 log.info("The fallback-list for clientside only mods has been updated to: $clientsideMods")
                 updated = true
             }
+            if (properties!!.getProperty(pConfigurationFallbackModsWhiteList) != null &&
+                internalProps.getProperty(pConfigurationFallbackModsWhiteList)
+                != properties!!.getProperty(pConfigurationFallbackModsWhiteList)
+            ) {
+                internalProps.setProperty(
+                    pConfigurationFallbackModsWhiteList,
+                    properties!!.getProperty(pConfigurationFallbackModsWhiteList)
+                )
+                modsWhitelist.clear()
+                modsWhitelist.addAll(internalProps.getProperty(pConfigurationFallbackModsWhiteList).split(","))
+                log.info("The fallback-list for whitelisted mods has been updated to: $modsWhitelist")
+                updated = true
+            }
         }
         if (updated) {
             saveProperties(File(homeDirectory, serverPackCreatorProperties).absoluteFile)
diff --git a/serverpackcreator-api/src/jvmMain/kotlin/de/griefed/serverpackcreator/api/ConfigurationHandler.kt b/serverpackcreator-api/src/jvmMain/kotlin/de/griefed/serverpackcreator/api/ConfigurationHandler.kt
index 3600ee106..7d29ab6dd 100644
--- a/serverpackcreator-api/src/jvmMain/kotlin/de/griefed/serverpackcreator/api/ConfigurationHandler.kt
+++ b/serverpackcreator-api/src/jvmMain/kotlin/de/griefed/serverpackcreator/api/ConfigurationHandler.kt
@@ -63,6 +63,7 @@ actual class ConfigurationHandler(
             val fileConf = PackConfig(utilities, configFile)
             packConfig.setClientMods(fileConf.clientMods)
             packConfig.setInclusions(fileConf.inclusions)
+            packConfig.setModsWhitelist(fileConf.modsWhitelist)
             packConfig.modpackDir = fileConf.modpackDir
             packConfig.minecraftVersion = fileConf.minecraftVersion
             packConfig.modloader = fileConf.modloader
@@ -94,8 +95,13 @@ actual class ConfigurationHandler(
         log.info("Checking configuration...")
         if (packConfig.clientMods.isEmpty()) {
             log.warn("No clientside-only mods specified. Using fallback list.")
-            packConfig.setClientMods(apiProperties.clientSideMods())
+            packConfig.setClientMods(apiProperties.clientSideMods().toMutableList())
         }
+        if (packConfig.modsWhitelist.isEmpty()) {
+            log.warn("No whitelist mods specified. Using fallback list.")
+            packConfig.setModsWhitelist(apiProperties.whitelistedMods().toMutableList())
+        }
+
         if (!checkIconAndProperties(packConfig.serverIconPath)) {
             configCheck.serverIconErrors.add(Api.configuration_log_error_servericon(packConfig.serverIconPath))
             log.error("The specified server-icon does not exist: ${packConfig.serverIconPath}")
diff --git a/serverpackcreator-api/src/jvmMain/kotlin/de/griefed/serverpackcreator/api/PackConfig.kt b/serverpackcreator-api/src/jvmMain/kotlin/de/griefed/serverpackcreator/api/PackConfig.kt
index ba61beccb..bede9a548 100644
--- a/serverpackcreator-api/src/jvmMain/kotlin/de/griefed/serverpackcreator/api/PackConfig.kt
+++ b/serverpackcreator-api/src/jvmMain/kotlin/de/griefed/serverpackcreator/api/PackConfig.kt
@@ -70,6 +70,11 @@ private const val clientModsComment =
     "\n No need to include version specifics. Must be the filenames of the mods, not their project names on CurseForge/Modrinth!" +
     "\n Example: [AmbientSounds-,ClientTweaks-,PackMenu-,BetterAdvancement-,jeiintegration-]"
 
+private const val whitelistComment =
+    "\n List of mods to include if present, regardless whether a match was found through the list of clientside-only mods." +
+    "\n No need to include version specifics. Must be the filenames of the mods, not their project names on CurseForge/Modrinth!" +
+    "\n Example: [Ping-Wheel-]"
+
 private const val includeServerPropertiesComment =
     "\n Include a server.properties in your server pack. Must be true or false." +
     "\n If no server.properties is provided but setting set to true, a default one will be provided. Default value is true."
@@ -118,6 +123,8 @@ private const val inclusionsKey = "inclusions"
 
 private const val clientModsKey = "clientMods"
 
+private const val whitelistKey = "whitelist"
+
 private const val includeServerPropertiesKey = "includeServerProperties"
 
 private const val includeServerIconKey = "includeServerIcon"
@@ -187,6 +194,7 @@ actual open class PackConfig actual constructor() : Pack<File, JsonNode, PackCon
      * Construct a new configuration model with custom values.
      *
      * @param clientMods                List of clientside mods to exclude from the server pack.
+     * @param whitelist                 List of mods to include if present, regardless whether a match was found through [clientMods]
      * @param copyDirs                  List of directories and/or files to include in the server pack.
      * @param modpackDir                The path to the modpack.
      * @param minecraftVersion          The Minecraft version the modpack uses.
@@ -206,6 +214,7 @@ actual open class PackConfig actual constructor() : Pack<File, JsonNode, PackCon
      */
     actual constructor(
         clientMods: List<String>,
+        whitelist: List<String>,
         copyDirs: List<InclusionSpecification>,
         modpackDir: String,
         minecraftVersion: String,
@@ -284,7 +293,8 @@ actual open class PackConfig actual constructor() : Pack<File, JsonNode, PackCon
         }
         setInclusions(inclusionSpecs)
 
-        setClientMods(config.getOrElse(clientModsKey, listOf("")) as ArrayList<String>)
+        setClientMods(config.getOrElse(clientModsKey, listOf("")).toMutableList())
+        setModsWhitelist(config.getOrElse(whitelistKey, listOf("")).toMutableList())
         modpackDir = config.getOrElse(modpackDirKey, "")
         minecraftVersion = config.getOrElse(minecraftVersionKey, "")
         modloader = config.getOrElse(modLoaderKey, "")
@@ -401,6 +411,9 @@ actual open class PackConfig actual constructor() : Pack<File, JsonNode, PackCon
         conf.setComment(clientModsKey, clientModsComment)
         conf.set<Any>(clientModsKey, clientMods)
 
+        conf.setComment(whitelistKey, whitelistComment)
+        conf.set<Any>(whitelistKey, modsWhitelist)
+
         conf.setComment(includeServerPropertiesKey, includeServerPropertiesComment)
         conf.set<Any>(includeServerPropertiesKey, isServerPropertiesInclusionDesired)
 
diff --git a/serverpackcreator-api/src/jvmMain/kotlin/de/griefed/serverpackcreator/api/ServerPackHandler.kt b/serverpackcreator-api/src/jvmMain/kotlin/de/griefed/serverpackcreator/api/ServerPackHandler.kt
index 1736008ad..d71a5358b 100644
--- a/serverpackcreator-api/src/jvmMain/kotlin/de/griefed/serverpackcreator/api/ServerPackHandler.kt
+++ b/serverpackcreator-api/src/jvmMain/kotlin/de/griefed/serverpackcreator/api/ServerPackHandler.kt
@@ -199,6 +199,7 @@ actual class ServerPackHandler actual constructor(
         modpackDir: String,
         inclusions: ArrayList<InclusionSpecification>,
         clientMods: List<String>,
+        whitelist: List<String>,
         minecraftVersion: String,
         destination: String,
         modloader: String
@@ -233,6 +234,7 @@ actual class ServerPackHandler actual constructor(
                 destination,
                 exclusions,
                 clientMods,
+                whitelist,
                 minecraftVersion,
                 modloader
             )
@@ -265,6 +267,7 @@ actual class ServerPackHandler actual constructor(
         destination: String,
         exclusions: MutableList<Regex>,
         clientMods: List<String>,
+        whitelist: List<String>,
         minecraftVersion: String,
         modloader: String
     ): List<ServerPackFile> {
@@ -308,7 +311,7 @@ actual class ServerPackHandler actual constructor(
                 } catch (ignored: IOException) {
                 }
                 acquired = mutableListOf()
-                for (mod in getModsToInclude(clientDir, clientMods, minecraftVersion, modloader)) {
+                for (mod in getModsToInclude(clientDir, clientMods, whitelist, minecraftVersion, modloader)) {
                     acquired.add(ServerPackFile(mod, File(serverDir, mod.name)))
                 }
                 processed = runFilters(acquired, inclusion, modpackDir)
@@ -817,6 +820,7 @@ actual class ServerPackHandler actual constructor(
     override fun getModsToInclude(
         modsDir: String,
         userSpecifiedClientMods: List<String>,
+        userSpecifiedModsWhitelist: List<String>,
         minecraftVersion: String,
         modloader: String
     ): List<File> {
@@ -857,7 +861,7 @@ actual class ServerPackHandler actual constructor(
         }
 
         // Exclude user-specified mods from copying.
-        excludeUserSpecifiedMod(userSpecifiedClientMods, modsInModpack)
+        excludeUserSpecifiedMod(userSpecifiedClientMods, userSpecifiedModsWhitelist, modsInModpack)
         return ArrayList(modsInModpack)
     }
 
@@ -975,11 +979,11 @@ actual class ServerPackHandler actual constructor(
     /**
      * @author Griefed
      */
-    override fun excludeUserSpecifiedMod(userSpecifiedExclusions: List<String>, modsInModpack: TreeSet<File>) {
+    override fun excludeUserSpecifiedMod(userSpecifiedExclusions: List<String>, userSpecifiedModsWhitelist: List<String>, modsInModpack: TreeSet<File>) {
         if (userSpecifiedExclusions.isNotEmpty()) {
             log.info("Performing ${apiProperties.exclusionFilter}-type checks for user-specified clientside-only mod exclusion.")
             for (userSpecifiedExclusion in userSpecifiedExclusions) {
-                exclude(userSpecifiedExclusion, modsInModpack)
+                exclude(userSpecifiedExclusion, userSpecifiedModsWhitelist, modsInModpack)
             }
         } else {
             log.warn("User specified no clientside-only mods.")
@@ -1022,22 +1026,23 @@ actual class ServerPackHandler actual constructor(
     /**
      * @author Griefed
      */
-    override fun exclude(userSpecifiedExclusion: String, modsInModpack: TreeSet<File>) {
-        modsInModpack.removeIf {
+    override fun exclude(userSpecifiedExclusion: String, userSpecifiedModsWhitelist: List<String>, modsInModpack: TreeSet<File>) {
+        modsInModpack.removeIf { modToCheck ->
             val excluded: Boolean
-            val check = it.name
+            val modName = modToCheck.name
             excluded = when (apiProperties.exclusionFilter) {
-                ExclusionFilter.END -> check.endsWith(userSpecifiedExclusion)
-                ExclusionFilter.CONTAIN -> check.contains(userSpecifiedExclusion)
-                ExclusionFilter.REGEX -> check.matches(userSpecifiedExclusion.toRegex())
-                ExclusionFilter.EITHER -> (check.startsWith(userSpecifiedExclusion) || check.endsWith(
-                    userSpecifiedExclusion
-                ) || check.contains(userSpecifiedExclusion) || check.matches(userSpecifiedExclusion.toRegex()))
-
-                ExclusionFilter.START -> check.startsWith(userSpecifiedExclusion)
+                ExclusionFilter.START -> modName.startsWith(userSpecifiedExclusion) && !userSpecifiedModsWhitelist.any { whitelistedMod -> modName.startsWith(whitelistedMod) }
+                ExclusionFilter.END -> modName.endsWith(userSpecifiedExclusion) && !userSpecifiedModsWhitelist.any { whitelistedMod -> modName.endsWith(whitelistedMod) }
+                ExclusionFilter.CONTAIN -> modName.contains(userSpecifiedExclusion) && !userSpecifiedModsWhitelist.any { whitelistedMod -> modName.contains(whitelistedMod) }
+                ExclusionFilter.REGEX -> modName.matches(userSpecifiedExclusion.toRegex()) && !userSpecifiedModsWhitelist.any { whitelistedMod -> modName.matches(whitelistedMod.toRegex()) }
+                ExclusionFilter.EITHER -> (
+                            (modName.startsWith(userSpecifiedExclusion) && !userSpecifiedModsWhitelist.any { whitelistedMod -> modName.startsWith(whitelistedMod) }) ||
+                            (modName.endsWith(userSpecifiedExclusion) && !userSpecifiedModsWhitelist.any { whitelistedMod -> modName.endsWith(whitelistedMod) }) ||
+                            (modName.contains(userSpecifiedExclusion) && !userSpecifiedModsWhitelist.any { whitelistedMod -> modName.contains(whitelistedMod) }) ||
+                            (modName.matches(userSpecifiedExclusion.toRegex()) && !userSpecifiedModsWhitelist.any { whitelistedMod -> modName.matches(whitelistedMod.toRegex()) }))
             }
             if (excluded) {
-                log.debug("Removed ${it.name} as per user-specified check: $userSpecifiedExclusion")
+                log.debug("Removed ${modToCheck.name} as per user-specified check: $userSpecifiedExclusion")
             }
             excluded
         }
diff --git a/serverpackcreator-api/src/jvmMain/resources/serverpackcreator.properties b/serverpackcreator-api/src/jvmMain/resources/serverpackcreator.properties
index 660084d62..2678a76c9 100644
--- a/serverpackcreator-api/src/jvmMain/resources/serverpackcreator.properties
+++ b/serverpackcreator-api/src/jvmMain/resources/serverpackcreator.properties
@@ -3,6 +3,7 @@ de.griefed.serverpackcreator.versioncheck.prerelease=false
 de.griefed.serverpackcreator.language=en_GB
 de.griefed.serverpackcreator.configuration.fallback.updateurl=https://raw.githubusercontent.com/Griefed/ServerPackCreator/main/serverpackcreator-api/src/jvmMain/resources/serverpackcreator.properties
 de.griefed.serverpackcreator.configuration.fallbackmodslist=3dskinlayers-,Absolutely-Not-A-Zoom-Mod-,AdvancedChat-,AdvancedChatCore-,AdvancedChatHUD-,AdvancedCompas-,Ambience,AmbientEnvironment-,AmbientSounds_,AreYouBlind-,Armor Status HUD-,ArmorSoundTweak-,BH-Menu-,Batty's Coordinates PLUS Mod,BetterAdvancements-,BetterAnimationsCollection-,BetterModsButton-,BetterDarkMode-,BetterF3-,BetterFog-,BetterFoliage-,BetterPingDisplay-,BetterPlacement-,BetterTaskbar-,BetterThirdPerson,BetterTitleScreen-,Blur-,BorderlessWindow-,CTM-,ChunkAnimator-,ClientTweaks_,CompletionistsIndex-,Controller Support-,Controlling-,CraftPresence-,Create_Questing-,CullLessLeaves-Reforged-,CustomCursorMod-,CustomMainMenu-,DefaultOptions_,DefaultSettings-,DeleteWorldsToTrash-,DetailArmorBar-,Ding-,DistantHorizons-,DripSounds-,Durability101-,DurabilityNotifier-,DynamicSurroundings-,DynamicSurroundingsHuds-,EffectsLeft-,EiraMoticons_,EnchantmentDescriptions-,EnhancedVisuals_,EquipmentCompare-,FPS-Monitor-,FabricCustomCursorMod-,Fallingleaves-,FancySpawnEggs,FancyVideo-API-,FirstPersonMod,FogTweaker-,ForgeCustomCursorMod-,FpsReducer-,FpsReducer2-,FullscreenWindowed-,GameMenuModOption-,HealthOverlay-,HeldItemTooltips-,HorseStatsMod-,ImmediatelyFastReforged-,InventoryEssentials_,InventoryHud_[1.17.1].forge-,InventorySpam-,InventoryTweaks-,ItemBorders-,ItemPhysicLite_,ItemStitchingFix-,JBRA-Client-,JustEnoughCalculation-,JustEnoughEffects-,JustEnoughProfessions-,LeaveMyBarsAlone-,LLOverlayReloaded-,LOTRDRP-,LegendaryTooltips,LegendaryTooltips-,LightOverlay-,MoBends,MouseTweaks-,MyServerIsCompatible-,Neat ,Neat-,NekosEnchantedBooks-,NoAutoJump-,NoFog-,Notes-,NotifMod-,OldJavaWarning-,OptiFine,OptiFine_,OptiForge,OptiForge-,OverflowingBars-,PackMenu-,PackModeMenu-,PickUpNotifier-,Ping-,PingHUD-,PresenceFootsteps-,RPG-HUD-,ReAuth-,Reforgium-,ResourceLoader-,ResourcePackOrganizer,Ryoamiclights-,ShoulderSurfing-,ShulkerTooltip-,SimpleDiscordRichPresence-,SimpleWorldTimer-,SoundFilters-,SpawnerFix-,StylishEffects-,TextruesRubidiumOptions-,TRansliterationLib-,TipTheScales-,Tips-,Toast Control-,Toast-Control-,ToastControl-,TravelersTitles-,VoidFog-,VR-Combat_,WindowedFullscreen-,WorldNameRandomizer-,[1.12.2]DamageIndicatorsMod-,[1.12.2]bspkrscore-,antighost-,anviltooltipmod-,appleskin-,armorchroma-,armorpointspp-,auditory-,authme-,auto-reconnect-,autojoin-,autoreconnect-,axolotl-item-fix-,backtools-,bannerunlimited-,beenfo-1.19-,better-recipe-book-,betterbiomeblend-,bhmenu-,blur-,borderless-mining-,catalogue-,charmonium-,chat_heads-,cherishedworlds-,cirback-1.0-,classicbar-,clickadv-,clienttweaks-,combat_music-,connectedness-,controllable-,cullleaves-,cullparticles-,custom-crosshair-mod-,customdiscordrpc-,darkness-,dashloader-,defaultoptions-,desiredservers-,discordrpc-,drippyloadingscreen-,drippyloadingscreen_,durabilitytooltip-,dynamic-fps-,dynamic-music-,dynamiclights-,dynmus-,effective-,eggtab-,eguilib-,eiramoticons-,embeddium-,enchantment-lore-,entity-texture-features-,entityculling-,essential_,exhaustedstamina-,extremesoundmuffler-,fabricemotes-,fancymenu_,fancymenu_video_extension,flickerfix-,fm_audio_extension_,forgemod_VoxelMap-,freelook-,galacticraft-rpc-,gamestagesviewer-,gpumemleakfix-,grid-,helium-,hiddenrecipebook_,hiddenrecipebook-,infinitemusic-,inventoryprofiles,invtweaks-,itemzoom,itlt-,jeed-,jehc-,jeiintegration_,just-enough-harvestcraft-,justenoughbeacons-,justenoughdrags-,justzoom_,keymap-,keywizard-,lazydfu-,lib39-,light-overlay-,lightfallclient-,lightspeed-,loadmyresources_,lock_minecart_view-,lootbeams-,lwl-,magnesium_extras-,maptooltip-,massunbind,mcbindtype-,mcwifipnp-,medievalmusic-,memoryusagescreen-,mightyarchitect-,mindful-eating-,minetogether-,mobplusplus-,modcredits-,modernworldcreation_,modnametooltip-,modnametooltip_,moreoverlays-,mousewheelie-,movement-vision-,multihotbar-,music-duration-reducer-,musicdr-,neiRecipeHandlers-,ngrok-lan-expose-mod-,no_nv_flash-,nopotionshift_,notenoughanimations-,oculus-,ornaments-,overloadedarmorbar-,panorama-,paperdoll-,physics-mod-,phosphor-,preciseblockplacing-,radon-,realm-of-lost-souls-,rebind_narrator-,rebind-narrator-,rebindnarrator-,rebrand-,reforgium-,replanter-,rubidium-,rubidium_extras-,screenshot-to-clipboard-,servercountryflags-,shutupexperimentalsettings-,shutupmodelloader-,signtools-,simple-rpc-,simpleautorun-,smartcursor-,smoothboot-,smoothfocus-,sodium-fabric-,sounddeviceoptions-,soundreloader-,spoticraft-,^textrues_embeddium_options-.*$,tconplanner-,textrues_embeddium_options-,timestamps-,tooltipscroller-,torchoptimizer-,torohealth-,totaldarkness,toughnessbar-,whats-that-slot-forge-,wisla-,xlifeheartcolors-,yisthereautojump-
+de.griefed.serverpackcreator.configuration.modswhitelist=Ping-Wheel-
 de.griefed.serverpackcreator.configuration.hastebinserver=https://haste.zneix.eu/documents
 de.griefed.serverpackcreator.configuration.aikar=-Xms4G -Xmx4G -XX:+UseG1GC -XX:+ParallelRefProcEnabled -XX:MaxGCPauseMillis=200 -XX:+UnlockExperimentalVMOptions -XX:+DisableExplicitGC -XX:+AlwaysPreTouch -XX:G1NewSizePercent=30 -XX:G1MaxNewSizePercent=40 -XX:G1HeapRegionSize=8M -XX:G1ReservePercent=20 -XX:G1HeapWastePercent=5 -XX:G1MixedGCCountTarget=4 -XX:InitiatingHeapOccupancyPercent=15 -XX:G1MixedGCLiveThresholdPercent=90 -XX:G1RSetUpdatingPauseTimePercent=5 -XX:SurvivorRatio=32 -XX:+PerfDisableSharedMem -XX:MaxTenuringThreshold=1 -Dusing.aikars.flags=https://mcflags.emc.gs -Daikars.new.flags=true
 de.griefed.serverpackcreator.serverpack.autodiscovery.enabled=true
diff --git a/serverpackcreator-api/src/jvmTest/kotlin/de/griefed/serverpackcreator/api/ConfigurationHandlerTest.kt b/serverpackcreator-api/src/jvmTest/kotlin/de/griefed/serverpackcreator/api/ConfigurationHandlerTest.kt
index fcf4d24dd..529c03b64 100644
--- a/serverpackcreator-api/src/jvmTest/kotlin/de/griefed/serverpackcreator/api/ConfigurationHandlerTest.kt
+++ b/serverpackcreator-api/src/jvmTest/kotlin/de/griefed/serverpackcreator/api/ConfigurationHandlerTest.kt
@@ -607,6 +607,9 @@ internal class ConfigurationHandlerTest {
             "TipTheScales",
             "WorldNameRandomizer"
         )
+        val whitelist = arrayListOf(
+            "Ping-Wheel-"
+        )
         val inclusions = ArrayList<InclusionSpecification>()
         inclusions.add(InclusionSpecification("config"))
         inclusions.add(InclusionSpecification("mods"))
@@ -628,6 +631,7 @@ internal class ConfigurationHandlerTest {
         Assertions.assertNotNull(
             PackConfig(
                 clientMods,
+                whitelist,
                 inclusions,
                 "src/jvmTest/resources/fabric_tests",
                 javaPath,
@@ -676,6 +680,9 @@ internal class ConfigurationHandlerTest {
             "TipTheScales",
             "WorldNameRandomizer"
         )
+        val whitelist = arrayListOf(
+            "Ping-Wheel-"
+        )
         val inclusions = ArrayList<InclusionSpecification>()
         inclusions.add(InclusionSpecification("config"))
         inclusions.add(InclusionSpecification("mods"))
@@ -696,6 +703,7 @@ internal class ConfigurationHandlerTest {
         Assertions.assertNotNull(
             PackConfig(
                 clientMods,
+                whitelist,
                 inclusions,
                 "src/jvmTest/resources/forge_tests",
                 javaPath,
diff --git a/serverpackcreator-gui/src/main/i18n/Gui_en_GB.properties b/serverpackcreator-gui/src/main/i18n/Gui_en_GB.properties
index 575b90d45..8e03bb0de 100644
--- a/serverpackcreator-gui/src/main/i18n/Gui_en_GB.properties
+++ b/serverpackcreator-gui/src/main/i18n/Gui_en_GB.properties
@@ -22,7 +22,9 @@ createserverpack.gui.createserverpack.labelsuffix=Server Pack Suffix
 createserverpack.gui.createserverpack.labelsuffix.tip=Optional. Suffix to append to the name of the server pack to be generated.
 createserverpack.gui.createserverpack.textsuffix.error=Server suffix contains illegal characters.
 createserverpack.gui.createserverpack.labelclientmods=Mod-Exclusions
-createserverpack.gui.createserverpack.labelclientmods.tip=Comma separated. Example: AmbientSounds-,BackTools-,BetterAdvancement-,BetterPing-
+createserverpack.gui.createserverpack.labelclientmods.tip=Mods to exclude from the server pack. Comma separated. Example: AmbientSounds-,BackTools-,BetterAdvancement-,BetterPing-
+createserverpack.gui.createserverpack.labelwhitelistmods=Whitelisted Mods
+createserverpack.gui.createserverpack.labelwhitelistmods.tip=Mods to include in the server pack, regardless of whether a match was found in the clientside-mods list. Comma separated. Example: Ping-Wheel.
 createserverpack.gui.createserverpack.textclientmods.error=Must not end with ',', spaces or contain illegal characters.
 createserverpack.gui.createserverpack.labelcopydirs=Server-files
 createserverpack.gui.createserverpack.labelcopydirs.tip=Comma separated. Example: mods, config, options.txt or explicit source/file;destination/file-combinations
@@ -98,6 +100,13 @@ createserverpack.gui.buttonclientmods.revert.tip=Revert to last loaded configura
 createserverpack.gui.buttonclientmods.reset.tip=Reset to default
 createserverpack.gui.buttonclientmods.reset.merge.title=Custom values found!
 createserverpack.gui.buttonclientmods.reset.merge.message=Your clientside-mods contains entries not present in the default list.\nMerge yours with the default to keep your custom values?\nCaution! The resulting list may contain mods which should be present in the server!\nMerge only if you know what you are doing!
+createserverpack.gui.buttonwhitelistmods=Open the file explorer to select the clientside-only mods in your modpack.
+createserverpack.gui.buttonwhitelistmods.title=Select whitelisted mods in modpack.
+createserverpack.gui.buttonwhitelistmods.filter=Minecraft Mod-Jar
+createserverpack.gui.buttonwhitelistmods.revert.tip=Revert to last loaded configuration value
+createserverpack.gui.buttonwhitelistmods.reset.tip=Reset to default
+createserverpack.gui.buttonwhitelistmods.reset.merge.title=Custom values found!
+createserverpack.gui.buttonwhitelistmods.reset.merge.message=Your whitelist contains entries not present in the default list.\nMerge yours with the default to keep your custom values?\nCaution! The resulting list may contain mods which should be present in the server!\nMerge only if you know what you are doing!
 createserverpack.gui.buttoncopydirs.revert.tip=Revert to last loaded configuration value
 createserverpack.gui.buttoncopydirs.reset.tip=Reset to default
 createserverpack.gui.buttoncopydirs=Open the file explorer to select the directories to include in the server pack.
diff --git a/serverpackcreator-gui/src/main/kotlin/de/griefed/serverpackcreator/gui/window/configs/ConfigEditor.kt b/serverpackcreator-gui/src/main/kotlin/de/griefed/serverpackcreator/gui/window/configs/ConfigEditor.kt
index b4041f467..669f74472 100644
--- a/serverpackcreator-gui/src/main/kotlin/de/griefed/serverpackcreator/gui/window/configs/ConfigEditor.kt
+++ b/serverpackcreator-gui/src/main/kotlin/de/griefed/serverpackcreator/gui/window/configs/ConfigEditor.kt
@@ -135,9 +135,10 @@ class ConfigEditor(
     private val prepareServerSetting = ActionCheckBox(Gui.createserverpack_gui_createserverpack_checkboxserver.toString(),validationActionListener)
 
     private val advSetExclusionsSetting = ScrollTextArea(apiWrapper.apiProperties.clientSideMods().joinToString(","),Gui.createserverpack_gui_createserverpack_labelclientmods.toString(),validationChangeListener, guiProps)
+    private val advSetWhitelistSetting = ScrollTextArea(apiWrapper.apiProperties.whitelistedMods().joinToString(","),Gui.createserverpack_gui_createserverpack_labelwhitelistmods.toString(),validationChangeListener, guiProps)
     private val advSetJavaArgsSetting = ScrollTextArea("-Xmx4G -Xms4G",Gui.createserverpack_gui_createserverpack_javaargs.toString(),validationChangeListener, guiProps)
     private val advSetScriptKVPairsSetting = ScriptKVPairs(guiProps, this)
-    private val advSetPanel = AdvancedSettingsPanel(this, advSetExclusionsSetting, advSetJavaArgsSetting, advSetScriptKVPairsSetting, guiProps, apiWrapper.apiProperties)
+    private val advSetPanel = AdvancedSettingsPanel(this, advSetExclusionsSetting, advSetWhitelistSetting, advSetJavaArgsSetting, advSetScriptKVPairsSetting, guiProps, apiWrapper.apiProperties)
     private val advSetCollapsible = CollapsiblePanel(Gui.createserverpack_gui_advanced.toString(), advSetPanel)
 
     private val pluginPanels = apiWrapper.apiPlugins!!.getConfigPanels(this).toMutableList()
@@ -202,89 +203,100 @@ class ConfigEditor(
         updateMinecraftValues()
 
         // "cell column row width height"
+        var column = 0
         // Modpack directory
-        panel.add(modpackIcon, "cell 0 0,grow")
-        panel.add(modpackLabel, "cell 1 0,grow")
-        panel.add(modpackSetting, "cell 2 0,grow")
-        panel.add(modpackChooser, "cell 3 0, h 30!,w 30!")
-        panel.add(modpackCheck, "cell 4 0")
+        panel.add(modpackIcon, "cell 0 $column,grow")
+        panel.add(modpackLabel, "cell 1 $column,grow")
+        panel.add(modpackSetting, "cell 2 $column,grow")
+        panel.add(modpackChooser, "cell 3 $column, h 30!,w 30!")
+        panel.add(modpackCheck, "cell 4 $column")
 
         // Server Properties
-        panel.add(propertiesIcon, "cell 0 1,grow")
-        panel.add(propertiesLabel)
-        panel.add(propertiesSetting, "cell 2 1, split 3,grow, w 50:50:")
-        panel.add(propertiesQuickSelectLabel, "cell 2 1")
-        panel.add(propertiesQuickSelect, "cell 2 1,w 200!")
-        panel.add(propertiesChooser, "cell 3 1")
-        panel.add(propertiesOpen, "cell 4 1")
+        column++ //1
+        panel.add(propertiesIcon, "cell 0 $column,grow")
+        panel.add(propertiesLabel, "cell 1 $column,grow")
+        panel.add(propertiesSetting, "cell 2 $column, split 3,grow, w 50:50:")
+        panel.add(propertiesQuickSelectLabel, "cell 2 $column")
+        panel.add(propertiesQuickSelect, "cell 2 $column,w 200!")
+        panel.add(propertiesChooser, "cell 3 $column")
+        panel.add(propertiesOpen, "cell 4 $column")
 
         // Server Icon
-        panel.add(iconIcon, "cell 0 2,grow")
-        panel.add(iconLabel, "cell 1 2,grow")
-        panel.add(iconSetting, "cell 2 2, split 2,grow, w 50:50:")
-        panel.add(iconQuickSelectLabel, "cell 2 2")
-        panel.add(iconQuickSelect, "cell 2 2,w 200!")
-        panel.add(iconChooser, "cell 3 2")
-        panel.add(iconPreview, "cell 4 2")
+        column++ //2
+        panel.add(iconIcon, "cell 0 $column,grow")
+        panel.add(iconLabel, "cell 1 $column,grow")
+        panel.add(iconSetting, "cell 2 $column, split 2,grow, w 50:50:")
+        panel.add(iconQuickSelectLabel, "cell 2 $column")
+        panel.add(iconQuickSelect, "cell 2 $column,w 200!")
+        panel.add(iconChooser, "cell 3 $column")
+        panel.add(iconPreview, "cell 4 $column")
 
         // Server Files
-        panel.add(inclusionsIcon, "cell 0 3 1 3")
-        panel.add(inclusionsLabel, "cell 1 3 1 3,grow")
-        panel.add(inclusionsSetting, "cell 2 3 3 3, grow, w 10:500:, h 150:225:300")
+        column++ //3
+        panel.add(inclusionsIcon, "cell 0 $column 1 3")
+        panel.add(inclusionsLabel, "cell 1 $column 1 3,grow")
+        panel.add(inclusionsSetting, "cell 2 $column 3 3, grow, w 10:500:, h 150:225:300")
 
         // Server Pack Suffix
-        panel.add(suffixIcon, "cell 0 6,grow")
-        panel.add(suffixLabel, "cell 1 6,grow")
-        panel.add(suffixSetting, "cell 2 6 3 1,grow")
+        column += 3 //6
+        panel.add(suffixIcon, "cell 0 $column,grow")
+        panel.add(suffixLabel, "cell 1 $column,grow")
+        panel.add(suffixSetting, "cell 2 $column 3 1,grow")
 
         // Minecraft Version
-        panel.add(mcVersionIcon, "cell 0 7,grow")
-        panel.add(mcVersionLabel, "cell 1 7,grow")
-        panel.add(mcVersionSetting, "cell 2 7,w 200!")
+        column++ //7
+        panel.add(mcVersionIcon, "cell 0 $column,grow")
+        panel.add(mcVersionLabel, "cell 1 $column,grow")
+        panel.add(mcVersionSetting, "cell 2 $column,w 200!")
 
         // Java Version Of Minecraft Version
-        panel.add(javaVersionIcon, "cell 2 7, w 40!, gapleft 40")
-        panel.add(javaVersionLabel, "cell 2 7")
-        panel.add(javaVersionInfo, "cell 2 7, w 40!")
+        panel.add(javaVersionIcon, "cell 2 $column, w 40!, gapleft 40")
+        panel.add(javaVersionLabel, "cell 2 $column")
+        panel.add(javaVersionInfo, "cell 2 $column, w 40!")
 
         // Modloader
-        panel.add(modloaderIcon, "cell 0 8,grow")
-        panel.add(modloaderLabel, "cell 1 8,grow")
-        panel.add(modloaderSetting, "cell 2 8,w 200!")
+        column++ //8
+        panel.add(modloaderIcon, "cell 0 $column,grow")
+        panel.add(modloaderLabel, "cell 1 $column,grow")
+        panel.add(modloaderSetting, "cell 2 $column,w 200!")
 
         // Include Server Icon
-        panel.add(includeIconIcon, "cell 2 8, w 40!, gapleft 40,grow")
-        panel.add(includeIconSetting, "cell 2 8, w 200!")
+        panel.add(includeIconIcon, "cell 2 $column, w 40!, gapleft 40,grow")
+        panel.add(includeIconSetting, "cell 2 $column, w 200!")
 
         // Create ZIP Archive
-        panel.add(zipIcon, "cell 2 8, w 40!,grow")
-        panel.add(zipSetting, "cell 2 8, w 200!")
+        panel.add(zipIcon, "cell 2 $column, w 40!,grow")
+        panel.add(zipSetting, "cell 2 $column, w 200!")
 
         // Modloader Version
-        panel.add(modloaderVersionIcon, "cell 0 9,grow")
-        panel.add(modloaderVersionLabel, "cell 1 9,grow")
-        panel.add(modloaderVersionSetting, "cell 2 9,w 200!")
+        column++ //9
+        panel.add(modloaderVersionIcon, "cell 0 $column,grow")
+        panel.add(modloaderVersionLabel, "cell 1 $column,grow")
+        panel.add(modloaderVersionSetting, "cell 2 $column,w 200!")
 
         // Include Server Properties
-        panel.add(includePropertiesIcon, "cell 2 9, w 40!, gapleft 40,grow")
-        panel.add(includePropertiesSetting, "cell 2 9, w 200!")
+        panel.add(includePropertiesIcon, "cell 2 $column, w 40!, gapleft 40,grow")
+        panel.add(includePropertiesSetting, "cell 2 $column, w 200!")
 
         // Install Local Server
-        panel.add(prepareServerIcon, "cell 2 9, w 40!,grow")
-        panel.add(prepareServerSetting, "cell 2 9, w 200!")
+        panel.add(prepareServerIcon, "cell 2 $column, w 40!,grow")
+        panel.add(prepareServerSetting, "cell 2 $column, w 200!")
 
         // Advanced Settings
-        panel.add(advSetCollapsible, "cell 0 10 5,grow")
+        column++ //10
+        panel.add(advSetCollapsible, "cell 0 $column 5,grow")
 
         // Plugins
+        column++ //11
         if (pluginPanels.isNotEmpty()) {
-            panel.add(pluginPanel, "cell 0 11 5,grow")
+            panel.add(pluginPanel, "cell 0 $column 5,grow")
         }
         validateInputFields()
         lastConfig = getCurrentConfiguration()
         componentResizer.registerComponent(advSetExclusionsSetting, "cell 2 0 1 3,grow,w 10:500:,h %s!")
-        componentResizer.registerComponent(advSetJavaArgsSetting, "cell 2 3 1 3,grow,w 10:500:,h %s!")
-        componentResizer.registerComponent(advSetScriptKVPairsSetting.scrollPanel, "cell 2 6 1 3,grow,w 10:500:,h %s!")
+        componentResizer.registerComponent(advSetWhitelistSetting, "cell 2 3 1 3,grow,w 10:500:,h %s!")
+        componentResizer.registerComponent(advSetJavaArgsSetting, "cell 2 6 1 3,grow,w 10:500:,h %s!")
+        componentResizer.registerComponent(advSetScriptKVPairsSetting.scrollPanel, "cell 2 9 1 3,grow,w 10:500:,h %s!")
     }
 
     /**
@@ -342,6 +354,14 @@ class ConfigEditor(
         validateInputFields()
     }
 
+    /**
+     * @author Griefed
+     */
+    override fun setWhitelist(entries: MutableList<String>) {
+        advSetWhitelistSetting.text = apiWrapper.utilities!!.stringUtilities.buildString(entries)
+        validateInputFields()
+    }
+
     /**
      * @author Griefed
      */
@@ -465,6 +485,13 @@ class ConfigEditor(
         return advSetExclusionsSetting.text.replace(", ", ",")
     }
 
+    /**
+     * @author Griefed
+     */
+    override fun getWhitelist(): String {
+        return advSetWhitelistSetting.text.replace(", ", ",")
+    }
+
     /**
      * @author Griefed
      */
@@ -476,6 +503,17 @@ class ConfigEditor(
         )
     }
 
+    /**
+     * @author Griefed
+     */
+    override fun getWhitelistList(): MutableList<String> {
+        return apiWrapper.utilities!!.listUtilities.cleanList(
+            getWhitelist().split(",")
+                .dropLastWhile { it.isEmpty() }
+                .toMutableList()
+        )
+    }
+
     /**
      * @author Griefed
      */
@@ -489,6 +527,7 @@ class ConfigEditor(
     override fun getCurrentConfiguration(): PackConfig {
         return PackConfig(
             getClientSideModsList(),
+            getWhitelistList(),
             getInclusions(),
             getModpackDirectory(),
             getMinecraftVersion(),
@@ -740,6 +779,7 @@ class ConfigEditor(
 
         when {
             currentConfig.clientMods != lastConfig!!.clientMods
+                    || currentConfig.modsWhitelist != lastConfig!!.modsWhitelist
                     || currentConfig.inclusions != lastConfig!!.inclusions
                     || currentConfig.javaArgs != lastConfig!!.javaArgs
                     || currentConfig.minecraftVersion != lastConfig!!.minecraftVersion
@@ -775,8 +815,14 @@ class ConfigEditor(
             try {
                 setModpackDirectory(packConfig.modpackDir)
                 if (packConfig.clientMods.isEmpty()) {
-                    setClientSideMods(apiWrapper.apiProperties.clientSideMods())
-                    log.debug("Set clientMods with fallback list.")
+                    setClientSideMods(apiWrapper.apiProperties.clientSideMods().toMutableList())
+                    log.debug("Set clientMods to fallback list.")
+                } else {
+                    setClientSideMods(packConfig.clientMods)
+                }
+                if (packConfig.modsWhitelist.isEmpty()) {
+                    setWhitelist(apiWrapper.apiProperties.whitelistedMods().toMutableList())
+                    log.debug("Set whitelist to fallback list.")
                 } else {
                     setClientSideMods(packConfig.clientMods)
                 }
@@ -986,6 +1032,15 @@ class ConfigEditor(
         return advSetPanel.validateExclusions()
     }
 
+    /**
+     * Validate the input field for client mods.
+     *
+     * @author Griefed
+     */
+    fun validateWhitelist(): List<String> {
+        return advSetPanel.validateWhitelist()
+    }
+
     /**
      * Validate the input field for copy directories.
      *
diff --git a/serverpackcreator-gui/src/main/kotlin/de/griefed/serverpackcreator/gui/window/configs/components/ConfigCheckTimer.kt b/serverpackcreator-gui/src/main/kotlin/de/griefed/serverpackcreator/gui/window/configs/components/ConfigCheckTimer.kt
index 52dde08fa..851a90a8b 100644
--- a/serverpackcreator-gui/src/main/kotlin/de/griefed/serverpackcreator/gui/window/configs/components/ConfigCheckTimer.kt
+++ b/serverpackcreator-gui/src/main/kotlin/de/griefed/serverpackcreator/gui/window/configs/components/ConfigCheckTimer.kt
@@ -52,6 +52,9 @@ class ConfigCheckTimer(delay: Int, guiProps: GuiProps, tabbedConfigsTab: TabbedC
                     launch {
                         errors.addAll(editor.validateExclusions())
                     }
+                    launch {
+                        errors.addAll(editor.validateWhitelist())
+                    }
                     launch {
                         errors.addAll(editor.validateInclusions())
                     }
diff --git a/serverpackcreator-gui/src/main/kotlin/de/griefed/serverpackcreator/gui/window/configs/components/advanced/AdvancedSettingsPanel.kt b/serverpackcreator-gui/src/main/kotlin/de/griefed/serverpackcreator/gui/window/configs/components/advanced/AdvancedSettingsPanel.kt
index a3a542ed8..86f76bbfc 100644
--- a/serverpackcreator-gui/src/main/kotlin/de/griefed/serverpackcreator/gui/window/configs/components/advanced/AdvancedSettingsPanel.kt
+++ b/serverpackcreator-gui/src/main/kotlin/de/griefed/serverpackcreator/gui/window/configs/components/advanced/AdvancedSettingsPanel.kt
@@ -46,6 +46,7 @@ import javax.swing.JPanel
 class AdvancedSettingsPanel(
     private val configEditor: ConfigEditor,
     exclusions: ScrollTextArea,
+    whitelisted: ScrollTextArea,
     javaArgs: ScrollTextArea,
     scriptKVPairs: ScriptKVPairs,
     private val guiProps: GuiProps,
@@ -63,6 +64,12 @@ class AdvancedSettingsPanel(
     private val clientModsChooser = BalloonTipButton(null, guiProps.folderIcon, Gui.createserverpack_gui_browser.toString(), guiProps) { selectClientMods() }
     private val clientModsReset = BalloonTipButton(null, guiProps.resetIcon, Gui.createserverpack_gui_buttonclientmods_reset_tip.toString(), guiProps) { resetClientMods() }
 
+    private val whitelistModsIcon = StatusIcon(guiProps,Gui.createserverpack_gui_createserverpack_labelwhitelistmods_tip.toString())
+    private val whitelistModsLabel = ElementLabel(Gui.createserverpack_gui_createserverpack_labelwhitelistmods.toString())
+    private val whitelistModsRevert = BalloonTipButton(null, guiProps.revertIcon, Gui.createserverpack_gui_buttonwhitelistmods_revert_tip.toString(), guiProps) { revertWhitelist() }
+    private val whitelistModsChooser = BalloonTipButton(null, guiProps.folderIcon, Gui.createserverpack_gui_browser.toString(), guiProps) { selectWhitelist() }
+    private val whitelistModsReset = BalloonTipButton(null, guiProps.resetIcon, Gui.createserverpack_gui_buttonwhitelistmods_reset_tip.toString(), guiProps) { resetWhitelist() }
+
     private val javaArgsIcon = StatusIcon(guiProps,Gui.createserverpack_gui_createserverpack_javaargs_tip.toString())
     private val javaArgsLabel = ElementLabel(Gui.createserverpack_gui_createserverpack_javaargs.toString())
     private val javaArgsAikarsFlagsButton = AikarsFlagsButton(configEditor, guiProps)
@@ -73,26 +80,38 @@ class AdvancedSettingsPanel(
     private val advScriptSettingsReset = BalloonTipButton(null, guiProps.resetIcon, Gui.createserverpack_gui_reset.toString(), guiProps) { resetScriptKVPairs() }
 
     init {
+        var column = 0
         // Mod Exclusions
-        add(clientModsIcon, "cell 0 0 1 3")
-        add(clientModsLabel, "cell 1 0 1 3")
-        add(exclusions, "cell 2 0 1 3,grow,w 10:500:,h 150!")
-        add(clientModsRevert, "cell 3 0 2 1, h 30!, aligny center, alignx center,growx")
-        add(clientModsChooser, "cell 3 1 2 1, h 30!, aligny center, alignx center,growx")
-        add(clientModsReset, "cell 3 2 2 1, h 30!, aligny top, alignx center,growx")
+        add(clientModsIcon, "cell 0 $column 1 3")
+        add(clientModsLabel, "cell 1 $column 1 3")
+        add(exclusions, "cell 2 $column 1 3,grow,w 10:500:,h 150!")
+        add(clientModsRevert, "cell 3 $column 2 1, h 30!, aligny center, alignx center,growx")
+        add(clientModsChooser, "cell 3 ${column + 1} 2 1, h 30!, aligny center, alignx center,growx")
+        add(clientModsReset, "cell 3 ${column + 2} 2 1, h 30!, aligny top, alignx center,growx")
+
+        // Mod Whitelist
+        column += 3
+        add(whitelistModsIcon, "cell 0 $column 1 3")
+        add(whitelistModsLabel, "cell 1 $column 1 3")
+        add(whitelisted, "cell 2 $column 1 3,grow,w 10:500:,h 150!")
+        add(whitelistModsRevert, "cell 3 $column 2 1, h 30!, aligny center, alignx center,growx")
+        add(whitelistModsChooser, "cell 3 ${column + 1} 2 1, h 30!, aligny center, alignx center,growx")
+        add(whitelistModsReset, "cell 3 ${column + 2} 2 1, h 30!, aligny top, alignx center,growx")
 
         // Java Arguments
-        add(javaArgsIcon, "cell 0 3 1 3")
-        add(javaArgsLabel, "cell 1 3 1 3")
-        add(javaArgs, "cell 2 3 1 3,grow,w 10:500:,h 100!")
-        add(javaArgsAikarsFlagsButton, "cell 3 3 2 3,growy")
+        column += 3
+        add(javaArgsIcon, "cell 0 $column 1 3")
+        add(javaArgsLabel, "cell 1 $column 1 3")
+        add(javaArgs, "cell 2 $column 1 3,grow,w 10:500:,h 100!")
+        add(javaArgsAikarsFlagsButton, "cell 3 $column 2 3,growy")
 
         // Script Key-Value Pairs
-        add(advScriptSettingsIcon, "cell 0 6 1 3")
-        add(advScriptSettingsLabel, "cell 1 6 1 3")
-        add(scriptKVPairs.scrollPanel, "cell 2 6 1 3,grow,w 10:500:,h 200!")
-        add(advScriptSettingsRevert, "cell 3 6 2 1, h 30!, aligny center, alignx center,growx")
-        add(advScriptSettingsReset, "cell 3 7 2 1, h 30!, aligny top, alignx center,growx")
+        column += 3
+        add(advScriptSettingsIcon, "cell 0 $column 1 3")
+        add(advScriptSettingsLabel, "cell 1 $column 1 3")
+        add(scriptKVPairs.scrollPanel, "cell 2 $column 1 3,grow,w 10:500:,h 200!")
+        add(advScriptSettingsRevert, "cell 3 $column 2 1, h 30!, aligny center, alignx center,growx")
+        add(advScriptSettingsReset, "cell 3 ${column + 1} 2 1, h 30!, aligny top, alignx center,growx")
         isVisible = false
     }
 
@@ -110,6 +129,20 @@ class AdvancedSettingsPanel(
     }
 
     /**
+     * Reverts the list of whitelisted mods to the value of the last loaded configuration, if one
+     * is available.
+     *
+     * @author Griefed
+     */
+    private fun revertWhitelist() {
+        if (configEditor.lastConfig != null) {
+            configEditor.setWhitelist(configEditor.lastConfig!!.modsWhitelist)
+            configEditor.validateInputFields()
+        }
+    }
+
+    /**
+     * Let the user choose the mods to exclude by browsing the filesystem and selecting the mod-files.
      * @author Griefed
      */
     private fun selectClientMods() {
@@ -132,6 +165,30 @@ class AdvancedSettingsPanel(
     }
 
     /**
+     * Let the user choose the mods to whitelist by browsing the filesystem and selecting the mod-files.
+     * @author Griefed
+     */
+    private fun selectWhitelist() {
+        val whitelistChooser = if (File(configEditor.getModpackDirectory(), "mods").isDirectory) {
+            WhitelistChooser(File(configEditor.getModpackDirectory(), "mods"), guiProps.defaultFileChooserDimension)
+        } else {
+            WhitelistChooser(guiProps.defaultFileChooserDimension)
+        }
+
+        if (whitelistChooser.showOpenDialog(parent) == JFileChooser.APPROVE_OPTION) {
+            val whitelist: Array<File> = whitelistChooser.selectedFiles
+            val whitelistFilenames: TreeSet<String> = TreeSet()
+            whitelistFilenames.addAll(configEditor.getWhitelistList())
+            for (mod in whitelist) {
+                whitelistFilenames.add(mod.name)
+            }
+            configEditor.setWhitelist(whitelistFilenames.toMutableList())
+            log.debug("Selected mods: $whitelistFilenames")
+        }
+    }
+
+    /**
+     * Reset the clientside-mods list to the default value from the ServerPackCreator configuration.
      * @author Griefed
      */
     private fun resetClientMods() {
@@ -153,10 +210,40 @@ class AdvancedSettingsPanel(
                     configEditor.setClientSideMods(set.toMutableList())
                 }
 
-                1 -> configEditor.setClientSideMods(default)
+                1 -> configEditor.setClientSideMods(default.toMutableList())
             }
         } else {
-            configEditor.setClientSideMods(default)
+            configEditor.setClientSideMods(default.toMutableList())
+        }
+    }
+
+    /**
+     * Reset the whitelist to the default value from the ServerPackCreator configuration.
+     * @author Griefed
+     */
+    private fun resetWhitelist() {
+        val current = configEditor.getWhitelistList()
+        val default = apiProperties.whitelistedMods()
+        if (!default.any { mod -> !default.contains(mod) }) {
+            when (JOptionPane.showConfirmDialog(
+                this,
+                Gui.createserverpack_gui_buttonwhitelistmods_reset_merge_message.toString(),
+                Gui.createserverpack_gui_buttonwhitelistmods_reset_merge_title.toString(),
+                JOptionPane.YES_NO_OPTION,
+                JOptionPane.WARNING_MESSAGE,
+                guiProps.warningIcon
+            )) {
+                0 -> {
+                    val set = TreeSet<String>()
+                    set.addAll(current)
+                    set.addAll(default)
+                    configEditor.setWhitelist(set.toMutableList())
+                }
+
+                1 -> configEditor.setWhitelist(default.toMutableList())
+            }
+        } else {
+            configEditor.setWhitelist(default.toMutableList())
         }
     }
 
@@ -194,4 +281,23 @@ class AdvancedSettingsPanel(
         }
         return errors
     }
+
+    /**
+     * Validate the input field for whitelisted mods.
+     *
+     * @author Griefed
+     */
+    fun validateWhitelist(): List<String> {
+        val errors: MutableList<String> = ArrayList(10)
+        if (!configEditor.getWhitelist().matches(guiProps.whitespace)) {
+            whitelistModsIcon.info()
+        } else {
+            errors.add(Gui.configuration_log_error_formatting.toString())
+            whitelistModsIcon.error("<html>${errors.joinToString("<br>")}</html>")
+        }
+        for (error in errors) {
+            log.error(error)
+        }
+        return errors
+    }
 }
\ No newline at end of file
diff --git a/serverpackcreator-gui/src/main/kotlin/de/griefed/serverpackcreator/gui/window/configs/components/advanced/WhitelistChooser.kt b/serverpackcreator-gui/src/main/kotlin/de/griefed/serverpackcreator/gui/window/configs/components/advanced/WhitelistChooser.kt
new file mode 100644
index 000000000..3a4561103
--- /dev/null
+++ b/serverpackcreator-gui/src/main/kotlin/de/griefed/serverpackcreator/gui/window/configs/components/advanced/WhitelistChooser.kt
@@ -0,0 +1,48 @@
+/* Copyright (C) 2023  Griefed
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
+ * USA
+ *
+ * The full license can be found at https:github.com/Griefed/ServerPackCreator/blob/main/LICENSE
+ */
+package de.griefed.serverpackcreator.gui.window.configs.components.advanced
+
+import Gui
+import de.griefed.serverpackcreator.gui.components.BaseFileChooser
+import java.awt.Dimension
+import java.io.File
+import javax.swing.filechooser.FileNameExtensionFilter
+
+/**
+ * File-chooser to select mod-JARs to add to the clientside-mods list of a server pack config.
+ *
+ * @author Griefed
+ */
+class WhitelistChooser(current: File?, dimension: Dimension) : BaseFileChooser() {
+    constructor(dimension: Dimension) : this(null, dimension)
+
+    init {
+        currentDirectory = current
+        isFileHidingEnabled = false
+        dialogTitle = Gui.createserverpack_gui_buttonwhitelistmods_title.toString()
+        fileSelectionMode = FILES_ONLY
+        fileFilter = FileNameExtensionFilter(
+            Gui.createserverpack_gui_buttonwhitelistmods_filter.toString(), "jar"
+        )
+        isAcceptAllFileFilterUsed = false
+        isMultiSelectionEnabled = true
+        preferredSize = dimension
+    }
+}
\ No newline at end of file
diff --git a/serverpackcreator-gui/src/main/kotlin/de/griefed/serverpackcreator/gui/window/configs/components/inclusions/InclusionsEditor.kt b/serverpackcreator-gui/src/main/kotlin/de/griefed/serverpackcreator/gui/window/configs/components/inclusions/InclusionsEditor.kt
index 846afd9de..caf1e9232 100644
--- a/serverpackcreator-gui/src/main/kotlin/de/griefed/serverpackcreator/gui/window/configs/components/inclusions/InclusionsEditor.kt
+++ b/serverpackcreator-gui/src/main/kotlin/de/griefed/serverpackcreator/gui/window/configs/components/inclusions/InclusionsEditor.kt
@@ -276,6 +276,7 @@ class InclusionsEditor(
                     apiWrapper.serverPackHandler!!.getServerPackDestination(configEditor.getCurrentConfiguration()),
                     mutableListOf(),
                     configEditor.getClientSideModsList(),
+                    configEditor.getWhitelistList(),
                     configEditor.getMinecraftVersion(),
                     configEditor.getModloader()
                 )
-- 
GitLab