初始化 monorepo: Go后端(7微服务) + Unity客户端(9模块) + 启动器 HTML5原型: Three.js 3D体素世界, Perlin噪声地形, 原版材质, 22种方块 Minecraft创造模式背包: 双栏布局, 拖拽移动物品, 方向性元件引脚 AI助搭策划文档 + 客户端/服务端骨架 + Docker Compose + CI
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
using PCL.Core.App;
|
||||
using PCL.Core.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace PCL.Core.Minecraft.Java.Scanner;
|
||||
|
||||
public class DefaultPathsScanner : IJavaScanner
|
||||
{
|
||||
private const int MaxSearchDepth = 6;
|
||||
|
||||
public void Scan(ICollection<string> results)
|
||||
{
|
||||
try
|
||||
{
|
||||
var searchRoots = _GetSearchRoots();
|
||||
LogWrapper.Info($"[Java] 对下列目录进行广度关键词搜索:{Environment.NewLine}{string.Join(Environment.NewLine, searchRoots)}");
|
||||
|
||||
foreach (var root in searchRoots)
|
||||
{
|
||||
_BfsSearch(root, results);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogWrapper.Error(ex, "Java", "默认路径扫描失败");
|
||||
}
|
||||
}
|
||||
|
||||
private static HashSet<string> _GetSearchRoots()
|
||||
{
|
||||
var roots = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), ".minecraft", "runtime"),
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
|
||||
Path.Combine(Basics.ExecutableDirectory, "PCL")
|
||||
};
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
var keyFolders = new[] { "Program Files", "Program Files (x86)" };
|
||||
var drives = DriveInfo.GetDrives()
|
||||
.Where(d => d.DriveType.Equals(DriveType.Fixed) && d.IsReady)
|
||||
.Select(d => d.Name);
|
||||
|
||||
foreach (var drive in drives)
|
||||
{
|
||||
foreach (var folder in keyFolders)
|
||||
{
|
||||
roots.Add(Path.Combine(drive, folder));
|
||||
}
|
||||
|
||||
// 根目录关键词搜索
|
||||
try
|
||||
{
|
||||
var rootDirs = Directory.EnumerateDirectories(drive)
|
||||
.Where(dir => JavaConsts.MostPossibleKeywords.Any(k =>
|
||||
Path.GetFileName(dir).Contains(k, StringComparison.OrdinalIgnoreCase)));
|
||||
|
||||
foreach (var dir in rootDirs)
|
||||
roots.Add(dir);
|
||||
}
|
||||
catch (UnauthorizedAccessException) { /* 忽略无权限目录 */ }
|
||||
catch (IOException) { /* 忽略IO错误 */ }
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
|
||||
var programFilesX86 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
|
||||
|
||||
if (!string.IsNullOrEmpty(programFiles) && Directory.Exists(programFiles))
|
||||
roots.Add(programFiles);
|
||||
if (!string.IsNullOrEmpty(programFilesX86) && Directory.Exists(programFilesX86))
|
||||
roots.Add(programFilesX86);
|
||||
}
|
||||
|
||||
return roots;
|
||||
}
|
||||
|
||||
private static void _BfsSearch(string rootPath, ICollection<string> results)
|
||||
{
|
||||
if (!Directory.Exists(rootPath)) return;
|
||||
|
||||
var queue = new Queue<(string Path, int Depth)>();
|
||||
queue.Enqueue((rootPath, 0));
|
||||
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
var (current, depth) = queue.Dequeue();
|
||||
if (depth > MaxSearchDepth || !Directory.Exists(current)) continue;
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var subDir in Directory.EnumerateDirectories(current))
|
||||
{
|
||||
var javaExe = Path.Combine(subDir, "java.exe");
|
||||
if (File.Exists(javaExe))
|
||||
{
|
||||
results.Add(javaExe);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_ShouldExploreDeeper(subDir))
|
||||
queue.Enqueue((subDir, depth + 1));
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (ex is UnauthorizedAccessException or IOException or DirectoryNotFoundException)
|
||||
{
|
||||
LogWrapper.Debug($"跳过目录 {current}: {ex.Message}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogWrapper.Error(ex, "Java", $"搜索目录 {current} 时出错");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool _ShouldExploreDeeper(string path)
|
||||
{
|
||||
var name = Path.GetFileName(path).AsSpan();
|
||||
|
||||
foreach (var ex in JavaConsts.ExcludeFolderNames)
|
||||
if (name.Contains(ex, StringComparison.OrdinalIgnoreCase))
|
||||
return false;
|
||||
|
||||
foreach (var kw in JavaConsts.AllKeywords)
|
||||
if (name.Contains(kw, StringComparison.OrdinalIgnoreCase))
|
||||
return true;
|
||||
|
||||
return _IsVersionLikeDirectory(name);
|
||||
}
|
||||
|
||||
private static bool _IsVersionLikeDirectory(ReadOnlySpan<char> name)
|
||||
{
|
||||
if (name.IsEmpty || name.Length > 20)
|
||||
return false;
|
||||
|
||||
var hasDigit = false;
|
||||
foreach (var c in name)
|
||||
{
|
||||
if (char.IsDigit(c))
|
||||
{
|
||||
hasDigit = true;
|
||||
}
|
||||
else if (c != '.' && c != '_' && c != '-')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return hasDigit;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PCL.Core.Minecraft.Java.Scanner;
|
||||
public interface IJavaScanner
|
||||
{
|
||||
void Scan(ICollection<string> results);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
using PCL.Core.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace PCL.Core.Minecraft.Java.Scanner;
|
||||
public class MicrosoftStoreJavaScanner : IJavaScanner
|
||||
{
|
||||
private const string StorePackagePath =
|
||||
@"Packages\Microsoft.4297127D64EC6_8wekyb3d8bbwe\LocalCache\Local\runtime";
|
||||
|
||||
public void Scan(ICollection<string> results)
|
||||
{
|
||||
try
|
||||
{
|
||||
var basePath = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
StorePackagePath);
|
||||
|
||||
if (!Directory.Exists(basePath)) return;
|
||||
|
||||
// 第一级:java-runtime* 目录
|
||||
foreach (var runtimeDir in Directory.EnumerateDirectories(basePath))
|
||||
{
|
||||
if (!Path.GetFileName(runtimeDir).StartsWith("java-runtime", StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
|
||||
// 第二级:架构目录 (windows-x64等)
|
||||
foreach (var archDir in Directory.EnumerateDirectories(runtimeDir))
|
||||
{
|
||||
// 第三级:版本目录
|
||||
foreach (var versionDir in Directory.EnumerateDirectories(archDir))
|
||||
{
|
||||
var javaExe = Path.Combine(versionDir, "bin", "java.exe");
|
||||
if (File.Exists(javaExe))
|
||||
{
|
||||
LogWrapper.Info($"[Java] 检测到 Microsoft Store Java: {javaExe}");
|
||||
results.Add(javaExe);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogWrapper.Error(ex, "Java", "Microsoft Store Java 扫描失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using PCL.Core.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace PCL.Core.Minecraft.Java.Scanner;
|
||||
|
||||
public class PathEnvironmentScanner : IJavaScanner
|
||||
{
|
||||
public void Scan(ICollection<string> results)
|
||||
{
|
||||
try
|
||||
{
|
||||
var pathVar = Environment.GetEnvironmentVariable("PATH");
|
||||
if (string.IsNullOrEmpty(pathVar)) return;
|
||||
|
||||
foreach (var dir in pathVar.Split(';', StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
if (!Directory.Exists(dir)) continue;
|
||||
|
||||
var javaExe = Path.Combine(dir, "java.exe");
|
||||
if (File.Exists(javaExe)) results.Add(javaExe);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogWrapper.Error(ex, "Java", "PATH环境变量扫描失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
using Microsoft.Win32;
|
||||
using PCL.Core.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace PCL.Core.Minecraft.Java.Scanner;
|
||||
|
||||
public class RegistryJavaScanner : IJavaScanner
|
||||
{
|
||||
private static readonly string[] _RegistryPaths =
|
||||
[
|
||||
@"SOFTWARE\JavaSoft\Java Development Kit",
|
||||
@"SOFTWARE\JavaSoft\Java Runtime Environment",
|
||||
@"SOFTWARE\WOW6432Node\JavaSoft\Java Development Kit",
|
||||
@"SOFTWARE\WOW6432Node\JavaSoft\Java Runtime Environment"
|
||||
];
|
||||
|
||||
private static readonly string[] _BrandRegistryPaths =
|
||||
[
|
||||
@"SOFTWARE\Azul Systems\Zulu",
|
||||
@"SOFTWARE\BellSoft\Liberica"
|
||||
];
|
||||
|
||||
public void Scan(ICollection<string> results)
|
||||
{
|
||||
try
|
||||
{
|
||||
_ScanJavaSoftRegistry(results);
|
||||
_ScanBrandRegistry(results);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogWrapper.Error(ex, "Java", "注册表扫描失败");
|
||||
}
|
||||
}
|
||||
|
||||
private static void _ScanJavaSoftRegistry(ICollection<string> results)
|
||||
{
|
||||
foreach (var regPath in _RegistryPaths)
|
||||
{
|
||||
using var regKey = Registry.LocalMachine.OpenSubKey(regPath);
|
||||
if (regKey is null) continue;
|
||||
|
||||
foreach (var subKeyName in regKey.GetSubKeyNames())
|
||||
{
|
||||
using var subKey = regKey.OpenSubKey(subKeyName);
|
||||
var javaHome = subKey?.GetValue("JavaHome") as string;
|
||||
if (string.IsNullOrEmpty(javaHome) ||
|
||||
Path.GetInvalidPathChars().Any(c => javaHome.Contains(c))) continue;
|
||||
|
||||
var javaExePath = Path.Combine(javaHome, "bin", "java.exe");
|
||||
if (File.Exists(javaExePath)) results.Add(javaExePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void _ScanBrandRegistry(ICollection<string> results)
|
||||
{
|
||||
foreach (var keyPath in _BrandRegistryPaths)
|
||||
{
|
||||
using var brandKey = Registry.LocalMachine.OpenSubKey(keyPath);
|
||||
if (brandKey is null) continue;
|
||||
|
||||
foreach (var subKeyName in brandKey.GetSubKeyNames())
|
||||
{
|
||||
using var subKey = brandKey.OpenSubKey(subKeyName);
|
||||
var installPath = subKey?.GetValue("InstallationPath") as string;
|
||||
if (string.IsNullOrEmpty(installPath) ||
|
||||
Path.GetInvalidPathChars().Any(c => installPath.Contains(c))) continue;
|
||||
|
||||
var javaExePath = Path.Combine(installPath, "bin", "java.exe");
|
||||
if (File.Exists(javaExePath)) results.Add(javaExePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using PCL.Core.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace PCL.Core.Minecraft.Java.Scanner;
|
||||
|
||||
public class WhereCommandScanner : IJavaScanner
|
||||
{
|
||||
public void Scan(ICollection<string> results)
|
||||
{
|
||||
if (!OperatingSystem.IsWindows()) return;
|
||||
|
||||
try
|
||||
{
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = "where",
|
||||
Arguments = "java",
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
using var proc = Process.Start(psi);
|
||||
if (proc is null) return;
|
||||
|
||||
var output = proc.StandardOutput.ReadToEnd();
|
||||
proc.WaitForExit();
|
||||
|
||||
if (proc.ExitCode != 0) return;
|
||||
|
||||
var paths = output.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(p => p.Trim())
|
||||
.Where(p => File.Exists(p));
|
||||
|
||||
foreach (var path in paths)
|
||||
results.Add(path);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogWrapper.Error(ex, "Java", "where 命令扫描失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user