Build offline OpenPrestige release bundles
Rename the injector and package it with an embedded payload for Windows, Linux, and macOS x64/ARM64. Add a core-only ImGui native build, multi-platform CI and release packaging, and shared per-user storage with legacy config migration.
This commit is contained in:
1 parent
5152e6e0a6
commit
2d6b2a3cd1
37 files changed
+1284
-451
No files matched your search
@@ -0,0 +1,182 @@
|
||||
"""Build the core-only JNI library matching imgui-java 1.92.7.1.
|
||||
|
||||
Requires Python 3, a JDK, CMake, and a C++17 compiler. Select a cross compiler
|
||||
with --toolchain or a Visual Studio target with --architecture ARM64.
|
||||
"""
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import urllib.request
|
||||
import zipfile
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
BINDING_COMMIT = "b164a88602af3e28483efbb4d85aea372010c7c5"
|
||||
IMGUI_COMMIT = "b1bcb12a624af7509894c8e77dd47416997777fa"
|
||||
NATIVE_FILES = (
|
||||
"_common.h", "_internal.h", "imconfig.h",
|
||||
"jni_jvm", "jni_common", "jni_binding_struct", "jni_callbacks", "jni_assertion", "jni_internal",
|
||||
)
|
||||
|
||||
|
||||
def write_if_changed(path, data):
|
||||
if not path.is_file() or path.read_bytes() != data:
|
||||
path.write_bytes(data)
|
||||
|
||||
def download(url, destination):
|
||||
if not destination.is_file():
|
||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||
with tempfile.NamedTemporaryFile(dir=destination.parent, suffix=".download", delete=False) as output:
|
||||
temporary = Path(output.name)
|
||||
try:
|
||||
with urllib.request.urlopen(url, timeout=60) as response:
|
||||
shutil.copyfileobj(response, output)
|
||||
except BaseException:
|
||||
output.close()
|
||||
temporary.unlink(missing_ok=True)
|
||||
raise
|
||||
try:
|
||||
if not destination.is_file():
|
||||
try:
|
||||
temporary.replace(destination)
|
||||
except PermissionError:
|
||||
if not destination.is_file():
|
||||
raise
|
||||
finally:
|
||||
temporary.unlink(missing_ok=True)
|
||||
return destination
|
||||
|
||||
|
||||
def archive(repository, commit, cache):
|
||||
path = download(f"https://codeload.github.com/{repository}/zip/{commit}",
|
||||
cache / "downloads" / f"{repository.split('/')[-1]}-{commit}.zip")
|
||||
return zipfile.ZipFile(path)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--build-dir", type=Path, default=ROOT / "build/imgui-core")
|
||||
parser.add_argument("--output", type=Path, required=True)
|
||||
parser.add_argument("--toolchain", type=Path)
|
||||
parser.add_argument("--architecture", choices=("x64", "ARM64"))
|
||||
parser.add_argument("--cache-dir", type=Path)
|
||||
parser.add_argument("--zig-target", choices=(
|
||||
"x86_64-windows-gnu", "aarch64-windows-gnu",
|
||||
"x86_64-linux-gnu.2.17", "aarch64-linux-gnu.2.17",
|
||||
"x86_64-macos.11.0", "aarch64-macos.11.0",
|
||||
))
|
||||
args = parser.parse_args()
|
||||
# cmd.exe cannot use extended-length (\\?\) working directory paths.
|
||||
build = Path(os.path.abspath(args.build_dir))
|
||||
cache = Path(os.path.abspath(args.cache_dir)) if args.cache_dir else build
|
||||
output = Path(os.path.abspath(args.output))
|
||||
generated = build / "jni"
|
||||
sources = build / "sources"
|
||||
for directory in (build, output, generated, sources):
|
||||
directory.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
dependencies = []
|
||||
for group, artifact, version in (
|
||||
("com.badlogicgames.gdx", "gdx-jnigen", "2.5.2"),
|
||||
("com.badlogicgames.gdx", "gdx-jnigen-loader", "2.5.2"),
|
||||
("com.github.javaparser", "javaparser-core", "3.14.14"),
|
||||
("io.github.spair", "imgui-java-binding", "1.92.7.1"),
|
||||
):
|
||||
name = f"{artifact}-{version}.jar"
|
||||
dependencies.append(download(
|
||||
f"https://repo.maven.apache.org/maven2/{group.replace('.', '/')}/{artifact}/{version}/{name}",
|
||||
cache / "deps" / name,
|
||||
))
|
||||
binding = dependencies[-1]
|
||||
with zipfile.ZipFile(dependencies[0]) as generator:
|
||||
header_prefix = "com/badlogic/gdx/jnigen/resources/headers/"
|
||||
for entry in generator.infolist():
|
||||
if entry.filename.startswith(header_prefix) and not entry.is_dir():
|
||||
destination = generated / "jni-headers" / entry.filename[len(header_prefix):]
|
||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||
write_if_changed(destination, generator.read(entry))
|
||||
with archive("SpaiR/imgui-java", BINDING_COMMIT, cache) as upstream:
|
||||
prefix = upstream.namelist()[0]
|
||||
java_prefix = prefix + "imgui-binding/src/generated/java/"
|
||||
native_prefix = prefix + "imgui-binding/src/main/native/"
|
||||
for entry in upstream.infolist():
|
||||
if entry.filename.startswith(java_prefix) and entry.filename.endswith(".java"):
|
||||
relative = entry.filename[len(java_prefix):]
|
||||
if relative.startswith(("imgui/extension/", "imgui/lwjgl3/")):
|
||||
continue
|
||||
target = sources / relative
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
write_if_changed(target, upstream.read(entry))
|
||||
if entry.filename.startswith(native_prefix):
|
||||
name = entry.filename[len(native_prefix):]
|
||||
if name in NATIVE_FILES or name.rsplit(".", 1)[0] in NATIVE_FILES:
|
||||
write_if_changed(generated / name, upstream.read(entry))
|
||||
write_if_changed(output / "LICENSE-imgui-java.txt", upstream.read(prefix + "LICENSE"))
|
||||
with archive("ocornut/imgui", IMGUI_COMMIT, cache) as upstream:
|
||||
prefix = upstream.namelist()[0]
|
||||
for entry in upstream.infolist():
|
||||
name = entry.filename[len(prefix):]
|
||||
if "/" not in name and name.endswith((".h", ".cpp")) and name != "imconfig.h":
|
||||
write_if_changed(generated / name, upstream.read(entry))
|
||||
write_if_changed(output / "LICENSE-imgui.txt", upstream.read(prefix + "LICENSE.txt"))
|
||||
|
||||
java_home = os.environ.get("JAVA_HOME")
|
||||
java = str(Path(java_home) / "bin/java") if java_home else shutil.which("java")
|
||||
if not java:
|
||||
raise RuntimeError("Set JAVA_HOME to a JDK.")
|
||||
env = os.environ.copy()
|
||||
if java_home:
|
||||
env["PATH"] = str(Path(java_home) / "bin") + os.pathsep + env["PATH"]
|
||||
# jnigen constructs its javac command as a string. Relative paths avoid its
|
||||
# whitespace bug without depending on Windows short filename support.
|
||||
write_if_changed(build / "binding.jar", binding.read_bytes())
|
||||
subprocess.run([
|
||||
java, "-cp", os.pathsep.join(map(str, dependencies)),
|
||||
str(ROOT / "scripts/imgui-core/GenerateCore.java"),
|
||||
"sources", "binding.jar", "jni",
|
||||
], check=True, env=env, cwd=build)
|
||||
# Upstream's GNU builds accept variable-length arrays. Use standard C++
|
||||
# storage in generated JNI code so the identical methods also build in MSVC.
|
||||
declaration = re.compile(r"\b(ImVec2|const char\*|jlong)\s+(\w+)\[(\w+)\];")
|
||||
for source in generated.glob("imgui_*.cpp"):
|
||||
text = source.read_text(encoding="utf-8")
|
||||
text, count = declaration.subn(
|
||||
lambda match: f"std::vector<{match[1]}> {match[2]}Storage({match[3]});\n"
|
||||
f" auto* {match[2]} = {match[2]}Storage.data();",
|
||||
text,
|
||||
)
|
||||
if count:
|
||||
source.write_text("#include <vector>\n" + text, encoding="utf-8")
|
||||
configuration = [
|
||||
"cmake", "-S", str(ROOT / "scripts/imgui-core"), "-B", str(build / "cmake"),
|
||||
f"-DCORE_JNI_DIR={generated.as_posix()}", f"-DCORE_OUTPUT_DIR={output.as_posix()}", "-DCMAKE_BUILD_TYPE=Release",
|
||||
]
|
||||
if args.zig_target:
|
||||
if args.toolchain or args.architecture:
|
||||
parser.error("--zig-target cannot be combined with --toolchain or --architecture")
|
||||
import ziglang
|
||||
executable = Path(ziglang.__file__).parent / ("zig.exe" if os.name == "nt" else "zig")
|
||||
family = "Windows" if "windows" in args.zig_target else "Darwin" if "macos" in args.zig_target else "Linux"
|
||||
toolchain = build / "zig-toolchain.cmake"
|
||||
toolchain.write_text(
|
||||
f'set(CMAKE_SYSTEM_NAME {family})\n'
|
||||
f'set(CMAKE_SYSTEM_PROCESSOR {args.zig_target.split("-")[0]})\n'
|
||||
f'set(CMAKE_CXX_COMPILER "{executable.as_posix()}")\n'
|
||||
f'set(CMAKE_CXX_COMPILER_ARG1 "c++ -target {args.zig_target} -Wno-nullability-completeness")\n',
|
||||
encoding="utf-8",
|
||||
)
|
||||
configuration.extend(("-G", "Ninja", f"-DCMAKE_TOOLCHAIN_FILE={toolchain.as_posix()}"))
|
||||
if args.toolchain:
|
||||
configuration.append(f"-DCMAKE_TOOLCHAIN_FILE={args.toolchain.resolve().as_posix()}")
|
||||
if args.architecture:
|
||||
configuration.extend(("-A", args.architecture))
|
||||
subprocess.run(configuration, check=True, env=env)
|
||||
subprocess.run(["cmake", "--build", str(build / "cmake"), "--config", "Release", "--parallel", "4"],
|
||||
check=True, env=env)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,127 @@
|
||||
"""Fetch and build every core ImGui native required by build-release.bat."""
|
||||
import argparse
|
||||
import importlib.util
|
||||
import importlib.metadata
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import sysconfig
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
TARGETS = (
|
||||
("windows-x86_64", "x86_64-windows-gnu", "imgui-java64.dll"),
|
||||
("windows-aarch64", "aarch64-windows-gnu", "imgui-java64.dll"),
|
||||
("linux-x86_64", "x86_64-linux-gnu.2.17", "libimgui-java64.so"),
|
||||
("linux-aarch64", "aarch64-linux-gnu.2.17", "libimgui-java64.so"),
|
||||
("macos-x86_64", "x86_64-macos.11.0", "libimgui-java64.dylib"),
|
||||
("macos-aarch64", "aarch64-macos.11.0", "libimgui-java64.dylib"),
|
||||
)
|
||||
|
||||
|
||||
def ensure_tools():
|
||||
scripts = Path(sys.executable).parent / "Scripts"
|
||||
user_scheme = "nt_user" if os.name == "nt" else "posix_user"
|
||||
user_scripts = Path(sysconfig.get_path("scripts", scheme=user_scheme))
|
||||
os.environ["PATH"] = os.pathsep.join((str(scripts), str(user_scripts), os.environ["PATH"]))
|
||||
missing = [tool for tool in ("cmake", "ninja") if not shutil.which(tool)]
|
||||
if importlib.util.find_spec("ziglang") is None or importlib.metadata.version("ziglang") != "0.16.0":
|
||||
missing.append("ziglang==0.16.0")
|
||||
if missing:
|
||||
subprocess.run([sys.executable, "-m", "pip", "install", "--user", *missing], check=True)
|
||||
for tool in ("cmake", "ninja"):
|
||||
if not shutil.which(tool):
|
||||
raise RuntimeError(f"{tool} is unavailable after installation. Add the Python Scripts directory to PATH.")
|
||||
|
||||
|
||||
def universal(slices, destination):
|
||||
images = [path.read_bytes() for path in slices]
|
||||
records = []
|
||||
offset = 16384
|
||||
for image in images:
|
||||
magic, cpu, subtype = struct.unpack_from("<III", image)
|
||||
if magic != 0xFEEDFACF or cpu not in (0x01000007, 0x0100000C):
|
||||
raise ValueError("The macOS build produced an unexpected Mach-O architecture.")
|
||||
records.append((cpu, subtype, offset, len(image), 14))
|
||||
offset = (offset + len(image) + 16383) & ~16383
|
||||
if len({record[0] for record in records}) != 2:
|
||||
raise ValueError("The universal library needs one Intel slice and one ARM64 slice.")
|
||||
temporary = destination.with_suffix(".dylib.tmp")
|
||||
with temporary.open("wb") as output:
|
||||
output.write(struct.pack(">II", 0xCAFEBABE, len(images)))
|
||||
for record in records:
|
||||
output.write(struct.pack(">IIIII", *record))
|
||||
for record, image in zip(records, images):
|
||||
output.seek(record[2])
|
||||
output.write(image)
|
||||
temporary.replace(destination)
|
||||
|
||||
|
||||
def validate_native(path, platform):
|
||||
with path.open("rb") as library:
|
||||
header = library.read(64)
|
||||
if platform.startswith("windows-") and header[:2] == b"MZ":
|
||||
library.seek(struct.unpack_from("<I", header, 60)[0])
|
||||
signature, machine = struct.unpack("<4sH", library.read(6))
|
||||
expected = 0xAA64 if platform.endswith("aarch64") else 0x8664
|
||||
if signature == b"PE\0\0" and machine == expected:
|
||||
return
|
||||
elif platform.startswith("linux-") and header[:6] == b"\x7fELF\x02\x01":
|
||||
expected = 183 if platform.endswith("aarch64") else 62
|
||||
if struct.unpack_from("<H", header, 18)[0] == expected:
|
||||
return
|
||||
elif platform.startswith("macos-") and header[:4] == b"\xcf\xfa\xed\xfe":
|
||||
expected = 0x0100000C if platform.endswith("aarch64") else 0x01000007
|
||||
if struct.unpack_from("<I", header, 4)[0] == expected:
|
||||
return
|
||||
raise ValueError(f"The {platform} build produced a library with an unexpected architecture.")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--platform", choices=[item[0] for item in TARGETS],
|
||||
help="Build one slice for troubleshooting. Default builds the full release set.")
|
||||
args = parser.parse_args()
|
||||
ensure_tools()
|
||||
cache = ROOT / "build/imgui-core-cache"
|
||||
for platform, target, filename in TARGETS:
|
||||
if args.platform and args.platform != platform:
|
||||
continue
|
||||
print(f"Building small ImGui JNI for {platform}", flush=True)
|
||||
output = ROOT / "build/core-natives" / platform
|
||||
if platform.startswith("macos-"):
|
||||
output = ROOT / "build/imgui-core-slices" / platform
|
||||
work = ROOT / "build/imgui-core-release" / platform
|
||||
work.mkdir(parents=True, exist_ok=True)
|
||||
log_path = work / "build.log"
|
||||
command = [
|
||||
sys.executable, str(ROOT / "scripts/build_imgui_core.py"),
|
||||
"--build-dir", str(work),
|
||||
"--cache-dir", str(cache), "--output", str(output), "--zig-target", target,
|
||||
]
|
||||
with log_path.open("w", encoding="utf-8") as log:
|
||||
result = subprocess.run(command, stdout=log, stderr=subprocess.STDOUT)
|
||||
if result.returncode:
|
||||
print("\n".join(log_path.read_text(encoding="utf-8", errors="replace").splitlines()[-40:]), flush=True)
|
||||
raise RuntimeError(f"The {platform} build failed. Full log: {log_path}")
|
||||
if not (output / filename).is_file():
|
||||
raise RuntimeError(f"The {platform} build did not produce {filename}.")
|
||||
validate_native(output / filename, platform)
|
||||
print(f"Built {output / filename} ({(output / filename).stat().st_size:,} bytes)", flush=True)
|
||||
if not args.platform:
|
||||
destination = ROOT / "build/core-natives/macos-universal"
|
||||
destination.mkdir(parents=True, exist_ok=True)
|
||||
slices = ROOT / "build/imgui-core-slices"
|
||||
universal([
|
||||
slices / "macos-x86_64/libimgui-java64.dylib",
|
||||
slices / "macos-aarch64/libimgui-java64.dylib",
|
||||
], destination / "libimgui-java64.dylib")
|
||||
for license_file in (slices / "macos-x86_64").glob("LICENSE-*.txt"):
|
||||
shutil.copyfile(license_file, destination / license_file.name)
|
||||
print("All five release ImGui native libraries are ready.", flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,58 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
project(openprestige_imgui LANGUAGES CXX)
|
||||
|
||||
file(GLOB GENERATED CONFIGURE_DEPENDS "${CORE_JNI_DIR}/imgui_*.cpp")
|
||||
add_library(imgui-java64 SHARED
|
||||
${GENERATED}
|
||||
"${CORE_JNI_DIR}/imgui.cpp"
|
||||
"${CORE_JNI_DIR}/imgui_draw.cpp"
|
||||
"${CORE_JNI_DIR}/imgui_tables.cpp"
|
||||
"${CORE_JNI_DIR}/imgui_widgets.cpp"
|
||||
"${CORE_JNI_DIR}/imgui_demo.cpp"
|
||||
"${CORE_JNI_DIR}/jni_jvm.cpp"
|
||||
"${CORE_JNI_DIR}/jni_common.cpp"
|
||||
"${CORE_JNI_DIR}/jni_binding_struct.cpp"
|
||||
"${CORE_JNI_DIR}/jni_callbacks.cpp"
|
||||
"${CORE_JNI_DIR}/jni_assertion.cpp"
|
||||
"${CORE_JNI_DIR}/jni_internal.cpp"
|
||||
)
|
||||
target_compile_features(imgui-java64 PRIVATE cxx_std_17)
|
||||
if(WIN32)
|
||||
set(JNI_OS win32)
|
||||
elseif(APPLE)
|
||||
set(JNI_OS mac)
|
||||
else()
|
||||
set(JNI_OS linux)
|
||||
endif()
|
||||
target_include_directories(imgui-java64 PRIVATE
|
||||
"${CORE_JNI_DIR}"
|
||||
"${CORE_JNI_DIR}/jni-headers"
|
||||
"${CORE_JNI_DIR}/jni-headers/${JNI_OS}"
|
||||
)
|
||||
target_compile_definitions(imgui-java64 PRIVATE IMGUI_DISABLE_DEMO_WINDOWS)
|
||||
set_target_properties(imgui-java64 PROPERTIES
|
||||
CXX_VISIBILITY_PRESET hidden
|
||||
VISIBILITY_INLINES_HIDDEN YES
|
||||
MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>"
|
||||
LIBRARY_OUTPUT_DIRECTORY "${CORE_OUTPUT_DIR}"
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CORE_OUTPUT_DIR}"
|
||||
RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CORE_OUTPUT_DIR}"
|
||||
)
|
||||
if(MSVC)
|
||||
target_compile_options(imgui-java64 PRIVATE /O1 /Gy /GL /utf-8)
|
||||
target_link_options(imgui-java64 PRIVATE /LTCG /OPT:REF /OPT:ICF)
|
||||
else()
|
||||
target_compile_options(imgui-java64 PRIVATE -Os -ffunction-sections -fdata-sections)
|
||||
if(APPLE)
|
||||
target_link_options(imgui-java64 PRIVATE -Wl,-dead_strip)
|
||||
else()
|
||||
target_link_options(imgui-java64 PRIVATE -Wl,--gc-sections -s)
|
||||
endif()
|
||||
if(MINGW)
|
||||
target_link_options(imgui-java64 PRIVATE -static-libgcc -static-libstdc++)
|
||||
endif()
|
||||
endif()
|
||||
if(WIN32)
|
||||
set_target_properties(imgui-java64 PROPERTIES PREFIX "")
|
||||
target_link_libraries(imgui-java64 PRIVATE imm32 user32)
|
||||
endif()
|
||||
@@ -0,0 +1,8 @@
|
||||
import com.badlogic.gdx.jnigen.NativeCodeGenerator;
|
||||
|
||||
public final class GenerateCore {
|
||||
public static void main(String[] args) throws Exception {
|
||||
new NativeCodeGenerator().generate(args[0], args[1], args[2], null,
|
||||
new String[] {"**/extension/**", "**/lwjgl3/**"});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
# Core ImGui JNI build
|
||||
|
||||
OpenPrestige uses core ImGui with its Java OpenGL backend. This build keeps all core and internal
|
||||
JNI methods from imgui-java 1.92.7.1, and removes the native extension implementations for ImPlot,
|
||||
ImNodes, ImGuizmo, imgui-node-editor, text editor, file dialog, knobs, and memory editor. Demo
|
||||
windows are disabled. Core callbacks, font rendering, tables, docking, and draw lists remain available.
|
||||
|
||||
The source revisions and generator dependencies are pinned in `../build_imgui_core.py`.
|
||||
The build consumes upstream generated Java without modifying it. A portability step replaces
|
||||
GNU variable-length arrays in the generated C++ with standard C++ vectors for MSVC.
|
||||
|
||||
## Windows x64
|
||||
|
||||
Set `JAVA_HOME` to JDK 21 and install Visual Studio C++ tools and CMake. From the repository root run
|
||||
|
||||
```powershell
|
||||
python scripts/build_imgui_core.py --output build/core-natives/windows-x86_64 --architecture x64
|
||||
```
|
||||
|
||||
The output contains `imgui-java64.dll` and the upstream licenses. Gradle picks up
|
||||
`build/core-natives`, embeds it under `prestige-natives`, and omits the corresponding upstream
|
||||
native. The Java extension wrappers are omitted from the shaded payload.
|
||||
|
||||
Use a distinct `--build-dir` for each target. ARM64 cross builds use the toolchain files in
|
||||
`toolchains`, which require LLVM-MinGW on Windows targets and an aarch64 Linux cross compiler on
|
||||
Linux targets. The macOS toolchain produces a universal library. CI builds all five variants.
|
||||
|
||||
## Full release build on Windows
|
||||
|
||||
`build-release.bat` calls `python scripts/build_release_imgui.py` before Gradle builds the JAR.
|
||||
The helper fetches the pinned sources and generator dependencies, installs CMake, Ninja and Zig
|
||||
when needed, and builds Windows and Linux x64/ARM64 plus both macOS slices. It combines the macOS
|
||||
slices into a universal library. Downloads are cached in `build/imgui-core-cache`, and compilation
|
||||
is incremental under `build/imgui-core-release`.
|
||||
|
||||
The complete release still requires JDK 21, Rust, and the MSVC x64/ARM64 build tools used by the
|
||||
Rust Windows targets. Python's package installer needs network access on the first run.
|
||||
|
||||
To diagnose one cross build, use `python scripts/build_release_imgui.py --platform windows-aarch64`.
|
||||
Running the helper without arguments builds the entire ImGui release set.
|
||||
|
||||
## JNI verification
|
||||
|
||||
```powershell
|
||||
java -Dimgui.library.path=build/core-natives/windows-x86_64 -cp build/imgui-core/deps/imgui-java-binding-1.92.7.1.jar scripts/imgui-core/SmokeTest.java src/main/resources/prestige_ui/fonts/DMSans-Regular.ttf
|
||||
```
|
||||
|
||||
This initializes JNI, builds the real UI font atlas, and generates geometry from widgets and draw
|
||||
lists. A Minecraft smoke test should additionally exercise the ClickGUI and OpenGL backend.
|
||||
@@ -0,0 +1,59 @@
|
||||
import imgui.ImDrawList;
|
||||
import imgui.ImFontConfig;
|
||||
import imgui.ImGui;
|
||||
import imgui.ImGuiIO;
|
||||
import imgui.type.ImInt;
|
||||
import imgui.type.ImString;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
/** Exercises core JNI entry points, real UI fonts, input widgets and draw data without a game. */
|
||||
public final class SmokeTest {
|
||||
public static void main(String[] args) throws Exception {
|
||||
ImGui.createContext();
|
||||
try {
|
||||
ImGuiIO io = ImGui.getIO();
|
||||
io.setIniFilename(null);
|
||||
io.setDisplaySize(1000, 600);
|
||||
io.setDeltaTime(1.0f / 60);
|
||||
ImFontConfig config = new ImFontConfig();
|
||||
try {
|
||||
config.setPixelSnapH(true);
|
||||
io.getFonts().addFontFromMemoryTTF(Files.readAllBytes(Path.of(args[0])), 18, config);
|
||||
} finally {
|
||||
config.destroy();
|
||||
}
|
||||
if (!io.getFonts().build()) throw new AssertionError("Font atlas did not build");
|
||||
ImInt width = new ImInt();
|
||||
ImInt height = new ImInt();
|
||||
ByteBuffer pixels = io.getFonts().getTexDataAsRGBA32(width, height);
|
||||
if (pixels == null || width.get() <= 0 || height.get() <= 0) {
|
||||
throw new AssertionError("Font atlas is empty");
|
||||
}
|
||||
ImString input = new ImString("OpenPrestige", 64);
|
||||
float[] value = {0.5f};
|
||||
int vertices = 0;
|
||||
for (int frame = 0; frame < 3; frame++) {
|
||||
ImGui.newFrame();
|
||||
ImGui.setNextWindowSize(500, 350);
|
||||
ImGui.begin("Core JNI smoke test");
|
||||
ImGui.text("OpenPrestige core-only ImGui");
|
||||
ImGui.button("Inject");
|
||||
ImGui.sliderFloat("Scale", value, 0, 1);
|
||||
ImGui.inputText("Config name", input);
|
||||
ImDrawList draw = ImGui.getWindowDrawList();
|
||||
draw.addCircleFilled(100, 100, 20, 0xff80a0ff);
|
||||
draw.addText(120, 100, 0xffffffff, "Draw list");
|
||||
ImGui.end();
|
||||
ImGui.render();
|
||||
vertices = ImGui.getDrawData().getTotalVtxCount();
|
||||
}
|
||||
if (vertices <= 0) throw new AssertionError("No UI geometry was generated");
|
||||
System.out.println("Core JNI passed. Font atlas " + width.get() + "x" + height.get()
|
||||
+ ", vertices " + vertices);
|
||||
} finally {
|
||||
ImGui.destroyContext();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
set(CMAKE_SYSTEM_NAME Linux)
|
||||
set(CMAKE_SYSTEM_PROCESSOR aarch64)
|
||||
set(CMAKE_CXX_COMPILER aarch64-linux-gnu-g++)
|
||||
@@ -0,0 +1,2 @@
|
||||
set(CMAKE_OSX_ARCHITECTURES "x86_64;arm64" CACHE STRING "" FORCE)
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET "11.0" CACHE STRING "" FORCE)
|
||||
@@ -0,0 +1,4 @@
|
||||
set(CMAKE_SYSTEM_NAME Windows)
|
||||
set(CMAKE_SYSTEM_PROCESSOR aarch64)
|
||||
set(CMAKE_CXX_COMPILER aarch64-w64-mingw32-clang++)
|
||||
set(CMAKE_RC_COMPILER aarch64-w64-mingw32-windres)
|
||||
@@ -0,0 +1,4 @@
|
||||
set(CMAKE_SYSTEM_NAME Windows)
|
||||
set(CMAKE_SYSTEM_PROCESSOR x86_64)
|
||||
set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-clang++)
|
||||
set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres)
|
||||
+28
-80
@@ -1,9 +1,7 @@
|
||||
"""Packs one platform's release zip, or writes SHA256SUMS.txt for dist/release. Called by release.bat.
|
||||
"""Package openprestige releases.
|
||||
|
||||
package_release.py zip <out_dir> <version> <platform> <injector> <native_lib> <payload_jar>
|
||||
package_release.py offline <out_dir> <version> <platform> <injector> <native_lib> <payload_jar>
|
||||
package_release.py checksums <out_dir>
|
||||
|
||||
Zips are written with unix permissions so PrestigeInjector stays executable after unzip on Linux/macOS.
|
||||
"""
|
||||
import hashlib
|
||||
import os
|
||||
@@ -11,69 +9,7 @@ import sys
|
||||
import time
|
||||
import zipfile
|
||||
|
||||
README = {
|
||||
"windows": """Prestige {version} - Windows (x86_64)
|
||||
|
||||
Contents
|
||||
PrestigeInjector.exe injector
|
||||
prestige_native.dll loader library (keep it next to the injector)
|
||||
{jar} the same client as a Fabric mod (optional, see below)
|
||||
|
||||
Inject
|
||||
1. Start Minecraft 1.21.11 with Fabric Loader 0.19+ and Fabric API.
|
||||
2. Run PrestigeInjector.exe and pick the game from the list.
|
||||
|
||||
If SmartScreen blocks the exe: More info -> Run anyway.
|
||||
""",
|
||||
"linux": """Prestige {version} - Linux (x86_64)
|
||||
|
||||
Works on any glibc distro from 2012 on (glibc 2.17+): Ubuntu, Debian, Fedora, Arch, Mint, openSUSE, ...
|
||||
Not for musl distros (Alpine) or ARM machines.
|
||||
|
||||
Contents
|
||||
PrestigeInjector injector
|
||||
libprestige_native.so loader library (keep it next to the injector)
|
||||
{jar} the same client as a Fabric mod (optional, see below)
|
||||
|
||||
Inject
|
||||
1. Start Minecraft 1.21.11 with Fabric Loader 0.19+ and Fabric API.
|
||||
2. In a terminal in this folder: ./PrestigeInjector
|
||||
Run it as the same user as the game. The game must not use -XX:+DisableAttachMechanism.
|
||||
""",
|
||||
"macos": """Prestige {version} - macOS (universal: Intel + Apple Silicon)
|
||||
|
||||
Contents
|
||||
PrestigeInjector injector
|
||||
libprestige_native.dylib loader library (keep it next to the injector)
|
||||
{jar} the same client as a Fabric mod (optional, see below)
|
||||
|
||||
Inject
|
||||
1. Start Minecraft 1.21.11 with Fabric Loader 0.19+ and Fabric API.
|
||||
2. In Terminal, in this folder, clear the download quarantine once:
|
||||
xattr -dr com.apple.quarantine .
|
||||
3. Run: ./PrestigeInjector
|
||||
Run it as the same user as the game. The game must not use -XX:+DisableAttachMechanism.
|
||||
""",
|
||||
}
|
||||
|
||||
MOD_NOTE = """
|
||||
Install as a mod instead
|
||||
Put {jar} into the mods/ folder of a Fabric 1.21.11 instance; no injector needed.
|
||||
Do not use both at once.
|
||||
"""
|
||||
|
||||
TESTED = "Tested on Prism Launcher with Minecraft 1.21.11 + Fabric.\n"
|
||||
|
||||
FOOTER = """
|
||||
Credits
|
||||
Cracked by Skid Solutions
|
||||
technix sourcing / Frida scripts
|
||||
stutheguyy fixing
|
||||
bynbang / nitrogenoliquido ZKM transformers
|
||||
dario AI models
|
||||
|
||||
Discord: https://discord.gg/x2FJyGrDNg
|
||||
"""
|
||||
FOOTER = '\nCredits\n Cracked by Skid Solutions\n technix sourcing / Frida scripts\n stutheguyy fixing\n bynbang / nitrogenoliquido ZKM transformers\n dario AI models\n\nDiscord: https://discord.gg/x2FJyGrDNg\n'
|
||||
|
||||
|
||||
def add(zf, arcname, data, mode):
|
||||
@@ -85,19 +21,31 @@ def add(zf, arcname, data, mode):
|
||||
|
||||
|
||||
def make_zip(out_dir, version, platform, injector, lib, jar):
|
||||
family = platform.split("-")[0]
|
||||
name = f"prestige-{version}-{platform}"
|
||||
jar_name = os.path.basename(jar)
|
||||
title, body = README[family].format(version=version, jar=jar_name).split("\n", 1)
|
||||
readme = title + "\n" + TESTED + body + MOD_NOTE.format(jar=jar_name) + FOOTER
|
||||
if family == "windows":
|
||||
readme = readme.replace("\n", "\r\n")
|
||||
if platform in ("windows-aarch64", "linux-aarch64"):
|
||||
native = "imgui-java64.dll" if platform.startswith("windows") else "libimgui-java64.so"
|
||||
with zipfile.ZipFile(jar) as payload:
|
||||
entry = f"prestige-natives/{platform}/{native}"
|
||||
if entry not in payload.namelist():
|
||||
raise ValueError(f"ARM64 payload is missing {entry}. Build the core ImGui natives first.")
|
||||
name = f"openprestige-{version}-{platform}"
|
||||
out = os.path.join(out_dir, name + ".zip")
|
||||
with zipfile.ZipFile(out, "w", zipfile.ZIP_DEFLATED, compresslevel=9) as zf:
|
||||
for path, mode in ((injector, 0o755), (lib, 0o644), (jar, 0o644)):
|
||||
with open(path, "rb") as f:
|
||||
add(zf, f"{name}/{os.path.basename(path)}", f.read(), mode)
|
||||
add(zf, f"{name}/README.txt", readme.encode(), 0o644)
|
||||
os.makedirs(out_dir, exist_ok=True)
|
||||
with zipfile.ZipFile(out, "w", zipfile.ZIP_DEFLATED, compresslevel=9) as bundle:
|
||||
executable = "OpenPrestigeInjector" + (".exe" if platform.startswith("windows-") else "")
|
||||
for path, filename, permissions in ((injector, executable, 0o755), (lib, os.path.basename(lib), 0o644)):
|
||||
with open(path, "rb") as file:
|
||||
add(bundle, f"{name}/{filename}", file.read(), permissions)
|
||||
readme = (f"OpenPrestige {version} - {platform}\n\n"
|
||||
f"Extract this complete folder and run {executable}.\n"
|
||||
"Original CLI: Up/Down selects a Minecraft JVM, Enter injects, Esc quits.\n"
|
||||
f"Keep {os.path.basename(lib)} beside the executable.\n"
|
||||
"The native library embeds its payload. No external JAR or runtime download is required.\n"
|
||||
"The launcher has no downloader or update checks.\n\n"
|
||||
"Requires a matching 64-bit Minecraft 1.21.11 Fabric JVM.\n"
|
||||
"Linux/macOS: run from a terminal as the same user as the game; attach must be enabled.\n"
|
||||
"The Fabric mod JAR is available separately. Use either injection or the mod.\n"
|
||||
+ FOOTER)
|
||||
add(bundle, f"{name}/README.txt", readme.encode(), 0o644)
|
||||
print(f" wrote {out} ({os.path.getsize(out) // 1024} KiB)")
|
||||
|
||||
|
||||
@@ -114,7 +62,7 @@ def checksums(out_dir):
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) == 8 and sys.argv[1] == "zip":
|
||||
if len(sys.argv) == 8 and sys.argv[1] == "offline":
|
||||
make_zip(*sys.argv[2:])
|
||||
elif len(sys.argv) == 3 and sys.argv[1] == "checksums":
|
||||
checksums(sys.argv[2])
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
"""Check this repository's release contents and architecture requirements."""
|
||||
import contextlib
|
||||
import hashlib
|
||||
import io
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import unittest
|
||||
import zipfile
|
||||
|
||||
import package_release
|
||||
|
||||
|
||||
class ReleasePackaging(unittest.TestCase):
|
||||
def test_contents_and_checksums(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
injector = root / "injector.exe"
|
||||
library = root / "prestige_native.dll"
|
||||
jar = root / "prestige-1.2.3.jar"
|
||||
injector.write_bytes(b"launcher")
|
||||
library.write_bytes(b"library")
|
||||
with zipfile.ZipFile(jar, "w") as payload:
|
||||
payload.writestr("example.class", b"payload")
|
||||
out = root / "release"
|
||||
with contextlib.redirect_stdout(io.StringIO()):
|
||||
package_release.make_zip(str(out), "1.2.3", "windows-x86_64", str(injector), str(library), str(jar))
|
||||
archive = next(out.glob("*.zip"))
|
||||
with zipfile.ZipFile(archive) as bundle:
|
||||
names = {Path(name).name for name in bundle.namelist()}
|
||||
self.assertEqual(names, {"OpenPrestigeInjector.exe", library.name, "README.txt"})
|
||||
readme = bundle.read("openprestige-1.2.3-windows-x86_64/README.txt").decode()
|
||||
self.assertIn("embeds its payload", readme)
|
||||
self.assertNotIn("Keep the payload JAR beside", readme)
|
||||
package_release.checksums(str(out))
|
||||
expected = f"{hashlib.sha256(archive.read_bytes()).hexdigest()} {archive.name}\n"
|
||||
self.assertEqual((out / "SHA256SUMS.txt").read_text(), expected)
|
||||
|
||||
def test_arm_native_required(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
jar = Path(directory) / "prestige-1.2.3.jar"
|
||||
with zipfile.ZipFile(jar, "w"):
|
||||
pass
|
||||
with self.assertRaisesRegex(ValueError, "ARM64 payload is missing"):
|
||||
package_release.make_zip(directory, "1.2.3", "windows-aarch64", "unused.exe", "unused.dll", str(jar))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user