33import com .fasterxml .jackson .core .type .TypeReference ;
44import com .fasterxml .jackson .databind .ObjectMapper ;
55import java .io .IOException ;
6+ import java .math .BigInteger ;
67import java .net .URI ;
78import java .net .URISyntaxException ;
89import java .nio .file .Files ;
910import java .nio .file .Path ;
1011import java .nio .file .Paths ;
1112import java .nio .file .StandardCopyOption ;
13+ import java .security .MessageDigest ;
14+ import java .security .NoSuchAlgorithmException ;
1215import java .util .ArrayList ;
1316import java .util .Collections ;
1417import java .util .HashSet ;
1518import java .util .List ;
1619import java .util .Set ;
20+ import java .util .UUID ;
1721import java .util .concurrent .Callable ;
1822import lombok .extern .slf4j .Slf4j ;
1923import me .itzg .helpers .errors .GenericException ;
2529import me .itzg .helpers .json .ObjectMappers ;
2630import me .itzg .helpers .users .model .JavaOp ;
2731import me .itzg .helpers .users .model .JavaUser ;
32+
33+ import org .apache .commons .codec .binary .Hex ;
34+ import org .apache .commons .codec .digest .Crypt ;
35+ import org .apache .commons .codec .digest .Md5Crypt ;
2836import org .apache .maven .artifact .versioning .ComparableVersion ;
37+ import org .codehaus .plexus .util .Os ;
38+
2939import picocli .CommandLine .ArgGroup ;
3040import picocli .CommandLine .Command ;
3141import picocli .CommandLine .ExitCode ;
@@ -46,6 +56,9 @@ public class ManageUsersCommand implements Callable<Integer> {
4656 @ Option (names = {"--help" , "-h" }, usageHelp = true )
4757 boolean help ;
4858
59+ @ Option (names = {"--offline" }, required = false , description = "Use for offline server, UUIDs are generated" )
60+ boolean offline ;
61+
4962 @ Option (names = "--output-directory" , defaultValue = "." )
5063 Path outputDirectory ;
5164
@@ -240,6 +253,10 @@ private JavaUser resolveJavaUserId(SharedFetch sharedFetch, List<? extends JavaU
240253 }
241254 }
242255
256+ if (offline ) {
257+ return getOfflineUUID (input );
258+ }
259+
243260 final UserApi userApi ;
244261 switch (userApiProvider ) {
245262 case mojang :
@@ -329,4 +346,38 @@ private void processInputAsFile(SharedFetch sharedFetch, String filePathUrl) thr
329346 private boolean usesTextUserList () {
330347 return version != null && new ComparableVersion (version ).compareTo (MIN_VERSION_USES_JSON ) < 0 ;
331348 }
349+
350+ private static JavaUser getOfflineUUID (String username ) {
351+ byte [] bytes = new byte [16 ];
352+ try {
353+ bytes = MessageDigest .getInstance ("MD5" ).digest (("OfflinePlayer:" +username ).getBytes ());
354+ }catch (NoSuchAlgorithmException e ){
355+ System .exit (1 );
356+ return JavaUser .builder ().name (username ).build ();
357+ }
358+
359+ // Force version = 3 (bits 12-15 of time_hi_and_version)
360+ bytes [6 ] &= 0x0F ;
361+ bytes [6 ] |= 0x30 ;
362+
363+ // Force variant = 2 (bits 6-7 of clock_seq_hi_and_reserved)
364+ bytes [8 ] &= 0x3F ;
365+ bytes [8 ] |= 0x80 ;
366+
367+ long msb = 0 ;
368+ long lsb = 0 ;
369+
370+ for (int i = 0 ; i < 8 ; i ++) {
371+ msb = (msb << 8 ) | (bytes [i ] & 0xFF );
372+ }
373+
374+ for (int i = 8 ; i < 16 ; i ++) {
375+ lsb = (lsb << 8 ) | (bytes [i ] & 0xFF );
376+ }
377+
378+ return JavaUser .builder ()
379+ .name (username )
380+ .uuid (new UUID (msb , lsb ).toString ())
381+ .build ();
382+ }
332383}
0 commit comments