88import com .github .kd_gaming1 .packcore .configpack .ConfigPackExtractor ;
99import com .google .gson .JsonObject ;
1010import eu .midnightdust .lib .config .MidnightConfig ;
11+ import net .fabricmc .loader .api .FabricLoader ;
1112import net .fabricmc .loader .api .entrypoint .PreLaunchEntrypoint ;
1213import org .slf4j .Logger ;
1314import org .slf4j .LoggerFactory ;
@@ -25,24 +26,25 @@ public class PackCorePreLaunch implements PreLaunchEntrypoint {
2526
2627 @ Override
2728 public void onPreLaunch () {
29+ Path gameDir = FabricLoader .getInstance ().getGameDir ();
2830 Path packcoreDir = PackCore .PACKCORE_DIR ;
29- Path configsDir = packcoreDir .resolve ("configs" );
31+ Path configsDir = packcoreDir .resolve ("configs" );
3032
3133 MidnightConfig .init ("packcore" , PackCoreConfig .class );
3234
3335 // Highest priority: restore a backup if one is pending
3436 if (!PackCoreConfig .pendingRestoreBackup .isBlank ()) {
35- applyPendingRestore (packcoreDir );
37+ applyPendingRestore (packcoreDir , gameDir );
3638 return ;
3739 }
3840
3941 // Next priority: the user explicitly chose a config from the wizard GUI
4042 if (!PackCoreConfig .pendingConfigPack .isBlank ()) {
41- applyPendingConfig (packcoreDir , configsDir );
43+ applyPendingConfig (gameDir , configsDir );
4244 return ;
4345 }
4446
45- // ── Normal path: auto-detect the best resolution match ─────────────────
47+ // Normal path: auto-detect the best resolution + GUI scale match
4648 ScreenResolution .ScreenSize screen = ScreenResolution .detect ();
4749
4850 ConfigPackScanner scanner = new ConfigPackScanner ();
@@ -60,38 +62,32 @@ public void onPreLaunch() {
6062 return ;
6163 }
6264
63- ConfigPackEntry selectedPack = findBestResolutionMatch (scannedPacks , screen .width (), screen .height ());
65+ ConfigPackEntry selectedPack = findBestMatch (scannedPacks , screen .width (), screen .height ());
6466
6567 if (selectedPack == null ) {
6668 LOGGER .warn ("No packs had valid resolution fields, aborting." );
6769 return ;
6870 }
6971
70- extractIfNeeded (selectedPack , packcoreDir );
72+ extractIfNeeded (selectedPack , gameDir );
7173 }
7274
73- // ── Pending (user-selected) config ────────────────────────────────────────
74-
7575 /**
7676 * Applies the pack whose filename is stored in {@link PackCoreConfig#pendingConfigPack}.
77- * <p>
78- * Always uses {@link ConfigPackExtractor.OverwriteMode#REPLACE_EXISTING} because
79- * the user explicitly asked to switch, so we want a clean application of the new pack.
77+ * Always uses REPLACE_EXISTING because the user explicitly asked to switch.
8078 * The pending flag is cleared regardless of success or failure.
8179 */
82- private void applyPendingConfig (Path packcoreDir , Path configsDir ) {
80+ private void applyPendingConfig (Path gameDir , Path configsDir ) {
8381 String pendingFile = PackCoreConfig .pendingConfigPack ;
8482 LOGGER .info ("Pending config switch requested: {}" , pendingFile );
8583
86- // Locate the zip in the configs directory
8784 Path zipPath = configsDir .resolve (pendingFile );
8885 if (!Files .exists (zipPath )) {
8986 LOGGER .error ("Pending config zip not found at: {}" , zipPath );
9087 clearPending ();
9188 return ;
9289 }
9390
94- // Re-scan so we get the ConfigPackEntry (with the parsed JsonObject)
9591 ConfigPackScanner scanner = new ConfigPackScanner ();
9692 List <ConfigPackEntry > packs ;
9793 try {
@@ -113,10 +109,9 @@ private void applyPendingConfig(Path packcoreDir, Path configsDir) {
113109 return ;
114110 }
115111
116- // Extract — fully replace
117112 try {
118113 ConfigPackExtractor .extractAll (
119- entry .zipPath (), packcoreDir ,
114+ entry .zipPath (), gameDir ,
120115 ConfigPackExtractor .OverwriteMode .REPLACE_EXISTING
121116 );
122117 } catch (IOException e ) {
@@ -125,18 +120,18 @@ private void applyPendingConfig(Path packcoreDir, Path configsDir) {
125120 return ;
126121 }
127122
128- JsonObject config = entry .config ();
129- String packVersion = config .has ("version" ) ? config .get ("version" ).getAsString () : "" ;
123+ JsonObject config = entry .config ();
124+ String packVersion = config .has ("version" ) ? config .get ("version" ).getAsString () : "" ;
130125
131- PackCoreConfig .lastAppliedVersion = packVersion ;
126+ PackCoreConfig .lastAppliedVersion = packVersion ;
132127 PackCoreConfig .lastAppliedPackFile = pendingFile ;
133- PackCoreConfig .pendingConfigPack = "" ;
128+ PackCoreConfig .pendingConfigPack = "" ;
134129 MidnightConfig .write (MOD_ID );
135130
136131 LOGGER .info ("Successfully applied pending config: {} (version: {})" , pendingFile , packVersion );
137132 }
138133
139- private void applyPendingRestore (Path packcoreDir ) {
134+ private void applyPendingRestore (Path packcoreDir , Path gameDir ) {
140135 String backupFile = PackCoreConfig .pendingRestoreBackup ;
141136 Path backupPath = packcoreDir .resolve ("backups" ).resolve (backupFile );
142137
@@ -149,9 +144,10 @@ private void applyPendingRestore(Path packcoreDir) {
149144 }
150145
151146 try {
152- // Backup files are relative to the game dir, so extract there
153- ConfigPackExtractor .extractAll (backupPath , packcoreDir .getParent (),
154- ConfigPackExtractor .OverwriteMode .REPLACE_EXISTING );
147+ ConfigPackExtractor .extractAll (
148+ backupPath , gameDir ,
149+ ConfigPackExtractor .OverwriteMode .REPLACE_EXISTING
150+ );
155151 LOGGER .info ("Successfully restored backup: {}" , backupFile );
156152 } catch (IOException e ) {
157153 LOGGER .error ("Failed to restore backup '{}': {}" , backupFile , e .getMessage ());
@@ -160,44 +156,50 @@ private void applyPendingRestore(Path packcoreDir) {
160156 clearPendingRestore ();
161157 }
162158
163- /** Clears {@code applyPendingRestore} and persists the change. */
164159 private static void clearPendingRestore () {
165160 PackCoreConfig .pendingRestoreBackup = "" ;
166161 MidnightConfig .write (MOD_ID );
167162 }
168163
169- /** Clears {@code pendingConfigPack} and persists the change. */
170164 private static void clearPending () {
171165 PackCoreConfig .pendingConfigPack = "" ;
172166 MidnightConfig .write (MOD_ID );
173167 }
174168
175- // ── Auto-detect helpers ───────────────────────────────────────────────────
176-
177169 /**
178- * Extracts {@code selectedPack} if it is newer than the installed version,
179- * or if no version has been applied yet.
170+ * Extracts {@code selectedPack} into the game directory if:
171+ * <ul>
172+ * <li>No version has been applied yet, or</li>
173+ * <li>The selected pack's filename matches the last applied file AND its version is newer.</li>
174+ * </ul>
175+ * If the filenames differ, extraction is skipped to preserve what the user has installed.
180176 */
181- private void extractIfNeeded (ConfigPackEntry selectedPack , Path packcoreDir ) {
182- JsonObject config = selectedPack .config ();
183- String packVersion = config .has ("version" ) ? config .get ("version" ).getAsString () : "" ;
184- String installedVersion = PackCoreConfig .lastAppliedVersion ;
177+ private void extractIfNeeded (ConfigPackEntry selectedPack , Path gameDir ) {
178+ JsonObject config = selectedPack .config ();
179+ String packVersion = config .has ("version" ) ? config .get ("version" ).getAsString () : "" ;
180+ String installedVersion = PackCoreConfig .lastAppliedVersion ;
181+ String installedPackFile = PackCoreConfig .lastAppliedPackFile ;
182+ String selectedPackFile = selectedPack .zipPath ().getFileName ().toString ();
185183
186- LOGGER .info ("Best resolution match: {} (version: {})" ,
187- selectedPack .zipPath ().getFileName (), packVersion );
184+ LOGGER .info ("Best match: {} (version: {})" , selectedPackFile , packVersion );
188185
189186 try {
190187 if (installedVersion .isEmpty ()) {
191188 LOGGER .info ("No config applied yet — performing full extraction." );
192189 ConfigPackExtractor .extractAll (
193- selectedPack .zipPath (), packcoreDir ,
190+ selectedPack .zipPath (), gameDir ,
194191 ConfigPackExtractor .OverwriteMode .REPLACE_EXISTING
195192 );
193+ } else if (!installedPackFile .equals (selectedPackFile )) {
194+ // Selected pack differs from last applied — keep what's installed.
195+ LOGGER .info ("Selected pack '{}' differs from last applied '{}', skipping." ,
196+ selectedPackFile , installedPackFile );
197+ return ;
196198 } else if (UpdateChecker .isNewerVersion (packVersion , installedVersion )) {
197199 LOGGER .info ("Newer config available ({} → {}), applying with SKIP_EXISTING." ,
198200 installedVersion , packVersion );
199201 ConfigPackExtractor .extractAll (
200- selectedPack .zipPath (), packcoreDir ,
202+ selectedPack .zipPath (), gameDir ,
201203 ConfigPackExtractor .OverwriteMode .SKIP_EXISTING
202204 );
203205 } else {
@@ -209,21 +211,22 @@ private void extractIfNeeded(ConfigPackEntry selectedPack, Path packcoreDir) {
209211 return ;
210212 }
211213
212- PackCoreConfig .lastAppliedVersion = packVersion ;
213- PackCoreConfig .lastAppliedPackFile = selectedPack . zipPath (). getFileName (). toString () ;
214+ PackCoreConfig .lastAppliedVersion = packVersion ;
215+ PackCoreConfig .lastAppliedPackFile = selectedPackFile ;
214216 MidnightConfig .write (MOD_ID );
215217
216218 LOGGER .info ("Successfully applied config version: {}" , packVersion );
217219 }
218220
219221 /**
220222 * Returns the pack whose target resolution is closest to the screen resolution
221- * using squared Euclidean distance.
223+ * using squared Euclidean distance. When two packs tie on distance, the one
224+ * with the higher guiScale wins.
222225 */
223- private ConfigPackEntry findBestResolutionMatch (List <ConfigPackEntry > packs ,
224- int screenWidth , int screenHeight ) {
225- ConfigPackEntry selectedPack = null ;
226- long bestDistanceSquared = Long . MAX_VALUE ;
226+ private ConfigPackEntry findBestMatch (List <ConfigPackEntry > packs , int screenWidth , int screenHeight ) {
227+ ConfigPackEntry best = null ;
228+ long bestDistSq = Long . MAX_VALUE ;
229+ int bestGuiScale = - 1 ;
227230
228231 for (ConfigPackEntry pack : packs ) {
229232 JsonObject config = pack .config ();
@@ -233,16 +236,18 @@ private ConfigPackEntry findBestResolutionMatch(List<ConfigPackEntry> packs,
233236 continue ;
234237 }
235238
236- long widthDiff = config .get ("targetWidth" ).getAsInt () - screenWidth ;
239+ long widthDiff = config .get ("targetWidth" ).getAsInt () - screenWidth ;
237240 long heightDiff = config .get ("targetHeight" ).getAsInt () - screenHeight ;
238- long distSq = widthDiff * widthDiff + heightDiff * heightDiff ;
241+ long distSq = widthDiff * widthDiff + heightDiff * heightDiff ;
242+ int guiScale = config .has ("guiScale" ) ? config .get ("guiScale" ).getAsInt () : 0 ;
239243
240- if (distSq < bestDistanceSquared ) {
241- bestDistanceSquared = distSq ;
242- selectedPack = pack ;
244+ if (distSq < bestDistSq || (distSq == bestDistSq && guiScale > bestGuiScale )) {
245+ bestDistSq = distSq ;
246+ bestGuiScale = guiScale ;
247+ best = pack ;
243248 }
244249 }
245250
246- return selectedPack ;
251+ return best ;
247252 }
248253}
0 commit comments