mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-06-09 10:06:34 +02:00
Stuff to export standalone builds of Robust.Client.
This commit is contained in:
@@ -95,3 +95,6 @@ __pycache__
|
|||||||
MSBuild/Robust.Custom.targets
|
MSBuild/Robust.Custom.targets
|
||||||
|
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
|
|
||||||
|
release/
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>SS14</string>
|
||||||
|
<key>CFBundleDisplayName</key>
|
||||||
|
<string>Space Station 14</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>SS14</string>
|
||||||
|
<!--
|
||||||
|
Just a note about this icon.
|
||||||
|
MacOS seems REALLY iffy about this and even when the file is correct,
|
||||||
|
it can take forever before it decides to actually update it and display it.
|
||||||
|
TL;DR Apple is stupid.
|
||||||
|
-->
|
||||||
|
<key>CFBundleIconFile</key>
|
||||||
|
<string>ss14</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# cd to file containing script or something?
|
||||||
|
BASEDIR=$(dirname "$0")
|
||||||
|
echo "$BASEDIR"
|
||||||
|
cd "$BASEDIR"
|
||||||
|
|
||||||
|
exec ../Resources/Robust.Client "$@"
|
||||||
Binary file not shown.
@@ -4,7 +4,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||||
<OutputPath>../bin/Client</OutputPath>
|
<OutputPath>../bin/Client</OutputPath>
|
||||||
<NoWarn>NU1701</NoWarn>
|
<NoWarn>NU1701</NoWarn>
|
||||||
|
|||||||
Executable
+71
@@ -0,0 +1,71 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# exe_set_subsystem
|
||||||
|
|
||||||
|
# Copyright (c) 2020 20kdc
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in all
|
||||||
|
# copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
# SOFTWARE.
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import struct
|
||||||
|
|
||||||
|
if len(sys.argv) != 3:
|
||||||
|
print("exe_set_subsystem.py <EXE> <SUBSYSTEM>")
|
||||||
|
print(" alters EXE in-place to change it's subsystem to SUBSYSTEM")
|
||||||
|
print("")
|
||||||
|
print("SUBSYSTEM values:")
|
||||||
|
print(" 2: GUI")
|
||||||
|
print(" 3: Console")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
file = open(sys.argv[1], "r+b")
|
||||||
|
if file.read(2) != b"MZ":
|
||||||
|
print("Header must be 'MZ'.")
|
||||||
|
sys.exit(2)
|
||||||
|
file.seek(0x3C)
|
||||||
|
|
||||||
|
peSignatureOfs = struct.unpack("<I", file.read(4))[0]
|
||||||
|
print("PE Signature Ofs: " + str(peSignatureOfs))
|
||||||
|
file.seek(peSignatureOfs)
|
||||||
|
if file.read(4) != b"PE\x00\x00":
|
||||||
|
print("PE Signature must be 'PE'.")
|
||||||
|
sys.exit(3)
|
||||||
|
peHeader = struct.unpack("<HHIIIHH", file.read(20))
|
||||||
|
optHeaderOfs = peSignatureOfs + 4 + 20
|
||||||
|
print("Opt. Header Ofs: " + str(optHeaderOfs))
|
||||||
|
|
||||||
|
subsystemOptOfs = 0
|
||||||
|
# This is the hard bit.
|
||||||
|
# Or not.
|
||||||
|
# Expected these to be in different places.
|
||||||
|
if peHeader[0] == 332:
|
||||||
|
subsystemOptOfs = 68
|
||||||
|
elif peHeader[0] == 0x8664:
|
||||||
|
subsystemOptOfs = 68
|
||||||
|
else:
|
||||||
|
print("Unable to handle machine: " + str(peHeader[0]))
|
||||||
|
sys.exit(4)
|
||||||
|
subsystemOfs = optHeaderOfs + subsystemOptOfs
|
||||||
|
file.seek(subsystemOfs)
|
||||||
|
print("Current Subsystem: " + str(struct.unpack("<H", file.read(2))[0]))
|
||||||
|
file.seek(subsystemOfs)
|
||||||
|
file.write(struct.pack("<H", int(sys.argv[2])))
|
||||||
|
file.close()
|
||||||
|
print("Done!")
|
||||||
|
|
||||||
Executable
+269
@@ -0,0 +1,269 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Packages a full release build of the client that can be unzipped and you'll have your client engine redistributable.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import zipfile
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
try:
|
||||||
|
from colorama import init, Fore, Style
|
||||||
|
init()
|
||||||
|
|
||||||
|
except ImportError:
|
||||||
|
# Just give an empty string for everything, no colored logging.
|
||||||
|
class ColorDummy(object):
|
||||||
|
def __getattr__(self, name):
|
||||||
|
return ""
|
||||||
|
|
||||||
|
Fore = ColorDummy()
|
||||||
|
Style = ColorDummy()
|
||||||
|
|
||||||
|
|
||||||
|
p = os.path.join
|
||||||
|
|
||||||
|
PLATFORM_WINDOWS = "windows"
|
||||||
|
PLATFORM_LINUX = "linux"
|
||||||
|
PLATFORM_LINUX_ARM64 = "linux-arm64"
|
||||||
|
PLATFORM_MACOS = "mac"
|
||||||
|
|
||||||
|
IGNORED_RESOURCES = {
|
||||||
|
".gitignore",
|
||||||
|
".directory",
|
||||||
|
".DS_Store"
|
||||||
|
}
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Packages the Robust client repo for release on all platforms.")
|
||||||
|
parser.add_argument("--platform",
|
||||||
|
"-p",
|
||||||
|
action="store",
|
||||||
|
choices=[PLATFORM_WINDOWS, PLATFORM_MACOS, PLATFORM_LINUX],
|
||||||
|
nargs="*",
|
||||||
|
help="Which platform to build for. If not provided, all platforms will be built")
|
||||||
|
|
||||||
|
parser.add_argument("--skip-build",
|
||||||
|
action="store_true",
|
||||||
|
help=argparse.SUPPRESS)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
platforms = args.platform
|
||||||
|
skip_build = args.skip_build
|
||||||
|
|
||||||
|
if not platforms:
|
||||||
|
platforms = [PLATFORM_WINDOWS, PLATFORM_MACOS, PLATFORM_LINUX]
|
||||||
|
|
||||||
|
if os.path.exists("release"):
|
||||||
|
print(Fore.BLUE + Style.DIM +
|
||||||
|
"Cleaning old release packages (release/)..." + Style.RESET_ALL)
|
||||||
|
shutil.rmtree("release")
|
||||||
|
|
||||||
|
os.mkdir("release")
|
||||||
|
|
||||||
|
if PLATFORM_WINDOWS in platforms:
|
||||||
|
if not skip_build:
|
||||||
|
wipe_bin()
|
||||||
|
build_windows(skip_build)
|
||||||
|
|
||||||
|
if PLATFORM_LINUX in platforms:
|
||||||
|
if not skip_build:
|
||||||
|
wipe_bin()
|
||||||
|
build_linux(skip_build)
|
||||||
|
|
||||||
|
if PLATFORM_LINUX_ARM64 in platforms:
|
||||||
|
if not skip_build:
|
||||||
|
wipe_bin()
|
||||||
|
build_linux_arm64(skip_build)
|
||||||
|
|
||||||
|
if PLATFORM_MACOS in platforms:
|
||||||
|
if not skip_build:
|
||||||
|
wipe_bin()
|
||||||
|
build_macos(skip_build)
|
||||||
|
|
||||||
|
|
||||||
|
def wipe_bin():
|
||||||
|
print(Fore.BLUE + Style.DIM +
|
||||||
|
"Clearing old build artifacts (if any)..." + Style.RESET_ALL)
|
||||||
|
|
||||||
|
if os.path.exists("bin"):
|
||||||
|
shutil.rmtree("bin")
|
||||||
|
|
||||||
|
|
||||||
|
def build_windows(skip_build: bool) -> None:
|
||||||
|
# Run a full build.
|
||||||
|
print(Fore.GREEN + "Building project for Windows x64..." + Style.RESET_ALL)
|
||||||
|
|
||||||
|
if not skip_build:
|
||||||
|
publish_client("win-x64", "Windows")
|
||||||
|
subprocess.run(["Tools/download_natives.py", "x64", "Windows", "_", p("bin", "Client", "win-x64")])
|
||||||
|
if sys.platform != "win32":
|
||||||
|
subprocess.run(["Tools/exe_set_subsystem.py", p("bin", "Client", "win-x64", "publish", "Robust.Client"), "2"])
|
||||||
|
|
||||||
|
|
||||||
|
print(Fore.GREEN + "Packaging Windows x64 client..." + Style.RESET_ALL)
|
||||||
|
|
||||||
|
client_zip = zipfile.ZipFile(
|
||||||
|
p("release", "Robust.Client_win-x64.zip"), "w",
|
||||||
|
compression=zipfile.ZIP_DEFLATED)
|
||||||
|
|
||||||
|
copy_dir_into_zip(p("bin", "Client", "win-x64", "publish"), "", client_zip)
|
||||||
|
copy_resources("Resources", client_zip)
|
||||||
|
# Cool we're done.
|
||||||
|
client_zip.close()
|
||||||
|
|
||||||
|
def build_macos(skip_build: bool) -> None:
|
||||||
|
print(Fore.GREEN + "Building project for macOS x64..." + Style.RESET_ALL)
|
||||||
|
|
||||||
|
if not skip_build:
|
||||||
|
publish_client("osx-x64", "MacOS")
|
||||||
|
subprocess.run(["Tools/download_natives.py", "x64", "MacOS", "_", p("bin", "Client", "osx-x64")])
|
||||||
|
|
||||||
|
print(Fore.GREEN + "Packaging macOS x64 client..." + Style.RESET_ALL)
|
||||||
|
# Client has to go in an app bundle.
|
||||||
|
client_zip = zipfile.ZipFile(p("release", "Robust.Client_osx-x64.zip"), "a",
|
||||||
|
compression=zipfile.ZIP_DEFLATED)
|
||||||
|
|
||||||
|
contents = p("Space Station 14.app", "Contents", "Resources")
|
||||||
|
copy_dir_into_zip(p("BuildFiles", "Mac", "Space Station 14.app"), "Space Station 14.app", client_zip)
|
||||||
|
copy_dir_into_zip(p("bin", "Client", "osx-x64", "publish"), contents, client_zip)
|
||||||
|
copy_resources(p(contents, "Resources"), client_zip)
|
||||||
|
client_zip.close()
|
||||||
|
|
||||||
|
|
||||||
|
def build_linux(skip_build: bool) -> None:
|
||||||
|
# Run a full build.
|
||||||
|
print(Fore.GREEN + "Building project for Linux x64..." + Style.RESET_ALL)
|
||||||
|
|
||||||
|
if not skip_build:
|
||||||
|
publish_client("linux-x64", "Linux")
|
||||||
|
subprocess.run(["Tools/download_natives.py", "x64", "Linux", "_", p("bin", "Client", "linux-x64")])
|
||||||
|
|
||||||
|
print(Fore.GREEN + "Packaging Linux x64 client..." + Style.RESET_ALL)
|
||||||
|
|
||||||
|
client_zip = zipfile.ZipFile(
|
||||||
|
p("release", "Robust.Client_linux-x64.zip"), "w",
|
||||||
|
compression=zipfile.ZIP_DEFLATED)
|
||||||
|
|
||||||
|
copy_dir_into_zip(p("bin", "Client", "linux-x64", "publish"), "", client_zip)
|
||||||
|
copy_resources("Resources", client_zip)
|
||||||
|
# Cool we're done.
|
||||||
|
client_zip.close()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def build_linux_arm64(skip_build: bool) -> None:
|
||||||
|
# Run a full build.
|
||||||
|
# TODO: Linux-arm64 is currently server-only.
|
||||||
|
pass
|
||||||
|
""" print(Fore.GREEN + "Building project for Linux ARM64 (SERVER ONLY)..." + Style.RESET_ALL)
|
||||||
|
|
||||||
|
if not skip_build:
|
||||||
|
subprocess.run([
|
||||||
|
"dotnet",
|
||||||
|
"build",
|
||||||
|
"SpaceStation14.sln",
|
||||||
|
"-c", "Release",
|
||||||
|
"--nologo",
|
||||||
|
"/v:m",
|
||||||
|
"/p:TargetOS=Linux",
|
||||||
|
"/t:Rebuild",
|
||||||
|
"/p:FullRelease=True"
|
||||||
|
], check=True)
|
||||||
|
|
||||||
|
publish_client("linux-arm64", "Linux", True)
|
||||||
|
|
||||||
|
print(Fore.GREEN + "Packaging Linux ARM64 server..." + Style.RESET_ALL)
|
||||||
|
server_zip = zipfile.ZipFile(p("release", "SS14.Server_Linux_ARM64.zip"), "w",
|
||||||
|
compression=zipfile.ZIP_DEFLATED)
|
||||||
|
copy_dir_into_zip(p("RobustToolbox", "bin", "Server", "linux-arm64", "publish"), "", server_zip)
|
||||||
|
copy_resources(p("Resources"), server_zip, server=True)
|
||||||
|
server_zip.close()"""
|
||||||
|
|
||||||
|
|
||||||
|
def publish_client(runtime: str, target_os: str) -> None:
|
||||||
|
base = [
|
||||||
|
"dotnet", "publish",
|
||||||
|
"--runtime", runtime,
|
||||||
|
"--no-self-contained",
|
||||||
|
"-c", "Release",
|
||||||
|
f"/p:TargetOS={target_os}",
|
||||||
|
"/p:FullRelease=True"
|
||||||
|
]
|
||||||
|
|
||||||
|
subprocess.run(base + ["Robust.Client/Robust.Client.csproj"], check=True)
|
||||||
|
|
||||||
|
|
||||||
|
def copy_resources(target, zipf):
|
||||||
|
do_resource_copy(target, "Resources", zipf, IGNORED_RESOURCES)
|
||||||
|
|
||||||
|
|
||||||
|
def do_resource_copy(target, source, zipf, ignore_set):
|
||||||
|
for filename in os.listdir(source):
|
||||||
|
if filename in ignore_set:
|
||||||
|
continue
|
||||||
|
|
||||||
|
path = p(source, filename)
|
||||||
|
target_path = p(target, filename)
|
||||||
|
if os.path.isdir(path):
|
||||||
|
copy_dir_into_zip(path, target_path, zipf)
|
||||||
|
|
||||||
|
else:
|
||||||
|
zipf.write(path, target_path)
|
||||||
|
|
||||||
|
|
||||||
|
def zip_entry_exists(zipf, name):
|
||||||
|
try:
|
||||||
|
# Trick ZipInfo into sanitizing the name for us so this awful module stops spewing warnings.
|
||||||
|
zinfo = zipfile.ZipInfo.from_file("Resources", name)
|
||||||
|
zipf.getinfo(zinfo.filename)
|
||||||
|
except KeyError:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def copy_dir_into_zip(directory, basepath, zipf):
|
||||||
|
if basepath and not zip_entry_exists(zipf, basepath):
|
||||||
|
zipf.write(directory, basepath)
|
||||||
|
|
||||||
|
for root, _, files in os.walk(directory):
|
||||||
|
relpath = os.path.relpath(root, directory)
|
||||||
|
if relpath != "." and not zip_entry_exists(zipf, p(basepath, relpath)):
|
||||||
|
zipf.write(root, p(basepath, relpath))
|
||||||
|
|
||||||
|
for filename in files:
|
||||||
|
zippath = p(basepath, relpath, filename)
|
||||||
|
filepath = p(root, filename)
|
||||||
|
|
||||||
|
message = "{dim}{diskroot}{sep}{zipfile}{dim} -> {ziproot}{sep}{zipfile}".format(
|
||||||
|
sep=os.sep + Style.NORMAL,
|
||||||
|
dim=Style.DIM,
|
||||||
|
diskroot=directory,
|
||||||
|
ziproot=zipf.filename,
|
||||||
|
zipfile=os.path.normpath(zippath))
|
||||||
|
|
||||||
|
print(Fore.CYAN + message + Style.RESET_ALL)
|
||||||
|
zipf.write(filepath, zippath)
|
||||||
|
|
||||||
|
|
||||||
|
def copy_dir_or_file(src: str, dst: str):
|
||||||
|
"""
|
||||||
|
Just something from src to dst. If src is a dir it gets copied recursively.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if os.path.isfile(src):
|
||||||
|
shutil.copy2(src, dst)
|
||||||
|
|
||||||
|
elif os.path.isdir(src):
|
||||||
|
shutil.copytree(src, dst)
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise IOError("{} is neither file nor directory. Can't copy.".format(src))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user