diff --git a/atlas/root/distribution.json b/atlas/root/distribution.json
index d05cbf4..34ae73b 100644
--- a/atlas/root/distribution.json
+++ b/atlas/root/distribution.json
@@ -401,6 +401,16 @@
"MD5": "ee0b1ce7458afa5c4701c02b42a9f475"
}
},
+ {
+ "id": "fr.surviepote:nosingleplayer:1.0.0@jar",
+ "name": "NoSingleplayer (SurviePote)",
+ "type": "FabricMod",
+ "artifact": {
+ "size": 3237,
+ "url": "https://git.kisakay.com/k/mc/raw/branch/main/atlas/root/servers/SurviePote-26.3/fabricmods/required/nosingleplayer-1.0.0.jar",
+ "MD5": "5e9857a863ecd07d25f05260c92c6703"
+ }
+ },
{
"id": "dev.tonimatas:packetfixer:3.3.6@jar",
"name": "Packet Fixer",
diff --git a/atlas/root/servers/SurviePote-26.3/fabricmods/required/nosingleplayer-1.0.0.jar b/atlas/root/servers/SurviePote-26.3/fabricmods/required/nosingleplayer-1.0.0.jar
new file mode 100644
index 0000000..e96193c
Binary files /dev/null and b/atlas/root/servers/SurviePote-26.3/fabricmods/required/nosingleplayer-1.0.0.jar differ
diff --git a/mods/nosingleplayer/.gradle/8.14.4/checksums/checksums.lock b/mods/nosingleplayer/.gradle/8.14.4/checksums/checksums.lock
new file mode 100644
index 0000000..2b4a0a2
Binary files /dev/null and b/mods/nosingleplayer/.gradle/8.14.4/checksums/checksums.lock differ
diff --git a/mods/nosingleplayer/.gradle/8.14.4/checksums/md5-checksums.bin b/mods/nosingleplayer/.gradle/8.14.4/checksums/md5-checksums.bin
new file mode 100644
index 0000000..d4bbcb9
Binary files /dev/null and b/mods/nosingleplayer/.gradle/8.14.4/checksums/md5-checksums.bin differ
diff --git a/mods/nosingleplayer/.gradle/8.14.4/checksums/sha1-checksums.bin b/mods/nosingleplayer/.gradle/8.14.4/checksums/sha1-checksums.bin
new file mode 100644
index 0000000..19c59d8
Binary files /dev/null and b/mods/nosingleplayer/.gradle/8.14.4/checksums/sha1-checksums.bin differ
diff --git a/mods/nosingleplayer/.gradle/8.14.4/fileChanges/last-build.bin b/mods/nosingleplayer/.gradle/8.14.4/fileChanges/last-build.bin
new file mode 100644
index 0000000..f76dd23
Binary files /dev/null and b/mods/nosingleplayer/.gradle/8.14.4/fileChanges/last-build.bin differ
diff --git a/mods/nosingleplayer/.gradle/8.14.4/fileHashes/fileHashes.lock b/mods/nosingleplayer/.gradle/8.14.4/fileHashes/fileHashes.lock
new file mode 100644
index 0000000..4068fcc
Binary files /dev/null and b/mods/nosingleplayer/.gradle/8.14.4/fileHashes/fileHashes.lock differ
diff --git a/mods/nosingleplayer/.gradle/8.14.4/gc.properties b/mods/nosingleplayer/.gradle/8.14.4/gc.properties
new file mode 100644
index 0000000..e69de29
diff --git a/mods/nosingleplayer/.gradle/9.8.0/checksums/checksums.lock b/mods/nosingleplayer/.gradle/9.8.0/checksums/checksums.lock
new file mode 100644
index 0000000..cf4eaf3
Binary files /dev/null and b/mods/nosingleplayer/.gradle/9.8.0/checksums/checksums.lock differ
diff --git a/mods/nosingleplayer/.gradle/9.8.0/checksums/md5-checksums.bin b/mods/nosingleplayer/.gradle/9.8.0/checksums/md5-checksums.bin
new file mode 100644
index 0000000..27d5c46
Binary files /dev/null and b/mods/nosingleplayer/.gradle/9.8.0/checksums/md5-checksums.bin differ
diff --git a/mods/nosingleplayer/.gradle/9.8.0/checksums/sha1-checksums.bin b/mods/nosingleplayer/.gradle/9.8.0/checksums/sha1-checksums.bin
new file mode 100644
index 0000000..7b0f5f4
Binary files /dev/null and b/mods/nosingleplayer/.gradle/9.8.0/checksums/sha1-checksums.bin differ
diff --git a/mods/nosingleplayer/.gradle/9.8.0/executionHistory/executionHistory.bin b/mods/nosingleplayer/.gradle/9.8.0/executionHistory/executionHistory.bin
new file mode 100644
index 0000000..c6491cd
Binary files /dev/null and b/mods/nosingleplayer/.gradle/9.8.0/executionHistory/executionHistory.bin differ
diff --git a/mods/nosingleplayer/.gradle/9.8.0/executionHistory/executionHistory.lock b/mods/nosingleplayer/.gradle/9.8.0/executionHistory/executionHistory.lock
new file mode 100644
index 0000000..e67e9ba
Binary files /dev/null and b/mods/nosingleplayer/.gradle/9.8.0/executionHistory/executionHistory.lock differ
diff --git a/mods/nosingleplayer/.gradle/9.8.0/fileChanges/last-build.bin b/mods/nosingleplayer/.gradle/9.8.0/fileChanges/last-build.bin
new file mode 100644
index 0000000..f76dd23
Binary files /dev/null and b/mods/nosingleplayer/.gradle/9.8.0/fileChanges/last-build.bin differ
diff --git a/mods/nosingleplayer/.gradle/9.8.0/fileHashes/fileHashes.bin b/mods/nosingleplayer/.gradle/9.8.0/fileHashes/fileHashes.bin
new file mode 100644
index 0000000..4d88323
Binary files /dev/null and b/mods/nosingleplayer/.gradle/9.8.0/fileHashes/fileHashes.bin differ
diff --git a/mods/nosingleplayer/.gradle/9.8.0/fileHashes/fileHashes.lock b/mods/nosingleplayer/.gradle/9.8.0/fileHashes/fileHashes.lock
new file mode 100644
index 0000000..4880d4f
Binary files /dev/null and b/mods/nosingleplayer/.gradle/9.8.0/fileHashes/fileHashes.lock differ
diff --git a/mods/nosingleplayer/.gradle/9.8.0/fileHashes/resourceHashesCache.bin b/mods/nosingleplayer/.gradle/9.8.0/fileHashes/resourceHashesCache.bin
new file mode 100644
index 0000000..538fd76
Binary files /dev/null and b/mods/nosingleplayer/.gradle/9.8.0/fileHashes/resourceHashesCache.bin differ
diff --git a/mods/nosingleplayer/.gradle/9.8.0/gc.properties b/mods/nosingleplayer/.gradle/9.8.0/gc.properties
new file mode 100644
index 0000000..e69de29
diff --git a/mods/nosingleplayer/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/mods/nosingleplayer/.gradle/buildOutputCleanup/buildOutputCleanup.lock
new file mode 100644
index 0000000..e1a34b8
Binary files /dev/null and b/mods/nosingleplayer/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ
diff --git a/mods/nosingleplayer/.gradle/buildOutputCleanup/cache.properties b/mods/nosingleplayer/.gradle/buildOutputCleanup/cache.properties
new file mode 100644
index 0000000..9039100
--- /dev/null
+++ b/mods/nosingleplayer/.gradle/buildOutputCleanup/cache.properties
@@ -0,0 +1,2 @@
+#Sun Oct 18 20:30:39 CEST 2026
+gradle.version=9.8.0
diff --git a/mods/nosingleplayer/.gradle/buildOutputCleanup/outputFiles.bin b/mods/nosingleplayer/.gradle/buildOutputCleanup/outputFiles.bin
new file mode 100644
index 0000000..56c5547
Binary files /dev/null and b/mods/nosingleplayer/.gradle/buildOutputCleanup/outputFiles.bin differ
diff --git a/mods/nosingleplayer/.gradle/vcs-1/gc.properties b/mods/nosingleplayer/.gradle/vcs-1/gc.properties
new file mode 100644
index 0000000..e69de29
diff --git a/mods/nosingleplayer/README.md b/mods/nosingleplayer/README.md
new file mode 100644
index 0000000..4dd0ca2
--- /dev/null
+++ b/mods/nosingleplayer/README.md
@@ -0,0 +1,11 @@
+# nosingleplayer (mod maison SurviePote)
+
+Désactive le bouton **Solo** du menu principal (Mixin sur `TitleScreen.init`).
+- MC 26.3, Fabric Loader 0.20.0, Java 25, `environment: client`.
+- Particularité 26.x : Mojang ne publie plus de mappings (ni Yarn ni `client_mappings`) et le
+ jeu n'est plus obfusqué → **pas de Loom**, compilation directe contre le jar client Mojang
+ (téléchargé au build) + loader/mixin en `compileOnly`. Les noms Mojang passent tels quels,
+ pas de refmap nécessaire.
+
+Build : `cd mods/nosingleplayer && ./gradlew build` (dans nix-shell racine pour Java 25).
+Jar : `build/libs/nosingleplayer-1.0.0.jar` → copié dans `atlas/.../fabricmods/required/`.
diff --git a/mods/nosingleplayer/build.gradle b/mods/nosingleplayer/build.gradle
new file mode 100644
index 0000000..72f2881
--- /dev/null
+++ b/mods/nosingleplayer/build.gradle
@@ -0,0 +1,66 @@
+// SurviePote - nosingleplayer : build SANS loom.
+// MC 26.3 n'est plus obfusqué (noms Mojang lisibles dans le jar vanilla) et Mojang ne
+// publie plus de mappings (pas de client_mappings/server_mappings, pas de Yarn 26.x).
+// On compile directement contre le jar client Mojang + loader/mixin en compileOnly.
+plugins {
+ id 'java'
+}
+
+version = project.mod_version
+group = project.maven_group
+
+base {
+ archivesName = project.archives_base_name
+}
+
+def mcDir = layout.buildDirectory.dir('mc')
+
+tasks.register('downloadMcLibs') {
+ def manifestUrl = 'https://piston-meta.mojang.com/mc/game/version_manifest_v2.json'
+ def mcVersion = project.minecraft_version
+ def loaderVersion = project.loader_version
+ outputs.dir(mcDir)
+ doLast {
+ def outDir = mcDir.get().asFile
+ outDir.mkdirs()
+ def dl = { String url, File dest ->
+ if (!dest.exists()) {
+ println "Téléchargement ${dest.name} ..."
+ new URL(url).withInputStream { i -> dest.withOutputStream { it << i } }
+ }
+ }
+ def manifest = new groovy.json.JsonSlurper().parse(new URL(manifestUrl))
+ def entry = manifest.versions.find { it.id == mcVersion }
+ if (entry == null) throw new GradleException("Version MC introuvable : ${mcVersion}")
+ def versionJson = new groovy.json.JsonSlurper().parse(new URL(entry.url))
+ dl(versionJson.downloads.client.url, new File(outDir, 'client.jar'))
+ dl("https://maven.fabricmc.net/net/fabricmc/fabric-loader/${loaderVersion}/fabric-loader-${loaderVersion}.jar",
+ new File(outDir, 'fabric-loader.jar'))
+ dl('https://maven.fabricmc.net/net/fabricmc/sponge-mixin/0.17.5+mixin.0.8.7/sponge-mixin-0.17.5+mixin.0.8.7.jar',
+ new File(outDir, 'sponge-mixin.jar'))
+ dl('https://repo1.maven.org/maven2/org/slf4j/slf4j-api/2.0.17/slf4j-api-2.0.17.jar',
+ new File(outDir, 'slf4j-api.jar'))
+ dl('https://libraries.minecraft.net/com/mojang/brigadier/1.3.11/brigadier-1.3.11.jar',
+ new File(outDir, 'brigadier.jar'))
+ }
+}
+
+dependencies {
+ compileOnly files({ tasks.named('downloadMcLibs').map { mcDir.get().asFile.listFiles().findAll { it.name.endsWith('.jar') } } })
+}
+
+tasks.withType(JavaCompile).configureEach {
+ it.options.release = 25
+ it.dependsOn('downloadMcLibs')
+}
+
+processResources {
+ inputs.property 'version', project.version
+ filesMatching('fabric.mod.json') {
+ expand 'version': project.version
+ }
+}
+
+java {
+ withSourcesJar()
+}
diff --git a/mods/nosingleplayer/build/classes/java/main/fr/surviepote/nosingleplayer/NoSingleplayer.class b/mods/nosingleplayer/build/classes/java/main/fr/surviepote/nosingleplayer/NoSingleplayer.class
new file mode 100644
index 0000000..a29087f
Binary files /dev/null and b/mods/nosingleplayer/build/classes/java/main/fr/surviepote/nosingleplayer/NoSingleplayer.class differ
diff --git a/mods/nosingleplayer/build/classes/java/main/fr/surviepote/nosingleplayer/mixin/TitleScreenMixin.class b/mods/nosingleplayer/build/classes/java/main/fr/surviepote/nosingleplayer/mixin/TitleScreenMixin.class
new file mode 100644
index 0000000..cd37007
Binary files /dev/null and b/mods/nosingleplayer/build/classes/java/main/fr/surviepote/nosingleplayer/mixin/TitleScreenMixin.class differ
diff --git a/mods/nosingleplayer/build/libs/nosingleplayer-1.0.0-sources.jar b/mods/nosingleplayer/build/libs/nosingleplayer-1.0.0-sources.jar
new file mode 100644
index 0000000..4b34e01
Binary files /dev/null and b/mods/nosingleplayer/build/libs/nosingleplayer-1.0.0-sources.jar differ
diff --git a/mods/nosingleplayer/build/libs/nosingleplayer-1.0.0.jar b/mods/nosingleplayer/build/libs/nosingleplayer-1.0.0.jar
new file mode 100644
index 0000000..e96193c
Binary files /dev/null and b/mods/nosingleplayer/build/libs/nosingleplayer-1.0.0.jar differ
diff --git a/mods/nosingleplayer/build/mc/brigadier.jar b/mods/nosingleplayer/build/mc/brigadier.jar
new file mode 100644
index 0000000..36cf0d6
Binary files /dev/null and b/mods/nosingleplayer/build/mc/brigadier.jar differ
diff --git a/mods/nosingleplayer/build/mc/client.jar b/mods/nosingleplayer/build/mc/client.jar
new file mode 100644
index 0000000..c2126ab
Binary files /dev/null and b/mods/nosingleplayer/build/mc/client.jar differ
diff --git a/mods/nosingleplayer/build/mc/fabric-loader.jar b/mods/nosingleplayer/build/mc/fabric-loader.jar
new file mode 100644
index 0000000..15d020f
Binary files /dev/null and b/mods/nosingleplayer/build/mc/fabric-loader.jar differ
diff --git a/mods/nosingleplayer/build/mc/slf4j-api.jar b/mods/nosingleplayer/build/mc/slf4j-api.jar
new file mode 100644
index 0000000..26b1545
Binary files /dev/null and b/mods/nosingleplayer/build/mc/slf4j-api.jar differ
diff --git a/mods/nosingleplayer/build/mc/sponge-mixin.jar b/mods/nosingleplayer/build/mc/sponge-mixin.jar
new file mode 100644
index 0000000..9139ef5
Binary files /dev/null and b/mods/nosingleplayer/build/mc/sponge-mixin.jar differ
diff --git a/mods/nosingleplayer/build/reports/problems/problems-report.html b/mods/nosingleplayer/build/reports/problems/problems-report.html
new file mode 100644
index 0000000..e906245
--- /dev/null
+++ b/mods/nosingleplayer/build/reports/problems/problems-report.html
@@ -0,0 +1,666 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ Gradle Configuration Cache
+
+
+
+
+
+
+ Loading...
+
+
+
+
+
+
+
diff --git a/mods/nosingleplayer/build/resources/main/fabric.mod.json b/mods/nosingleplayer/build/resources/main/fabric.mod.json
new file mode 100644
index 0000000..a1ac97b
--- /dev/null
+++ b/mods/nosingleplayer/build/resources/main/fabric.mod.json
@@ -0,0 +1,19 @@
+{
+ "schemaVersion": 1,
+ "id": "nosingleplayer",
+ "version": "1.0.0",
+ "name": "NoSingleplayer (SurviePote)",
+ "description": "Désactive le bouton Solo du menu principal (serveur SurviePote).",
+ "authors": ["SurviePote"],
+ "license": "MIT",
+ "environment": "client",
+ "entrypoints": {
+ "client": ["fr.surviepote.nosingleplayer.NoSingleplayer"]
+ },
+ "mixins": ["nosingleplayer.mixins.json"],
+ "depends": {
+ "fabricloader": ">=0.16.0",
+ "minecraft": "~26.3",
+ "java": ">=25"
+ }
+}
diff --git a/mods/nosingleplayer/build/resources/main/nosingleplayer.mixins.json b/mods/nosingleplayer/build/resources/main/nosingleplayer.mixins.json
new file mode 100644
index 0000000..daf6458
--- /dev/null
+++ b/mods/nosingleplayer/build/resources/main/nosingleplayer.mixins.json
@@ -0,0 +1,9 @@
+{
+ "required": true,
+ "package": "fr.surviepote.nosingleplayer.mixin",
+ "compatibilityLevel": "JAVA_25",
+ "client": ["TitleScreenMixin"],
+ "injectors": {
+ "defaultRequire": 1
+ }
+}
diff --git a/mods/nosingleplayer/build/tmp/compileJava/previous-compilation-data.bin b/mods/nosingleplayer/build/tmp/compileJava/previous-compilation-data.bin
new file mode 100644
index 0000000..908d863
Binary files /dev/null and b/mods/nosingleplayer/build/tmp/compileJava/previous-compilation-data.bin differ
diff --git a/mods/nosingleplayer/build/tmp/jar/MANIFEST.MF b/mods/nosingleplayer/build/tmp/jar/MANIFEST.MF
new file mode 100644
index 0000000..58630c0
--- /dev/null
+++ b/mods/nosingleplayer/build/tmp/jar/MANIFEST.MF
@@ -0,0 +1,2 @@
+Manifest-Version: 1.0
+
diff --git a/mods/nosingleplayer/build/tmp/sourcesJar/MANIFEST.MF b/mods/nosingleplayer/build/tmp/sourcesJar/MANIFEST.MF
new file mode 100644
index 0000000..58630c0
--- /dev/null
+++ b/mods/nosingleplayer/build/tmp/sourcesJar/MANIFEST.MF
@@ -0,0 +1,2 @@
+Manifest-Version: 1.0
+
diff --git a/mods/nosingleplayer/gradle.properties b/mods/nosingleplayer/gradle.properties
new file mode 100644
index 0000000..0366b77
--- /dev/null
+++ b/mods/nosingleplayer/gradle.properties
@@ -0,0 +1,13 @@
+# Fabric Properties (SurviePote - nosingleplayer)
+minecraft_version=26.3
+yarn_mappings=26.3
+loader_version=0.20.0
+loom_version=1.19.1
+
+# Mod Properties
+mod_version=1.0.0
+maven_group=fr.surviepote
+archives_base_name=nosingleplayer
+
+# Dependencies (unused here, kept for template ref)
+# fabric_version=0.163.0+26.3
diff --git a/mods/nosingleplayer/gradle/wrapper/gradle-wrapper.jar b/mods/nosingleplayer/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..5097068
Binary files /dev/null and b/mods/nosingleplayer/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/mods/nosingleplayer/gradle/wrapper/gradle-wrapper.properties b/mods/nosingleplayer/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..9b7cb4a
--- /dev/null
+++ b/mods/nosingleplayer/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,9 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.8.0-bin.zip
+networkTimeout=10000
+retries=0
+retryBackOffMs=500
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/mods/nosingleplayer/gradlew b/mods/nosingleplayer/gradlew
new file mode 100755
index 0000000..249efbb
--- /dev/null
+++ b/mods/nosingleplayer/gradlew
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+#
+# Copyright © 2015 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# gradlew start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh gradlew
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/mods/nosingleplayer/gradlew.bat b/mods/nosingleplayer/gradlew.bat
new file mode 100644
index 0000000..03754c4
--- /dev/null
+++ b/mods/nosingleplayer/gradlew.bat
@@ -0,0 +1,112 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem gradlew startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables, and ensure extensions are enabled
+setlocal EnableExtensions
+
+@rem Catch executions from older scripts and ensure they exit cleanly.
+@rem This can be removed once we can be reasonably confident that few people
+@rem will be migrating directly to this new wrapper.
+goto afterSafetyNet
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+goto exitWithErrorLevel
+:afterSafetyNet
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+1>&2 echo.
+1>&2 echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+1>&2 echo.
+1>&2 echo Please set the JAVA_HOME variable in your environment to match the
+1>&2 echo location of your Java installation.
+
+"%COMSPEC%" /c exit 1
+goto exitWithErrorLevel
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+1>&2 echo.
+1>&2 echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+1>&2 echo.
+1>&2 echo Please set the JAVA_HOME variable in your environment to match the
+1>&2 echo location of your Java installation.
+
+"%COMSPEC%" /c exit 1
+goto exitWithErrorLevel
+
+:execute
+@rem Setup the command line
+
+
+
+@rem Execute gradlew
+@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
+@rem which allows us to clear the local environment before executing the java command
+endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel & goto exitWithErrorLevel
+
+@rem This label must not be changed. We rely on old scripts being able to jump to this point.
+:exitWithErrorLevel
+@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
+"%COMSPEC%" /c exit %ERRORLEVEL%
diff --git a/mods/nosingleplayer/settings.gradle b/mods/nosingleplayer/settings.gradle
new file mode 100644
index 0000000..c665ff9
--- /dev/null
+++ b/mods/nosingleplayer/settings.gradle
@@ -0,0 +1,5 @@
+pluginManagement {
+ repositories {
+ gradlePluginPortal()
+ }
+}
diff --git a/mods/nosingleplayer/src/main/java/fr/surviepote/nosingleplayer/NoSingleplayer.java b/mods/nosingleplayer/src/main/java/fr/surviepote/nosingleplayer/NoSingleplayer.java
new file mode 100644
index 0000000..483f790
--- /dev/null
+++ b/mods/nosingleplayer/src/main/java/fr/surviepote/nosingleplayer/NoSingleplayer.java
@@ -0,0 +1,15 @@
+package fr.surviepote.nosingleplayer;
+
+import net.fabricmc.api.ClientModInitializer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class NoSingleplayer implements ClientModInitializer {
+
+ public static final Logger LOGGER = LoggerFactory.getLogger("nosingleplayer");
+
+ @Override
+ public void onInitializeClient() {
+ LOGGER.info("[SurviePote] Solo désactivé, direction le multi.");
+ }
+}
diff --git a/mods/nosingleplayer/src/main/java/fr/surviepote/nosingleplayer/mixin/TitleScreenMixin.java b/mods/nosingleplayer/src/main/java/fr/surviepote/nosingleplayer/mixin/TitleScreenMixin.java
new file mode 100644
index 0000000..dcf668d
--- /dev/null
+++ b/mods/nosingleplayer/src/main/java/fr/surviepote/nosingleplayer/mixin/TitleScreenMixin.java
@@ -0,0 +1,25 @@
+package fr.surviepote.nosingleplayer.mixin;
+
+import net.minecraft.client.gui.components.Button;
+import net.minecraft.client.gui.components.events.GuiEventListener;
+import net.minecraft.client.gui.screens.TitleScreen;
+import net.minecraft.network.chat.Component;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+@Mixin(TitleScreen.class)
+public class TitleScreenMixin {
+
+ @Inject(method = "init", at = @At("TAIL"))
+ private void surviepote$disableSingleplayer(CallbackInfo ci) {
+ TitleScreen self = (TitleScreen) (Object) this;
+ String solo = Component.translatable("menu.singleplayer").getString();
+ for (GuiEventListener child : self.children()) {
+ if (child instanceof Button button && button.getMessage().getString().equals(solo)) {
+ button.active = false;
+ }
+ }
+ }
+}
diff --git a/mods/nosingleplayer/src/main/resources/fabric.mod.json b/mods/nosingleplayer/src/main/resources/fabric.mod.json
new file mode 100644
index 0000000..48f39a9
--- /dev/null
+++ b/mods/nosingleplayer/src/main/resources/fabric.mod.json
@@ -0,0 +1,19 @@
+{
+ "schemaVersion": 1,
+ "id": "nosingleplayer",
+ "version": "${version}",
+ "name": "NoSingleplayer (SurviePote)",
+ "description": "Désactive le bouton Solo du menu principal (serveur SurviePote).",
+ "authors": ["SurviePote"],
+ "license": "MIT",
+ "environment": "client",
+ "entrypoints": {
+ "client": ["fr.surviepote.nosingleplayer.NoSingleplayer"]
+ },
+ "mixins": ["nosingleplayer.mixins.json"],
+ "depends": {
+ "fabricloader": ">=0.16.0",
+ "minecraft": "~26.3",
+ "java": ">=25"
+ }
+}
diff --git a/mods/nosingleplayer/src/main/resources/nosingleplayer.mixins.json b/mods/nosingleplayer/src/main/resources/nosingleplayer.mixins.json
new file mode 100644
index 0000000..daf6458
--- /dev/null
+++ b/mods/nosingleplayer/src/main/resources/nosingleplayer.mixins.json
@@ -0,0 +1,9 @@
+{
+ "required": true,
+ "package": "fr.surviepote.nosingleplayer.mixin",
+ "compatibilityLevel": "JAVA_25",
+ "client": ["TitleScreenMixin"],
+ "injectors": {
+ "defaultRequire": 1
+ }
+}
diff --git a/shell.nix b/shell.nix
index 4c0e61b..2b7c34c 100644
--- a/shell.nix
+++ b/shell.nix
@@ -28,6 +28,8 @@ pkgs.mkShell {
alsa-lib
pulseaudio
pipewire
+ # Build mods Fabric (loom 1.19+ exige Gradle 9).
+ gradle_9
libxkbcommon
mesa
libX11
diff --git a/sync-mods.sh b/sync-mods.sh
index c340f29..ec071a3 100755
--- a/sync-mods.sh
+++ b/sync-mods.sh
@@ -53,6 +53,13 @@ echo "== resourcepack/shader (Prism -> atlas files/) =="
cp -u "$HOME/.local/share/PrismLauncher/instances/26.3/minecraft/resourcepacks/FallPack_26.3_v3.1.zip" "atlas/root/servers/SurviePote-26.3/files/resourcepacks/" 2>/dev/null || true
cp -u "$HOME/.local/share/PrismLauncher/instances/26.3/minecraft/shaderpacks/Revelation-dev.zip" "atlas/root/servers/SurviePote-26.3/files/shaderpacks/" 2>/dev/null || true
+echo "== mod maison nosingleplayer (build si besoin) =="
+if [ ! -f mods/nosingleplayer/build/libs/nosingleplayer-1.0.0.jar ]; then
+ (cd mods/nosingleplayer && ./gradlew build 2>&1 | tail -n 2) || echo "BUILD nosingleplayer échoué (réseau/gradle requis)"
+fi
+cp -u mods/nosingleplayer/build/libs/nosingleplayer-1.0.0.jar "$ATLAS_MODS/" 2>/dev/null || true
+# Note : environment=client (menu titre), inutile côté serveur.
+
echo "== regen distribution.json =="
(cd atlas && npm run faststart -- generate distro) 2>&1 | tail -n 2
echo OK.