| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889 |
- #!/usr/bin/env python3
- """Install the Ember DLL and all mission Lua files into a closed Linux/Proton game.
- Never launches/stops the game, and never modifies settings, saves or generated SDK.
- """
- import argparse
- import datetime
- import hashlib
- import json
- import os
- from pathlib import Path
- import shutil
- def closed():
- if not Path('/proc').is_dir():
- raise RuntimeError('This process guard requires Linux /proc; use a verified manual install elsewhere.')
- for process in Path('/proc').iterdir():
- if not process.name.isdecimal():
- continue
- try:
- name = (process / 'comm').read_text().strip().lower()
- except FileNotFoundError:
- continue
- if 'destiny' in name:
- raise RuntimeError('Game running; installation cancelled. The user must close it manually.')
- def sha(path):
- digest = hashlib.sha256()
- with path.open('rb') as stream:
- for block in iter(lambda: stream.read(1024 * 1024), b''):
- digest.update(block)
- return digest.hexdigest()
- def main():
- parser = argparse.ArgumentParser(description=__doc__)
- parser.add_argument('--game', type=Path, required=True, help='Game bin/x64 directory')
- parser.add_argument('--dll', type=Path, help='Override build/x64/Release/steam_api64.dll')
- parser.add_argument('--dry-run', action='store_true')
- args = parser.parse_args()
- root = Path(__file__).resolve().parents[1]
- game = args.game.expanduser().resolve()
- if not game.is_dir() or not (game / 'steam_api64.dll').is_file():
- raise RuntimeError('Target must be the existing game bin/x64 directory containing steam_api64.dll')
- dll = args.dll.expanduser().resolve() if args.dll else root / 'build/x64/Release/steam_api64.dll'
- pairs = [(dll, game / 'steam_api64.dll'),
- (root / 'scripts/mission_ember.lua', game / 'Sunrise/scripts/mission_ember.lua')]
- pairs += [(p, game / 'Sunrise/scripts/mission_ember' / p.name)
- for p in sorted((root / 'scripts/mission_ember').glob('*.lua'))]
- if len(pairs) != 18:
- raise RuntimeError('Expected one DLL and 17 mission Lua files; review the deployment list.')
- for source, _ in pairs:
- if not source.is_file():
- raise RuntimeError(f'Missing source: {source}')
- closed()
- manifest = {'created': datetime.datetime.now().astimezone().isoformat(), 'files': [
- {'path': str(dst.relative_to(game)), 'source': str(src), 'sha256': sha(src)}
- for src, dst in pairs]}
- if args.dry_run:
- print(json.dumps(manifest, indent=2))
- return
- backup = root / 'build' / ('ember-install-backup-' + datetime.datetime.now().strftime('%Y%m%d-%H%M%S-%f'))
- backup.mkdir(parents=True)
- for _, dst in pairs:
- if dst.exists():
- keep = backup / dst.relative_to(game)
- keep.parent.mkdir(parents=True, exist_ok=True)
- shutil.copy2(dst, keep)
- closed()
- for (src, dst), item in zip(pairs, manifest['files']):
- closed()
- dst.parent.mkdir(parents=True, exist_ok=True)
- temporary = dst.with_name(dst.name + '.ember-install-tmp')
- shutil.copy2(src, temporary)
- if sha(temporary) != item['sha256']:
- temporary.unlink()
- raise RuntimeError(f'Source changed during installation: {src}')
- os.replace(temporary, dst)
- if sha(dst) != item['sha256']:
- raise RuntimeError(f'Installed checksum mismatch: {dst}')
- manifest['backup'] = str(backup)
- (root / 'build/ember-installation.json').write_text(json.dumps(manifest, indent=2) + '\n')
- print(f'Installed and verified {len(pairs)} files. Backup: {backup}')
- print('Settings, saves and SDK untouched; game not launched.')
- if __name__ == '__main__':
- main()
|