|
16 | 16 | import java.io.IOException; |
17 | 17 | import java.io.InputStreamReader; |
18 | 18 | import java.nio.charset.StandardCharsets; |
| 19 | +import java.nio.file.Files; |
| 20 | +import java.nio.file.Path; |
| 21 | +import java.nio.file.Paths; |
19 | 22 | import java.security.MessageDigest; |
| 23 | +import java.util.Locale; |
| 24 | +import java.util.regex.Matcher; |
| 25 | +import java.util.regex.Pattern; |
20 | 26 |
|
21 | 27 |
|
22 | 28 | /** |
@@ -59,6 +65,10 @@ public static String GetMachineCode(int v) { |
59 | 65 | return GetMachineCode(); |
60 | 66 | } |
61 | 67 |
|
| 68 | + if(v== 3) { |
| 69 | + return getMachineCode_v3(); |
| 70 | + } |
| 71 | + |
62 | 72 | String operSys = System.getProperty("os.name").toLowerCase(); |
63 | 73 |
|
64 | 74 | if (operSys.contains("win")) { |
@@ -108,6 +118,43 @@ public static String GetMachineCode(int v) { |
108 | 118 |
|
109 | 119 | } |
110 | 120 |
|
| 121 | + private static String getMachineCode_v3() { |
| 122 | + final String operSys = System.getProperty("os.name").toLowerCase(Locale.ENGLISH); |
| 123 | + |
| 124 | + try { |
| 125 | + String seed = null; |
| 126 | + |
| 127 | + if (operSys.contains("win")) { |
| 128 | + seed = getWindowsUUID(); |
| 129 | + |
| 130 | + } else if (operSys.contains("mac")) { |
| 131 | + seed = getMacHardwareUUID(); |
| 132 | + |
| 133 | + } else if (operSys.contains("nix") || operSys.contains("nux") |
| 134 | + || operSys.contains("aix") || operSys.contains("linux")) { |
| 135 | + seed = getLinuxMachineSeed(); |
| 136 | + |
| 137 | + } else { |
| 138 | + return "This OS is not supported yet."; |
| 139 | + } |
| 140 | + |
| 141 | + if (seed == null) { |
| 142 | + return null; |
| 143 | + } |
| 144 | + |
| 145 | + seed = seed.trim(); |
| 146 | + if (seed.isEmpty()) { |
| 147 | + return null; |
| 148 | + } |
| 149 | + |
| 150 | + return sha256(seed); |
| 151 | + |
| 152 | + } catch (Exception e) { |
| 153 | + // Swallow to preserve current behavior; consider logging in real apps |
| 154 | + return null; |
| 155 | + } |
| 156 | + } |
| 157 | + |
111 | 158 | /** |
112 | 159 | * Check if the device is registered with the license key. |
113 | 160 | * @param license The license key object. |
@@ -448,4 +495,194 @@ private static String getRawDeviceID() |
448 | 495 | processors; |
449 | 496 | } |
450 | 497 |
|
| 498 | + |
| 499 | + /* ========================= |
| 500 | + Windows |
| 501 | + ========================= */ |
| 502 | + private static String getWindowsUUID() throws IOException, InterruptedException { |
| 503 | + // Primary: PowerShell CIM |
| 504 | + String out = run(new ProcessBuilder( |
| 505 | + "powershell.exe", "-NoProfile", "-NonInteractive", |
| 506 | + "-Command", "(Get-CimInstance -Class Win32_ComputerSystemProduct).UUID")); |
| 507 | + out = firstNonEmptyLine(out); |
| 508 | + if (isLikelyUUID(out)) { |
| 509 | + return out; |
| 510 | + } |
| 511 | + |
| 512 | + // Fallback: WMIC (deprecated on newer Windows but still present on many) |
| 513 | + out = run(new ProcessBuilder("wmic", "csproduct", "get", "UUID")); |
| 514 | + // WMIC outputs a header line "UUID" and then the value—pick the first line that looks like a UUID |
| 515 | + for (String line : out.split("\\R")) { |
| 516 | + line = line.trim(); |
| 517 | + if (isLikelyUUID(line)) return line; |
| 518 | + } |
| 519 | + return null; |
| 520 | + } |
| 521 | + |
| 522 | + /* ========================= |
| 523 | + macOS |
| 524 | + ========================= */ |
| 525 | + private static String getMacHardwareUUID() throws IOException, InterruptedException { |
| 526 | + // Matches: "Hardware UUID: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" |
| 527 | + String cmd = "system_profiler SPHardwareDataType | awk '/UUID/ { print $3; }'"; |
| 528 | + String out = runShell(cmd); |
| 529 | + out = firstNonEmptyLine(out); |
| 530 | + if (isLikelyUUID(out)) { |
| 531 | + return out; |
| 532 | + } |
| 533 | + |
| 534 | + // Optional fallback using ioreg, if needed |
| 535 | + String alt = runShell("ioreg -rd1 -c IOPlatformExpertDevice | awk '/IOPlatformUUID/ {print $4}' | tr -d '\"'"); |
| 536 | + alt = firstNonEmptyLine(alt); |
| 537 | + return isLikelyUUID(alt) ? alt : null; |
| 538 | + } |
| 539 | + |
| 540 | + /* ========================= |
| 541 | + Linux |
| 542 | + ========================= */ |
| 543 | + private static String getLinuxMachineSeed() throws IOException, InterruptedException { |
| 544 | + // 1) Raspberry Pi detection via /proc/device-tree/model (present on Pi; often missing in VMs/containers) |
| 545 | + String model = readFileIfExists("/proc/device-tree/model"); |
| 546 | + if (model == null) { |
| 547 | + // Some environments need this alt path or may deny access to device-tree; not fatal. |
| 548 | + model = readFileIfExists("/sys/firmware/devicetree/base/model"); |
| 549 | + } |
| 550 | + if (model != null && model.toLowerCase(Locale.ENGLISH).contains("raspberry")) { |
| 551 | + // Extract "Serial" from /proc/cpuinfo |
| 552 | + String cpuinfo = readFileIfExists("/proc/cpuinfo"); |
| 553 | + if (cpuinfo != null) { |
| 554 | + for (String line : cpuinfo.split("\\R")) { |
| 555 | + String t = line.trim(); |
| 556 | + if (t.toLowerCase(Locale.ENGLISH).startsWith("serial")) { |
| 557 | + String[] parts = t.split(":", 2); |
| 558 | + if (parts.length == 2) { |
| 559 | + String serial = parts[1].trim(); |
| 560 | + if (!serial.isEmpty()) return serial; |
| 561 | + } |
| 562 | + } |
| 563 | + } |
| 564 | + } |
| 565 | + // If Pi but serial not found, continue to the generic path |
| 566 | + } |
| 567 | + |
| 568 | + // 2) Generic Linux: try DMI product UUID (no sudo required on many distros) |
| 569 | + String dmi = readFileIfExists("/sys/class/dmi/id/product_uuid"); |
| 570 | + if (dmi != null) { |
| 571 | + dmi = firstNonEmptyLine(dmi); |
| 572 | + if (isLikelyUUID(dmi)) return dmi; |
| 573 | + } |
| 574 | + |
| 575 | + // 3) dmidecode (requires permissions; often needs sudo — will work in privileged envs) |
| 576 | + String dmiDecode = runMaybe(null, new ProcessBuilder("dmidecode", "-s", "system-uuid")); |
| 577 | + dmiDecode = firstNonEmptyLine(dmiDecode); |
| 578 | + if (isLikelyUUID(dmiDecode)) return dmiDecode; |
| 579 | + |
| 580 | + // 4) Your specified fallback when we cannot determine hardware: |
| 581 | + // Try UUID of /boot, then /boot/efi, then / |
| 582 | + String fsUuid = firstNonEmptyLine(runShell("findmnt --output=UUID --noheadings --target=/boot")); |
| 583 | + if (fsUuid == null || fsUuid.isEmpty()) { |
| 584 | + fsUuid = firstNonEmptyLine(runShell("findmnt --output=UUID --noheadings --target=/boot/efi")); |
| 585 | + } |
| 586 | + if (fsUuid == null || fsUuid.isEmpty()) { |
| 587 | + fsUuid = firstNonEmptyLine(runShell("findmnt --output=UUID --noheadings --target=/")); |
| 588 | + } |
| 589 | + if (fsUuid != null && !fsUuid.isEmpty()) { |
| 590 | + return fsUuid; |
| 591 | + } |
| 592 | + |
| 593 | + // 5) Last-resort for containerized / exotic hosts: OS installation id (not hardware!) |
| 594 | + String machineId = readFileIfExists("/etc/machine-id"); |
| 595 | + if (machineId == null) { |
| 596 | + machineId = readFileIfExists("/var/lib/dbus/machine-id"); |
| 597 | + } |
| 598 | + machineId = firstNonEmptyLine(machineId); |
| 599 | + if (machineId != null && !machineId.isEmpty()) { |
| 600 | + return machineId; |
| 601 | + } |
| 602 | + |
| 603 | + return null; |
| 604 | + } |
| 605 | + |
| 606 | + /* ========================= |
| 607 | + Helpers |
| 608 | + ========================= */ |
| 609 | + |
| 610 | + private static String runShell(String command) throws IOException, InterruptedException { |
| 611 | + // Use /bin/sh for portability across macOS and Linux |
| 612 | + return run(new ProcessBuilder("/bin/sh", "-c", command)); |
| 613 | + } |
| 614 | + |
| 615 | + private static String run(ProcessBuilder pb) throws IOException, InterruptedException { |
| 616 | + pb.redirectErrorStream(true); |
| 617 | + Process p = pb.start(); |
| 618 | + StringBuilder sb = new StringBuilder(); |
| 619 | + try (BufferedReader in = new BufferedReader( |
| 620 | + new InputStreamReader(p.getInputStream(), StandardCharsets.UTF_8))) { |
| 621 | + String line; |
| 622 | + while ((line = in.readLine()) != null) { |
| 623 | + if (sb.length() > 0) sb.append('\n'); |
| 624 | + sb.append(line); |
| 625 | + } |
| 626 | + } |
| 627 | + // Wait after draining streams to avoid deadlocks. |
| 628 | + p.waitFor(); |
| 629 | + return sb.toString().trim(); |
| 630 | + } |
| 631 | + |
| 632 | + private static String runMaybe(String defaultIfError, ProcessBuilder pb) { |
| 633 | + try { |
| 634 | + return run(pb); |
| 635 | + } catch (Exception e) { |
| 636 | + return defaultIfError; |
| 637 | + } |
| 638 | + } |
| 639 | + |
| 640 | + private static String readFileIfExists(String path) { |
| 641 | + try { |
| 642 | + Path p = Paths.get(path); |
| 643 | + if (!Files.exists(p)) return null; |
| 644 | + // Some files (like device-tree nodes) may contain NULs; read as bytes then clean |
| 645 | + byte[] bytes = Files.readAllBytes(p); |
| 646 | + String s = new String(bytes, StandardCharsets.UTF_8).replace("\u0000", ""); |
| 647 | + return s.trim(); |
| 648 | + } catch (Exception e) { |
| 649 | + return null; |
| 650 | + } |
| 651 | + } |
| 652 | + |
| 653 | + private static String firstNonEmptyLine(String text) { |
| 654 | + if (text == null) return null; |
| 655 | + for (String line : text.split("\\R")) { |
| 656 | + line = line.trim(); |
| 657 | + if (!line.isEmpty()) return line; |
| 658 | + } |
| 659 | + return null; |
| 660 | + } |
| 661 | + |
| 662 | + private static boolean isLikelyUUID(String s) { |
| 663 | + if (s == null) return false; |
| 664 | + String t = s.trim(); |
| 665 | + // Accept canonical UUIDs and common uppercase variants |
| 666 | + Pattern p = Pattern.compile("^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$"); |
| 667 | + Matcher m = p.matcher(t); |
| 668 | + if (m.matches()) return true; |
| 669 | + // Some firmware returns UUIDs without dashes; tolerate that too |
| 670 | + return t.matches("^[a-fA-F0-9]{32}$"); |
| 671 | + } |
| 672 | + |
| 673 | + private static String sha256(String input) { |
| 674 | + try { |
| 675 | + MessageDigest md = MessageDigest.getInstance("SHA-256"); |
| 676 | + byte[] digest = md.digest(input.getBytes(StandardCharsets.UTF_8)); |
| 677 | + StringBuilder hex = new StringBuilder(digest.length * 2); |
| 678 | + for (byte b : digest) { |
| 679 | + hex.append(Character.forDigit((b >>> 4) & 0xF, 16)); |
| 680 | + hex.append(Character.forDigit(b & 0xF, 16)); |
| 681 | + } |
| 682 | + return hex.toString(); |
| 683 | + } catch (Exception e) { |
| 684 | + return null; |
| 685 | + } |
| 686 | + } |
| 687 | + |
451 | 688 | } |
0 commit comments