Compare commits
131 Commits
Author | SHA1 | Date | |
---|---|---|---|
f4fe1c6699 | |||
59f1b09337 | |||
237fa8c16f | |||
78e155408e | |||
3033dd2315 | |||
48851fa8d3 | |||
84ea4e48af | |||
b408f13cce | |||
959c3a8a61 | |||
73df20ebb6 | |||
08814a8c3e | |||
65094d4701 | |||
a20b1acdda | |||
75b1d6254b | |||
65f05a5a1c | |||
fa6bdc9f29 | |||
7595a46b44 | |||
c41a843315 | |||
a90a60ec3a | |||
209b599120 | |||
9f00b0c0e7 | |||
aaede9b6cb | |||
8a14008706 | |||
ad7e78ec47 | |||
140f4c335f | |||
3e1a266c3e | |||
92663425ef | |||
ffa379e6dd | |||
a41375a791 | |||
ac76e9bb89 | |||
546d8c4cdf | |||
12ccc9554f | |||
c117eaeb31 | |||
50c7b9fbd7 | |||
f09271c29c | |||
b71c15cb8e | |||
a3000bd2fa | |||
cc00cad4f2 | |||
6a16299cee | |||
565a69566c | |||
1727446cfc | |||
4282eb7b75 | |||
a2922540c5 | |||
601abd008f | |||
322fd2f98a | |||
e061bca7a3 | |||
87e86cc954 | |||
d5d847df75 | |||
d620bc9838 | |||
76a7d8b2c6 | |||
6c7fbd1ff1 | |||
18651a68ca | |||
79559fe46a | |||
8fd66d16af | |||
4d3c6b9dab | |||
7ca7615222 | |||
2195f42abc | |||
f7635d4089 | |||
9598ec3dfe | |||
37266207e7 | |||
e041df80c5 | |||
b157bcb2c2 | |||
beae0adce5 | |||
f9ec044a15 | |||
0be3adf8d1 | |||
59f347462d | |||
78e4e18176 | |||
e0f4a5102e | |||
c80765d091 | |||
3b77755848 | |||
4c1114f3e0 | |||
b69611e66b | |||
6ec4deed32 | |||
744e309190 | |||
83e1f67d0d | |||
b83720af0a | |||
7e36b7496c | |||
98cbc2a9a7 | |||
85e17260eb | |||
14181845fb | |||
9323f12442 | |||
a7bf44f105 | |||
e715cd5abf | |||
a234232751 | |||
e84a8db0aa | |||
fdf35efa81 | |||
e94d71b80e | |||
d233dfb53c | |||
d04083c473 | |||
98d43d2627 | |||
4fe5ed1f77 | |||
7398ca6ca7 | |||
4e6ef5d0e5 | |||
33463f1a6e | |||
41311ed5ad | |||
69686b3a09 | |||
8be94a31ac | |||
99da9832ff | |||
b175fc2b5f | |||
02ae0459cb | |||
611049ec1b | |||
2e4467779d | |||
e2c22a716b | |||
796c67d708 | |||
21b7b1ea4b | |||
c4046ae148 | |||
b1decd0b17 | |||
966a63df62 | |||
abb583aeff | |||
06584a3195 | |||
7fcfa2aa57 | |||
2e4436ed9a | |||
d92319e15f | |||
a5cb68e7fa | |||
eb161c2375 | |||
36534e56d9 | |||
757c1acf1b | |||
b15d0eb7d7 | |||
eb508f2b48 | |||
6ae9bcabad | |||
d25e7cf3cd | |||
d587553845 | |||
4a488abb8e | |||
872bad1b25 | |||
f60555a442 | |||
03575e8fc0 | |||
580d6e9646 | |||
b6933d357a | |||
46f452a71f | |||
9986ce1172 | |||
6bc598cb2d |
63
.gitattributes
vendored
63
.gitattributes
vendored
@ -1,63 +0,0 @@
|
|||||||
###############################################################################
|
|
||||||
# Set default behavior to automatically normalize line endings.
|
|
||||||
###############################################################################
|
|
||||||
* text=auto
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
# Set default behavior for command prompt diff.
|
|
||||||
#
|
|
||||||
# This is need for earlier builds of msysgit that does not have it on by
|
|
||||||
# default for csharp files.
|
|
||||||
# Note: This is only used by command line
|
|
||||||
###############################################################################
|
|
||||||
#*.cs diff=csharp
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
# Set the merge driver for project and solution files
|
|
||||||
#
|
|
||||||
# Merging from the command prompt will add diff markers to the files if there
|
|
||||||
# are conflicts (Merging from VS is not affected by the settings below, in VS
|
|
||||||
# the diff markers are never inserted). Diff markers may cause the following
|
|
||||||
# file extensions to fail to load in VS. An alternative would be to treat
|
|
||||||
# these files as binary and thus will always conflict and require user
|
|
||||||
# intervention with every merge. To do so, just uncomment the entries below
|
|
||||||
###############################################################################
|
|
||||||
#*.sln merge=binary
|
|
||||||
#*.csproj merge=binary
|
|
||||||
#*.vbproj merge=binary
|
|
||||||
#*.vcxproj merge=binary
|
|
||||||
#*.vcproj merge=binary
|
|
||||||
#*.dbproj merge=binary
|
|
||||||
#*.fsproj merge=binary
|
|
||||||
#*.lsproj merge=binary
|
|
||||||
#*.wixproj merge=binary
|
|
||||||
#*.modelproj merge=binary
|
|
||||||
#*.sqlproj merge=binary
|
|
||||||
#*.wwaproj merge=binary
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
# behavior for image files
|
|
||||||
#
|
|
||||||
# image files are treated as binary by default.
|
|
||||||
###############################################################################
|
|
||||||
#*.jpg binary
|
|
||||||
#*.png binary
|
|
||||||
#*.gif binary
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
# diff behavior for common document formats
|
|
||||||
#
|
|
||||||
# Convert binary document formats to text before diffing them. This feature
|
|
||||||
# is only available from the command line. Turn it on by uncommenting the
|
|
||||||
# entries below.
|
|
||||||
###############################################################################
|
|
||||||
#*.doc diff=astextplain
|
|
||||||
#*.DOC diff=astextplain
|
|
||||||
#*.docx diff=astextplain
|
|
||||||
#*.DOCX diff=astextplain
|
|
||||||
#*.dot diff=astextplain
|
|
||||||
#*.DOT diff=astextplain
|
|
||||||
#*.pdf diff=astextplain
|
|
||||||
#*.PDF diff=astextplain
|
|
||||||
#*.rtf diff=astextplain
|
|
||||||
#*.RTF diff=astextplain
|
|
6
.github/dependabot.yml
vendored
Normal file
6
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "cargo" # See documentation for possible values
|
||||||
|
directory: "/" # Location of package manifests
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
30
.github/workflows/build.yml
vendored
Normal file
30
.github/workflows/build.yml
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
name: Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: build ${{ matrix.target }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
target: [x86_64-pc-windows-gnu, x86_64-unknown-linux-musl]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@master
|
||||||
|
- name: Compile and release
|
||||||
|
id: compile
|
||||||
|
uses: rust-build/rust-build.action@v1.4.3
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
RUSTTARGET: ${{ matrix.target }}
|
||||||
|
UPLOAD_MODE: none
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: Binary
|
||||||
|
path: |
|
||||||
|
${{ steps.compile.outputs.BUILT_ARCHIVE }}
|
||||||
|
${{ steps.compile.outputs.BUILT_CHECKSUM }}
|
79
.github/workflows/lint.yml
vendored
Normal file
79
.github/workflows/lint.yml
vendored
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
# Based on https://github.com/actions-rs/meta/blob/master/recipes/quickstart.md
|
||||||
|
#
|
||||||
|
# While our "example" application has the platform-specific code,
|
||||||
|
# for simplicity we are compiling and testing everything on the Ubuntu environment only.
|
||||||
|
# For multi-OS testing see the `cross.yml` workflow.
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
name: lint
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check:
|
||||||
|
name: Check
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout sources
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install stable toolchain
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
profile: minimal
|
||||||
|
toolchain: stable
|
||||||
|
override: true
|
||||||
|
|
||||||
|
- name: Run cargo check
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
continue-on-error: true # WARNING: only for this example, remove it!
|
||||||
|
with:
|
||||||
|
command: check
|
||||||
|
|
||||||
|
test:
|
||||||
|
name: Test Suite
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout sources
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install stable toolchain
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
profile: minimal
|
||||||
|
toolchain: stable
|
||||||
|
override: true
|
||||||
|
|
||||||
|
- name: Run cargo test
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
continue-on-error: true # WARNING: only for this example, remove it!
|
||||||
|
with:
|
||||||
|
command: test
|
||||||
|
|
||||||
|
lints:
|
||||||
|
name: Lints
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout sources
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install stable toolchain
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
profile: minimal
|
||||||
|
toolchain: stable
|
||||||
|
override: true
|
||||||
|
components: rustfmt, clippy
|
||||||
|
|
||||||
|
- name: Run cargo fmt
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
continue-on-error: true # WARNING: only for this example, remove it!
|
||||||
|
with:
|
||||||
|
command: fmt
|
||||||
|
args: --all -- --check
|
||||||
|
|
||||||
|
- name: Run cargo clippy
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
continue-on-error: true # WARNING: only for this example, remove it!
|
||||||
|
with:
|
||||||
|
command: clippy
|
||||||
|
args: -- -D warnings
|
33
.github/workflows/release.yml
vendored
Normal file
33
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- v[0-9]+.*
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
create-release:
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: taiki-e/create-gh-release-action@v1
|
||||||
|
with:
|
||||||
|
draft: true
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
upload-assets:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os:
|
||||||
|
- ubuntu-20.04
|
||||||
|
- windows-latest
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: taiki-e/upload-rust-binary-action@v1
|
||||||
|
with:
|
||||||
|
bin: alterware-launcher
|
||||||
|
tar: unix
|
||||||
|
zip: windows
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
364
.gitignore
vendored
364
.gitignore
vendored
@ -1,363 +1 @@
|
|||||||
## Ignore Visual Studio temporary files, build results, and
|
/target
|
||||||
## files generated by popular Visual Studio add-ons.
|
|
||||||
##
|
|
||||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
|
||||||
|
|
||||||
# User-specific files
|
|
||||||
*.rsuser
|
|
||||||
*.suo
|
|
||||||
*.user
|
|
||||||
*.userosscache
|
|
||||||
*.sln.docstates
|
|
||||||
|
|
||||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
|
||||||
*.userprefs
|
|
||||||
|
|
||||||
# Mono auto generated files
|
|
||||||
mono_crash.*
|
|
||||||
|
|
||||||
# Build results
|
|
||||||
[Dd]ebug/
|
|
||||||
[Dd]ebugPublic/
|
|
||||||
[Rr]elease/
|
|
||||||
[Rr]eleases/
|
|
||||||
x64/
|
|
||||||
x86/
|
|
||||||
[Ww][Ii][Nn]32/
|
|
||||||
[Aa][Rr][Mm]/
|
|
||||||
[Aa][Rr][Mm]64/
|
|
||||||
bld/
|
|
||||||
[Bb]in/
|
|
||||||
[Oo]bj/
|
|
||||||
[Oo]ut/
|
|
||||||
[Ll]og/
|
|
||||||
[Ll]ogs/
|
|
||||||
|
|
||||||
# Visual Studio 2015/2017 cache/options directory
|
|
||||||
.vs/
|
|
||||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
|
||||||
#wwwroot/
|
|
||||||
|
|
||||||
# Visual Studio 2017 auto generated files
|
|
||||||
Generated\ Files/
|
|
||||||
|
|
||||||
# MSTest test Results
|
|
||||||
[Tt]est[Rr]esult*/
|
|
||||||
[Bb]uild[Ll]og.*
|
|
||||||
|
|
||||||
# NUnit
|
|
||||||
*.VisualState.xml
|
|
||||||
TestResult.xml
|
|
||||||
nunit-*.xml
|
|
||||||
|
|
||||||
# Build Results of an ATL Project
|
|
||||||
[Dd]ebugPS/
|
|
||||||
[Rr]eleasePS/
|
|
||||||
dlldata.c
|
|
||||||
|
|
||||||
# Benchmark Results
|
|
||||||
BenchmarkDotNet.Artifacts/
|
|
||||||
|
|
||||||
# .NET Core
|
|
||||||
project.lock.json
|
|
||||||
project.fragment.lock.json
|
|
||||||
artifacts/
|
|
||||||
|
|
||||||
# ASP.NET Scaffolding
|
|
||||||
ScaffoldingReadMe.txt
|
|
||||||
|
|
||||||
# StyleCop
|
|
||||||
StyleCopReport.xml
|
|
||||||
|
|
||||||
# Files built by Visual Studio
|
|
||||||
*_i.c
|
|
||||||
*_p.c
|
|
||||||
*_h.h
|
|
||||||
*.ilk
|
|
||||||
*.meta
|
|
||||||
*.obj
|
|
||||||
*.iobj
|
|
||||||
*.pch
|
|
||||||
*.pdb
|
|
||||||
*.ipdb
|
|
||||||
*.pgc
|
|
||||||
*.pgd
|
|
||||||
*.rsp
|
|
||||||
*.sbr
|
|
||||||
*.tlb
|
|
||||||
*.tli
|
|
||||||
*.tlh
|
|
||||||
*.tmp
|
|
||||||
*.tmp_proj
|
|
||||||
*_wpftmp.csproj
|
|
||||||
*.log
|
|
||||||
*.vspscc
|
|
||||||
*.vssscc
|
|
||||||
.builds
|
|
||||||
*.pidb
|
|
||||||
*.svclog
|
|
||||||
*.scc
|
|
||||||
|
|
||||||
# Chutzpah Test files
|
|
||||||
_Chutzpah*
|
|
||||||
|
|
||||||
# Visual C++ cache files
|
|
||||||
ipch/
|
|
||||||
*.aps
|
|
||||||
*.ncb
|
|
||||||
*.opendb
|
|
||||||
*.opensdf
|
|
||||||
*.sdf
|
|
||||||
*.cachefile
|
|
||||||
*.VC.db
|
|
||||||
*.VC.VC.opendb
|
|
||||||
|
|
||||||
# Visual Studio profiler
|
|
||||||
*.psess
|
|
||||||
*.vsp
|
|
||||||
*.vspx
|
|
||||||
*.sap
|
|
||||||
|
|
||||||
# Visual Studio Trace Files
|
|
||||||
*.e2e
|
|
||||||
|
|
||||||
# TFS 2012 Local Workspace
|
|
||||||
$tf/
|
|
||||||
|
|
||||||
# Guidance Automation Toolkit
|
|
||||||
*.gpState
|
|
||||||
|
|
||||||
# ReSharper is a .NET coding add-in
|
|
||||||
_ReSharper*/
|
|
||||||
*.[Rr]e[Ss]harper
|
|
||||||
*.DotSettings.user
|
|
||||||
|
|
||||||
# TeamCity is a build add-in
|
|
||||||
_TeamCity*
|
|
||||||
|
|
||||||
# DotCover is a Code Coverage Tool
|
|
||||||
*.dotCover
|
|
||||||
|
|
||||||
# AxoCover is a Code Coverage Tool
|
|
||||||
.axoCover/*
|
|
||||||
!.axoCover/settings.json
|
|
||||||
|
|
||||||
# Coverlet is a free, cross platform Code Coverage Tool
|
|
||||||
coverage*.json
|
|
||||||
coverage*.xml
|
|
||||||
coverage*.info
|
|
||||||
|
|
||||||
# Visual Studio code coverage results
|
|
||||||
*.coverage
|
|
||||||
*.coveragexml
|
|
||||||
|
|
||||||
# NCrunch
|
|
||||||
_NCrunch_*
|
|
||||||
.*crunch*.local.xml
|
|
||||||
nCrunchTemp_*
|
|
||||||
|
|
||||||
# MightyMoose
|
|
||||||
*.mm.*
|
|
||||||
AutoTest.Net/
|
|
||||||
|
|
||||||
# Web workbench (sass)
|
|
||||||
.sass-cache/
|
|
||||||
|
|
||||||
# Installshield output folder
|
|
||||||
[Ee]xpress/
|
|
||||||
|
|
||||||
# DocProject is a documentation generator add-in
|
|
||||||
DocProject/buildhelp/
|
|
||||||
DocProject/Help/*.HxT
|
|
||||||
DocProject/Help/*.HxC
|
|
||||||
DocProject/Help/*.hhc
|
|
||||||
DocProject/Help/*.hhk
|
|
||||||
DocProject/Help/*.hhp
|
|
||||||
DocProject/Help/Html2
|
|
||||||
DocProject/Help/html
|
|
||||||
|
|
||||||
# Click-Once directory
|
|
||||||
publish/
|
|
||||||
|
|
||||||
# Publish Web Output
|
|
||||||
*.[Pp]ublish.xml
|
|
||||||
*.azurePubxml
|
|
||||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
|
||||||
# but database connection strings (with potential passwords) will be unencrypted
|
|
||||||
*.pubxml
|
|
||||||
*.publishproj
|
|
||||||
|
|
||||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
|
||||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
|
||||||
# in these scripts will be unencrypted
|
|
||||||
PublishScripts/
|
|
||||||
|
|
||||||
# NuGet Packages
|
|
||||||
*.nupkg
|
|
||||||
# NuGet Symbol Packages
|
|
||||||
*.snupkg
|
|
||||||
# The packages folder can be ignored because of Package Restore
|
|
||||||
**/[Pp]ackages/*
|
|
||||||
# except build/, which is used as an MSBuild target.
|
|
||||||
!**/[Pp]ackages/build/
|
|
||||||
# Uncomment if necessary however generally it will be regenerated when needed
|
|
||||||
#!**/[Pp]ackages/repositories.config
|
|
||||||
# NuGet v3's project.json files produces more ignorable files
|
|
||||||
*.nuget.props
|
|
||||||
*.nuget.targets
|
|
||||||
|
|
||||||
# Microsoft Azure Build Output
|
|
||||||
csx/
|
|
||||||
*.build.csdef
|
|
||||||
|
|
||||||
# Microsoft Azure Emulator
|
|
||||||
ecf/
|
|
||||||
rcf/
|
|
||||||
|
|
||||||
# Windows Store app package directories and files
|
|
||||||
AppPackages/
|
|
||||||
BundleArtifacts/
|
|
||||||
Package.StoreAssociation.xml
|
|
||||||
_pkginfo.txt
|
|
||||||
*.appx
|
|
||||||
*.appxbundle
|
|
||||||
*.appxupload
|
|
||||||
|
|
||||||
# Visual Studio cache files
|
|
||||||
# files ending in .cache can be ignored
|
|
||||||
*.[Cc]ache
|
|
||||||
# but keep track of directories ending in .cache
|
|
||||||
!?*.[Cc]ache/
|
|
||||||
|
|
||||||
# Others
|
|
||||||
ClientBin/
|
|
||||||
~$*
|
|
||||||
*~
|
|
||||||
*.dbmdl
|
|
||||||
*.dbproj.schemaview
|
|
||||||
*.jfm
|
|
||||||
*.pfx
|
|
||||||
*.publishsettings
|
|
||||||
orleans.codegen.cs
|
|
||||||
|
|
||||||
# Including strong name files can present a security risk
|
|
||||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
|
||||||
#*.snk
|
|
||||||
|
|
||||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
|
||||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
|
||||||
#bower_components/
|
|
||||||
|
|
||||||
# RIA/Silverlight projects
|
|
||||||
Generated_Code/
|
|
||||||
|
|
||||||
# Backup & report files from converting an old project file
|
|
||||||
# to a newer Visual Studio version. Backup files are not needed,
|
|
||||||
# because we have git ;-)
|
|
||||||
_UpgradeReport_Files/
|
|
||||||
Backup*/
|
|
||||||
UpgradeLog*.XML
|
|
||||||
UpgradeLog*.htm
|
|
||||||
ServiceFabricBackup/
|
|
||||||
*.rptproj.bak
|
|
||||||
|
|
||||||
# SQL Server files
|
|
||||||
*.mdf
|
|
||||||
*.ldf
|
|
||||||
*.ndf
|
|
||||||
|
|
||||||
# Business Intelligence projects
|
|
||||||
*.rdl.data
|
|
||||||
*.bim.layout
|
|
||||||
*.bim_*.settings
|
|
||||||
*.rptproj.rsuser
|
|
||||||
*- [Bb]ackup.rdl
|
|
||||||
*- [Bb]ackup ([0-9]).rdl
|
|
||||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
|
||||||
|
|
||||||
# Microsoft Fakes
|
|
||||||
FakesAssemblies/
|
|
||||||
|
|
||||||
# GhostDoc plugin setting file
|
|
||||||
*.GhostDoc.xml
|
|
||||||
|
|
||||||
# Node.js Tools for Visual Studio
|
|
||||||
.ntvs_analysis.dat
|
|
||||||
node_modules/
|
|
||||||
|
|
||||||
# Visual Studio 6 build log
|
|
||||||
*.plg
|
|
||||||
|
|
||||||
# Visual Studio 6 workspace options file
|
|
||||||
*.opt
|
|
||||||
|
|
||||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
|
||||||
*.vbw
|
|
||||||
|
|
||||||
# Visual Studio LightSwitch build output
|
|
||||||
**/*.HTMLClient/GeneratedArtifacts
|
|
||||||
**/*.DesktopClient/GeneratedArtifacts
|
|
||||||
**/*.DesktopClient/ModelManifest.xml
|
|
||||||
**/*.Server/GeneratedArtifacts
|
|
||||||
**/*.Server/ModelManifest.xml
|
|
||||||
_Pvt_Extensions
|
|
||||||
|
|
||||||
# Paket dependency manager
|
|
||||||
.paket/paket.exe
|
|
||||||
paket-files/
|
|
||||||
|
|
||||||
# FAKE - F# Make
|
|
||||||
.fake/
|
|
||||||
|
|
||||||
# CodeRush personal settings
|
|
||||||
.cr/personal
|
|
||||||
|
|
||||||
# Python Tools for Visual Studio (PTVS)
|
|
||||||
__pycache__/
|
|
||||||
*.pyc
|
|
||||||
|
|
||||||
# Cake - Uncomment if you are using it
|
|
||||||
# tools/**
|
|
||||||
# !tools/packages.config
|
|
||||||
|
|
||||||
# Tabs Studio
|
|
||||||
*.tss
|
|
||||||
|
|
||||||
# Telerik's JustMock configuration file
|
|
||||||
*.jmconfig
|
|
||||||
|
|
||||||
# BizTalk build output
|
|
||||||
*.btp.cs
|
|
||||||
*.btm.cs
|
|
||||||
*.odx.cs
|
|
||||||
*.xsd.cs
|
|
||||||
|
|
||||||
# OpenCover UI analysis results
|
|
||||||
OpenCover/
|
|
||||||
|
|
||||||
# Azure Stream Analytics local run output
|
|
||||||
ASALocalRun/
|
|
||||||
|
|
||||||
# MSBuild Binary and Structured Log
|
|
||||||
*.binlog
|
|
||||||
|
|
||||||
# NVidia Nsight GPU debugger configuration file
|
|
||||||
*.nvuser
|
|
||||||
|
|
||||||
# MFractors (Xamarin productivity tool) working folder
|
|
||||||
.mfractor/
|
|
||||||
|
|
||||||
# Local History for Visual Studio
|
|
||||||
.localhistory/
|
|
||||||
|
|
||||||
# BeatPulse healthcheck temp database
|
|
||||||
healthchecksdb
|
|
||||||
|
|
||||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
|
||||||
MigrationBackup/
|
|
||||||
|
|
||||||
# Ionide (cross platform F# VS Code tools) working folder
|
|
||||||
.ionide/
|
|
||||||
|
|
||||||
# Fody - auto-generated XML schema
|
|
||||||
FodyWeavers.xsd
|
|
||||||
|
1161
Cargo.lock
generated
Normal file
1161
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
44
Cargo.toml
Normal file
44
Cargo.toml
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
[package]
|
||||||
|
name = "alterware-launcher"
|
||||||
|
version = "0.5.1"
|
||||||
|
edition = "2021"
|
||||||
|
build = "res/build.rs"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
opt-level = "s"
|
||||||
|
|
||||||
|
# Symbols are a nice thing
|
||||||
|
debug = true
|
||||||
|
|
||||||
|
panic = "abort"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
http_req = { version = "0.9.2", default-features = false, features = [
|
||||||
|
"rust-tls",
|
||||||
|
] }
|
||||||
|
sha1_smol = "1.0.0"
|
||||||
|
serde = { version = "1.0.188", features = ["derive"] }
|
||||||
|
serde_json = "1.0.106"
|
||||||
|
rand = "0.8.5"
|
||||||
|
semver = "1.0.18"
|
||||||
|
zip = "0.6.6"
|
||||||
|
colored = "2.0.4"
|
||||||
|
|
||||||
|
[target.'cfg(windows)'.dependencies]
|
||||||
|
steamlocate = "2.0.0-alpha.0"
|
||||||
|
mslnk = "0.1.8"
|
||||||
|
# https://github.com/mitsuhiko/self-replace/pull/16/
|
||||||
|
windows-sys = { version = "0.48", features = [
|
||||||
|
"Win32_Security",
|
||||||
|
] }
|
||||||
|
self-replace = "1.3.6"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
winres = "0.1.12"
|
||||||
|
|
||||||
|
[package.metadata.winres]
|
||||||
|
OriginalFilename = "alterware-launcher.exe"
|
||||||
|
FileDescription = "AlterWare Launcher"
|
||||||
|
ProductName = "AlterWare Launcher"
|
63
My Project/Resources.Designer.vb
generated
63
My Project/Resources.Designer.vb
generated
@ -1,63 +0,0 @@
|
|||||||
'------------------------------------------------------------------------------
|
|
||||||
' <auto-generated>
|
|
||||||
' This code was generated by a tool.
|
|
||||||
' Runtime Version:4.0.30319.42000
|
|
||||||
'
|
|
||||||
' Changes to this file may cause incorrect behavior and will be lost if
|
|
||||||
' the code is regenerated.
|
|
||||||
' </auto-generated>
|
|
||||||
'------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
Option Strict On
|
|
||||||
Option Explicit On
|
|
||||||
|
|
||||||
Imports System
|
|
||||||
|
|
||||||
Namespace My.Resources
|
|
||||||
|
|
||||||
'This class was auto-generated by the StronglyTypedResourceBuilder
|
|
||||||
'class via a tool like ResGen or Visual Studio.
|
|
||||||
'To add or remove a member, edit your .ResX file then rerun ResGen
|
|
||||||
'with the /str option, or rebuild your VS project.
|
|
||||||
'''<summary>
|
|
||||||
''' A strongly-typed resource class, for looking up localized strings, etc.
|
|
||||||
'''</summary>
|
|
||||||
<Global.System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0"), _
|
|
||||||
Global.System.Diagnostics.DebuggerNonUserCodeAttribute(), _
|
|
||||||
Global.System.Runtime.CompilerServices.CompilerGeneratedAttribute(), _
|
|
||||||
Global.Microsoft.VisualBasic.HideModuleNameAttribute()> _
|
|
||||||
Friend Module Resources
|
|
||||||
|
|
||||||
Private resourceMan As Global.System.Resources.ResourceManager
|
|
||||||
|
|
||||||
Private resourceCulture As Global.System.Globalization.CultureInfo
|
|
||||||
|
|
||||||
'''<summary>
|
|
||||||
''' Returns the cached ResourceManager instance used by this class.
|
|
||||||
'''</summary>
|
|
||||||
<Global.System.ComponentModel.EditorBrowsableAttribute(Global.System.ComponentModel.EditorBrowsableState.Advanced)> _
|
|
||||||
Friend ReadOnly Property ResourceManager() As Global.System.Resources.ResourceManager
|
|
||||||
Get
|
|
||||||
If Object.ReferenceEquals(resourceMan, Nothing) Then
|
|
||||||
Dim temp As Global.System.Resources.ResourceManager = New Global.System.Resources.ResourceManager("alterware_launcher.Resources", GetType(Resources).Assembly)
|
|
||||||
resourceMan = temp
|
|
||||||
End If
|
|
||||||
Return resourceMan
|
|
||||||
End Get
|
|
||||||
End Property
|
|
||||||
|
|
||||||
'''<summary>
|
|
||||||
''' Overrides the current thread's CurrentUICulture property for all
|
|
||||||
''' resource lookups using this strongly typed resource class.
|
|
||||||
'''</summary>
|
|
||||||
<Global.System.ComponentModel.EditorBrowsableAttribute(Global.System.ComponentModel.EditorBrowsableState.Advanced)> _
|
|
||||||
Friend Property Culture() As Global.System.Globalization.CultureInfo
|
|
||||||
Get
|
|
||||||
Return resourceCulture
|
|
||||||
End Get
|
|
||||||
Set
|
|
||||||
resourceCulture = value
|
|
||||||
End Set
|
|
||||||
End Property
|
|
||||||
End Module
|
|
||||||
End Namespace
|
|
@ -1,110 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<root>
|
|
||||||
<!--
|
|
||||||
Microsoft ResX Schema
|
|
||||||
|
|
||||||
Version 2.0
|
|
||||||
|
|
||||||
The primary goals of this format is to allow a simple XML format
|
|
||||||
that is mostly human readable. The generation and parsing of the
|
|
||||||
various data types are done through the TypeConverter classes
|
|
||||||
associated with the data types.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
... ado.net/XML headers & schema ...
|
|
||||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
|
||||||
<resheader name="version">2.0</resheader>
|
|
||||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
|
||||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
|
||||||
<data name="Name1">this is my long string</data>
|
|
||||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
|
||||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
|
||||||
[base64 mime encoded serialized .NET Framework object]
|
|
||||||
</data>
|
|
||||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
|
||||||
[base64 mime encoded string representing a byte array form of the .NET Framework object]
|
|
||||||
</data>
|
|
||||||
|
|
||||||
There are any number of "resheader" rows that contain simple
|
|
||||||
name/value pairs.
|
|
||||||
|
|
||||||
Each data row contains a name, and value. The row also contains a
|
|
||||||
type or mimetype. Type corresponds to a .NET class that support
|
|
||||||
text/value conversion through the TypeConverter architecture.
|
|
||||||
Classes that don't support this are serialized and stored with the
|
|
||||||
mimetype set.
|
|
||||||
|
|
||||||
The mimetype is used for serialized objects, and tells the
|
|
||||||
ResXResourceReader how to depersist the object. This is currently not
|
|
||||||
extensible. For a given mimetype the value must be set accordingly:
|
|
||||||
|
|
||||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
|
||||||
that the ResXResourceWriter will generate, however the reader can
|
|
||||||
read any of the formats listed below.
|
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.binary.base64
|
|
||||||
value : The object must be serialized with
|
|
||||||
: System.Serialization.Formatters.Binary.BinaryFormatter
|
|
||||||
: and then encoded with base64 encoding.
|
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.soap.base64
|
|
||||||
value : The object must be serialized with
|
|
||||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
|
||||||
: and then encoded with base64 encoding.
|
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
|
||||||
value : The object must be serialized into a byte array
|
|
||||||
: using a System.ComponentModel.TypeConverter
|
|
||||||
: and then encoded with base64 encoding.
|
|
||||||
-->
|
|
||||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
|
||||||
<xsd:element name="root" msdata:IsDataSet="true">
|
|
||||||
<xsd:complexType>
|
|
||||||
<xsd:choice maxOccurs="unbounded">
|
|
||||||
<xsd:element name="metadata">
|
|
||||||
<xsd:complexType>
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="name" type="xsd:string" />
|
|
||||||
<xsd:attribute name="type" type="xsd:string" />
|
|
||||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
|
||||||
</xsd:complexType>
|
|
||||||
</xsd:element>
|
|
||||||
<xsd:element name="data">
|
|
||||||
<xsd:complexType>
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
|
||||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
|
|
||||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
|
||||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
|
||||||
</xsd:complexType>
|
|
||||||
</xsd:element>
|
|
||||||
<xsd:element name="resheader">
|
|
||||||
<xsd:complexType>
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
|
||||||
</xsd:complexType>
|
|
||||||
</xsd:element>
|
|
||||||
</xsd:choice>
|
|
||||||
</xsd:complexType>
|
|
||||||
</xsd:element>
|
|
||||||
</xsd:schema>
|
|
||||||
<resheader name="resmimetype">
|
|
||||||
<value>text/microsoft-resx</value>
|
|
||||||
</resheader>
|
|
||||||
<resheader name="version">
|
|
||||||
<value>2.0</value>
|
|
||||||
</resheader>
|
|
||||||
<resheader name="reader">
|
|
||||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
|
||||||
</resheader>
|
|
||||||
<resheader name="writer">
|
|
||||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
|
||||||
</resheader>
|
|
||||||
</root>
|
|
39
Program.vb
39
Program.vb
@ -1,39 +0,0 @@
|
|||||||
Imports System
|
|
||||||
Imports System.IO
|
|
||||||
Imports System.Net
|
|
||||||
Imports System.Net.Http
|
|
||||||
|
|
||||||
Module Program
|
|
||||||
Private master As String = "https://master.alterware.dev"
|
|
||||||
|
|
||||||
Sub download_and_run(game As String)
|
|
||||||
Dim filename As String = game & ".exe"
|
|
||||||
Dim remote_path As String = If(game = "iw4-sp", "/iw4/", "/iw5/")
|
|
||||||
Console.WriteLine("Downloading " & game & "...")
|
|
||||||
Using wc As New WebClient
|
|
||||||
wc.DownloadFile(master & remote_path & filename, filename)
|
|
||||||
End Using
|
|
||||||
Console.WriteLine("Starting " & game)
|
|
||||||
Process.Start(filename)
|
|
||||||
End
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Sub Main(args As String())
|
|
||||||
Dim game As String
|
|
||||||
Try
|
|
||||||
game = args(0)
|
|
||||||
Catch ex As Exception
|
|
||||||
If File.Exists("iw4sp.exe") Or File.Exists("iw4mp.exe") Then
|
|
||||||
game = "iw4-sp"
|
|
||||||
ElseIf File.Exists("iw5sp.exe") Or File.Exists("iw5mp.exe") Or File.Exists("iw5mp_server.exe") Then
|
|
||||||
game = "iw5-mod"
|
|
||||||
Else
|
|
||||||
Console.WriteLine("No game specified nor found in local directory")
|
|
||||||
Console.ReadLine()
|
|
||||||
Return
|
|
||||||
End If
|
|
||||||
End Try
|
|
||||||
|
|
||||||
download_and_run(game)
|
|
||||||
End Sub
|
|
||||||
End Module
|
|
72
README.md
72
README.md
@ -1,7 +1,69 @@
|
|||||||
# alterware-launcher
|
# AlterWare Launcher
|
||||||
|
|
||||||
1. Download [latest release](https://github.com/mxve/alterware-launcher/releases/latest/download/alterware-launcher.exe)
|
### [AlterWare.dev](https://alterware.dev)
|
||||||
2. Please alterware-launcher.exe in MW2/MW3 game directory
|
|
||||||
3. Run alterware-launcher.exe
|
|
||||||
|
|
||||||
If the launcher doesn't work make sure you have dotnet 6.0 installed. Also try running the launcher as administrator.
|
##### IW4x | IW4-SP | IW5-Mod | IW6-Mod | S1-Mod
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Installation
|
||||||
|
|
||||||
|
1. Download the [latest alterware-launcher.exe](https://github.com/mxve/alterware-launcher/releases/latest/download/alterware-launcher.exe)
|
||||||
|
2. Place alterware-launcher.exe in the game directory
|
||||||
|
3. Run alterware-launcher.exe, after updating the game will launch automatically
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Command line arguments
|
||||||
|
|
||||||
|
- ```iw4-sp```, ```iw4x```, ```iw5-mod```, ```iw6-mod```, ```s1-mod```
|
||||||
|
- Skip automatic detection and launch the specified game
|
||||||
|
- This should always be the first argument if used
|
||||||
|
- ```--update```, ```-u```
|
||||||
|
- Only update the game, don't launch it
|
||||||
|
- ```--skip-launcher-update```
|
||||||
|
- Don't update the launcher
|
||||||
|
- ```--bonus```
|
||||||
|
- Download bonus content
|
||||||
|
- ```--force```, ```-f```
|
||||||
|
- Force file hash recheck
|
||||||
|
- ```--path```, ```-p```
|
||||||
|
- Set the game path
|
||||||
|
- Do not include a trailing backslash in the path
|
||||||
|
- ```--pass```
|
||||||
|
- Pass additional arguments to the game
|
||||||
|
- ```--version```, ```-v```
|
||||||
|
- Print the launcher version
|
||||||
|
|
||||||
|
Example: ```alterware-launcher.exe iw4x --bonus -u --path "C:\Games\IW4x" --pass "-console"```
|
||||||
|
|
||||||
|
Some arguments can be set in alterware-launcher.json, args generally override the values of the config.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Support
|
||||||
|
|
||||||
|
Visit the [AlterWare Forum](https://forum.alterware.dev/) or [Discord](https://discord.gg/2ETE8engZM) for support.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Building from Source
|
||||||
|
|
||||||
|
- [Install Rust](https://rustup.rs/)
|
||||||
|
- Clone the repository
|
||||||
|
- Run ```cargo build --release```
|
||||||
|
- The executable will be located in ```target/release```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Note for server owners:
|
||||||
|
When the launcher updates itself it needs to be restarted. It will return exit code 201 in this case.
|
||||||
|
|
||||||
|
```
|
||||||
|
@echo off
|
||||||
|
:loop
|
||||||
|
start /wait alterware-launcher.exe --update
|
||||||
|
if %errorlevel% equ 201 (
|
||||||
|
goto loop
|
||||||
|
)
|
||||||
|
```
|
@ -1,25 +0,0 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
|
||||||
# Visual Studio Version 17
|
|
||||||
VisualStudioVersion = 17.5.33530.505
|
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
|
||||||
Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "alterware-launcher", "alterware-launcher.vbproj", "{FDEA6E70-5F8E-438C-A79A-604DC8CEC5C2}"
|
|
||||||
EndProject
|
|
||||||
Global
|
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
|
||||||
Debug|Any CPU = Debug|Any CPU
|
|
||||||
Release|Any CPU = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
|
||||||
{FDEA6E70-5F8E-438C-A79A-604DC8CEC5C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{FDEA6E70-5F8E-438C-A79A-604DC8CEC5C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{FDEA6E70-5F8E-438C-A79A-604DC8CEC5C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{FDEA6E70-5F8E-438C-A79A-604DC8CEC5C2}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
|
||||||
HideSolutionNode = FALSE
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
|
||||||
SolutionGuid = {9C3806A8-30BF-4467-A257-B01E592BBF6E}
|
|
||||||
EndGlobalSection
|
|
||||||
EndGlobal
|
|
@ -1,46 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<OutputType>Exe</OutputType>
|
|
||||||
<RootNamespace>alterware_launcher</RootNamespace>
|
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
|
||||||
<PublishSingleFile>true</PublishSingleFile>
|
|
||||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
|
||||||
<SelfContained>true</SelfContained>
|
|
||||||
<ApplicationIcon>icon.ico</ApplicationIcon>
|
|
||||||
<Description>AlterWare iw4-sp/iw5-mod updater</Description>
|
|
||||||
<PackageProjectUrl>https://github.com/mxve/alterware-launcher</PackageProjectUrl>
|
|
||||||
<PackageIcon>icon.png</PackageIcon>
|
|
||||||
<RepositoryType>git</RepositoryType>
|
|
||||||
<FileVersion>0.1.0</FileVersion>
|
|
||||||
<AssemblyVersion>0.1.0</AssemblyVersion>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Content Include="icon.ico" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<None Include="..\..\Seafile\GFX\mxve\icon.png">
|
|
||||||
<Pack>True</Pack>
|
|
||||||
<PackagePath>\</PackagePath>
|
|
||||||
</None>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Compile Update="My Project\Resources.Designer.vb">
|
|
||||||
<DesignTime>True</DesignTime>
|
|
||||||
<AutoGen>True</AutoGen>
|
|
||||||
<DependentUpon>Resources.resx</DependentUpon>
|
|
||||||
</Compile>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<EmbeddedResource Update="My Project\Resources.resx">
|
|
||||||
<CustomToolNamespace>My.Resources</CustomToolNamespace>
|
|
||||||
<Generator>VbMyResourcesResXFileCodeGenerator</Generator>
|
|
||||||
<LastGenOutput>Resources.Designer.vb</LastGenOutput>
|
|
||||||
</EmbeddedResource>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
BIN
icon_bg.ico
BIN
icon_bg.ico
Binary file not shown.
Before Width: | Height: | Size: 118 KiB |
16
res/build.rs
Normal file
16
res/build.rs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#[cfg(windows)]
|
||||||
|
extern crate winres;
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn main() {
|
||||||
|
let mut res = winres::WindowsResource::new();
|
||||||
|
res.set_icon("res/icon.ico").set_language(0x0409);
|
||||||
|
|
||||||
|
if let Err(e) = res.compile() {
|
||||||
|
eprintln!("{}", e);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn main() {}
|
BIN
res/icon.ico
Normal file
BIN
res/icon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 105 KiB |
30
src/config.rs
Normal file
30
src/config.rs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
use crate::structs::Config;
|
||||||
|
|
||||||
|
use std::{fs, path::PathBuf};
|
||||||
|
|
||||||
|
pub fn load(config_path: PathBuf) -> Config {
|
||||||
|
if config_path.exists() {
|
||||||
|
let cfg = fs::read_to_string(&config_path).unwrap();
|
||||||
|
let cfg: Config = serde_json::from_str(&cfg).unwrap_or(Config::default());
|
||||||
|
return cfg;
|
||||||
|
}
|
||||||
|
save(config_path.clone(), Config::default());
|
||||||
|
Config::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save(config_path: PathBuf, config: Config) {
|
||||||
|
fs::write(config_path, serde_json::to_string(&config).unwrap()).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save_value(config_path: PathBuf, key: &str, value: bool) {
|
||||||
|
let mut config = load(config_path.clone());
|
||||||
|
match key {
|
||||||
|
"update_only" => config.update_only = value,
|
||||||
|
"skip_self_update" => config.skip_self_update = value,
|
||||||
|
"download_bonus_content" => config.download_bonus_content = value,
|
||||||
|
"ask_bonus_content" => config.ask_bonus_content = value,
|
||||||
|
"force_update" => config.force_update = value,
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
save(config_path, config);
|
||||||
|
}
|
22
src/github.rs
Normal file
22
src/github.rs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
use semver::Version;
|
||||||
|
|
||||||
|
pub fn latest_tag(owner: &str, repo: &str) -> String {
|
||||||
|
let github_body = crate::http::get_body_string(
|
||||||
|
format!(
|
||||||
|
"https://api.github.com/repos/{}/{}/releases/latest",
|
||||||
|
owner, repo
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
);
|
||||||
|
let github_json: serde_json::Value = serde_json::from_str(&github_body).unwrap();
|
||||||
|
github_json["tag_name"].to_string().replace('"', "")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn latest_version(owner: &str, repo: &str) -> Version {
|
||||||
|
let tag = latest_tag(owner, repo).replace('v', "");
|
||||||
|
Version::parse(&tag).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn latest_release_url(owner: &str, repo: &str) -> String {
|
||||||
|
format!("https://github.com/{}/{}/releases/latest", owner, repo)
|
||||||
|
}
|
5
src/global.rs
Normal file
5
src/global.rs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
pub const MASTER: &str = "https://master.alterware.dev";
|
||||||
|
pub const GH_OWNER: &str = "mxve";
|
||||||
|
pub const GH_REPO: &str = "alterware-launcher";
|
||||||
|
pub const GH_IW4X_OWNER: &str = "iw4x";
|
||||||
|
pub const GH_IW4X_REPO: &str = "iw4x-client";
|
36
src/http.rs
Normal file
36
src/http.rs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
use std::{fs, io::Write, path::Path, str};
|
||||||
|
|
||||||
|
pub fn get_body(url: &str) -> Vec<u8> {
|
||||||
|
let mut res: Vec<u8> = Vec::new();
|
||||||
|
let req = http_req::request::Request::new(&url.try_into().unwrap())
|
||||||
|
.header(
|
||||||
|
"User-Agent",
|
||||||
|
"AlterWare Launcher | github.com/mxve/alterware-launcher",
|
||||||
|
)
|
||||||
|
.send(&mut res)
|
||||||
|
.unwrap_or_else(|error| {
|
||||||
|
panic!("\n\n{}:\n{:?}", "Error", error);
|
||||||
|
});
|
||||||
|
|
||||||
|
if req.status_code() == http_req::response::StatusCode::new(302) {
|
||||||
|
let location = req.headers().get("Location").unwrap().as_str();
|
||||||
|
return get_body(location);
|
||||||
|
}
|
||||||
|
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_body_string(url: &str) -> String {
|
||||||
|
String::from_utf8(get_body(url)).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn download_file(url: &str, file_path: &Path) {
|
||||||
|
let body = get_body(url);
|
||||||
|
|
||||||
|
let mut f = fs::File::create(file_path).unwrap_or_else(|error| {
|
||||||
|
panic!("\n\n{}:\n{:?}", "Error", error);
|
||||||
|
});
|
||||||
|
f.write_all(&body).unwrap_or_else(|error| {
|
||||||
|
panic!("\n\n{}:\n{:?}", "Error", error);
|
||||||
|
});
|
||||||
|
}
|
22
src/io.rs
Normal file
22
src/io.rs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
use std::{fs, path::Path};
|
||||||
|
|
||||||
|
pub fn unzip(zip_path: &Path, out_path: &Path) {
|
||||||
|
let mut archive = zip::ZipArchive::new(fs::File::open(zip_path).unwrap()).unwrap();
|
||||||
|
for i in 0..archive.len() {
|
||||||
|
let mut file = archive.by_index(i).unwrap();
|
||||||
|
let outpath = out_path.join(file.name());
|
||||||
|
|
||||||
|
if (*file.name()).ends_with('/') {
|
||||||
|
fs::create_dir_all(outpath).unwrap();
|
||||||
|
} else {
|
||||||
|
println!("Unpacking {}", file.name());
|
||||||
|
if let Some(p) = outpath.parent() {
|
||||||
|
if !p.exists() {
|
||||||
|
fs::create_dir_all(p).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut outfile = fs::File::create(&outpath).unwrap();
|
||||||
|
std::io::copy(&mut file, &mut outfile).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
42
src/iw4x.rs
Normal file
42
src/iw4x.rs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
use crate::github;
|
||||||
|
use crate::global::*;
|
||||||
|
use crate::http;
|
||||||
|
use crate::misc;
|
||||||
|
|
||||||
|
use colored::*;
|
||||||
|
use std::{fs, path::Path};
|
||||||
|
|
||||||
|
pub fn local_revision(dir: &Path) -> u16 {
|
||||||
|
if let Ok(revision) = fs::read_to_string(dir.join(".iw4xrevision")) {
|
||||||
|
misc::rev_to_int(&revision)
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remote_revision() -> u16 {
|
||||||
|
misc::rev_to_int(&github::latest_tag(GH_IW4X_OWNER, GH_IW4X_REPO))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(dir: &Path) {
|
||||||
|
let remote = remote_revision();
|
||||||
|
let local = local_revision(dir);
|
||||||
|
|
||||||
|
if remote <= local && dir.join("iw4x.dll").exists() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"[{}] {}",
|
||||||
|
"Downloading".bright_yellow(),
|
||||||
|
dir.join("iw4x.dll").display()
|
||||||
|
);
|
||||||
|
http::download_file(
|
||||||
|
&format!(
|
||||||
|
"{}/download/iw4x.dll",
|
||||||
|
github::latest_release_url(GH_IW4X_OWNER, GH_IW4X_REPO)
|
||||||
|
),
|
||||||
|
&dir.join("iw4x.dll"),
|
||||||
|
);
|
||||||
|
fs::write(dir.join(".iw4xrevision"), format!("r{}", remote)).unwrap();
|
||||||
|
}
|
470
src/main.rs
Normal file
470
src/main.rs
Normal file
@ -0,0 +1,470 @@
|
|||||||
|
mod config;
|
||||||
|
mod github;
|
||||||
|
mod global;
|
||||||
|
mod http;
|
||||||
|
mod iw4x;
|
||||||
|
mod misc;
|
||||||
|
mod self_update;
|
||||||
|
mod structs;
|
||||||
|
|
||||||
|
use global::*;
|
||||||
|
use structs::*;
|
||||||
|
|
||||||
|
use colored::*;
|
||||||
|
#[cfg(windows)]
|
||||||
|
use mslnk::ShellLink;
|
||||||
|
use std::{borrow::Cow, collections::HashMap, fs, path::Path, path::PathBuf};
|
||||||
|
#[cfg(windows)]
|
||||||
|
use steamlocate::SteamDir;
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn get_installed_games(games: &Vec<Game>) -> Vec<(u32, PathBuf)> {
|
||||||
|
let mut installed_games = Vec::new();
|
||||||
|
let mut steamdir = match SteamDir::locate() {
|
||||||
|
Some(steamdir) => steamdir,
|
||||||
|
None => {
|
||||||
|
println!("{}", "Steam not found!".yellow());
|
||||||
|
return installed_games;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for game in games {
|
||||||
|
if let Some(app) = steamdir.app(&game.app_id) {
|
||||||
|
installed_games.push((game.app_id, PathBuf::from(&app.path)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
installed_games
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn setup_client_links(game: &Game, game_dir: &Path) {
|
||||||
|
if game.client.len() > 1 {
|
||||||
|
println!("Multiple clients installed, use the shortcuts (launch-<client>.lnk in the game directory or on the desktop) to launch a specific client.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let target = game_dir.join("alterware-launcher.exe");
|
||||||
|
|
||||||
|
for c in game.client.iter() {
|
||||||
|
let lnk = game_dir.join(format!("launch-{}.lnk", c));
|
||||||
|
|
||||||
|
let mut sl = ShellLink::new(target.clone()).unwrap();
|
||||||
|
sl.set_arguments(Some(c.to_string()));
|
||||||
|
sl.set_icon_location(Some(
|
||||||
|
game_dir
|
||||||
|
.join(format!("{}.exe", c))
|
||||||
|
.to_string_lossy()
|
||||||
|
.into_owned(),
|
||||||
|
));
|
||||||
|
sl.create_lnk(&lnk).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn setup_desktop_links(path: &Path, game: &Game) {
|
||||||
|
println!("Create Desktop shortcut? (Y/n)");
|
||||||
|
let input = misc::stdin().to_ascii_lowercase();
|
||||||
|
|
||||||
|
if input == "y" || input.is_empty() {
|
||||||
|
let desktop = PathBuf::from(&format!(
|
||||||
|
"{}\\Desktop",
|
||||||
|
std::env::var("USERPROFILE").unwrap()
|
||||||
|
));
|
||||||
|
|
||||||
|
let target = path.join("alterware-launcher.exe");
|
||||||
|
|
||||||
|
for c in game.client.iter() {
|
||||||
|
let lnk = desktop.join(format!("{}.lnk", c));
|
||||||
|
|
||||||
|
let mut sl = ShellLink::new(target.clone()).unwrap();
|
||||||
|
sl.set_arguments(Some(c.to_string()));
|
||||||
|
sl.set_icon_location(Some(
|
||||||
|
path.join(format!("{}.exe", c))
|
||||||
|
.to_string_lossy()
|
||||||
|
.into_owned(),
|
||||||
|
));
|
||||||
|
sl.create_lnk(lnk).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn auto_install(path: &Path, game: &Game) {
|
||||||
|
setup_client_links(game, path);
|
||||||
|
setup_desktop_links(path, game);
|
||||||
|
update(game, path, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn windows_launcher_install(games: &Vec<Game>) {
|
||||||
|
println!(
|
||||||
|
"{}",
|
||||||
|
"No game specified/found. Checking for installed Steam games..".yellow()
|
||||||
|
);
|
||||||
|
let installed_games = get_installed_games(games);
|
||||||
|
|
||||||
|
if !installed_games.is_empty() {
|
||||||
|
let current_dir = std::env::current_dir().unwrap();
|
||||||
|
for (id, path) in installed_games.iter() {
|
||||||
|
if current_dir.starts_with(path) {
|
||||||
|
println!("Found game in current directory.");
|
||||||
|
println!("Installing AlterWare client for {}.", id);
|
||||||
|
let game = games.iter().find(|&g| g.app_id == *id).unwrap();
|
||||||
|
auto_install(path, game);
|
||||||
|
println!("Installation complete. Please run the launcher again or use a shortcut to launch the game.");
|
||||||
|
std::io::stdin().read_line(&mut String::new()).unwrap();
|
||||||
|
std::process::exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Installed games:");
|
||||||
|
|
||||||
|
for (id, path) in installed_games.iter() {
|
||||||
|
println!("{}: {}", id, path.display());
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Enter the ID of the game you want to install the AlterWare client for, enter 0 for manual selection:");
|
||||||
|
let input: u32 = misc::stdin().parse().unwrap();
|
||||||
|
|
||||||
|
if input == 0 {
|
||||||
|
return manual_install(games);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (id, path) in installed_games.iter() {
|
||||||
|
if *id == input {
|
||||||
|
let game = games.iter().find(|&g| g.app_id == input).unwrap();
|
||||||
|
|
||||||
|
let launcher_path = std::env::current_exe().unwrap();
|
||||||
|
let target_path = path.join("alterware-launcher.exe");
|
||||||
|
|
||||||
|
if launcher_path != target_path {
|
||||||
|
fs::copy(launcher_path, target_path).unwrap();
|
||||||
|
println!("Launcher copied to {}", path.display());
|
||||||
|
}
|
||||||
|
auto_install(path, game);
|
||||||
|
println!("Installation complete. Please run the launcher again or use a shortcut to launch the game.");
|
||||||
|
std::io::stdin().read_line(&mut String::new()).unwrap();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::process::exit(0);
|
||||||
|
} else {
|
||||||
|
manual_install(games);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prompt_client_selection(games: &[Game]) -> String {
|
||||||
|
println!(
|
||||||
|
"Couldn't detect any games, please select a client to install in the current directory:"
|
||||||
|
);
|
||||||
|
for (i, g) in games.iter().enumerate() {
|
||||||
|
for c in g.client.iter() {
|
||||||
|
println!("{}: {}", i, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let input: usize = misc::stdin().parse().unwrap();
|
||||||
|
String::from(games[input].client[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn manual_install(games: &[Game]) {
|
||||||
|
let selection = prompt_client_selection(games);
|
||||||
|
let game = games.iter().find(|&g| g.client[0] == selection).unwrap();
|
||||||
|
update(game, &std::env::current_dir().unwrap(), false, false);
|
||||||
|
println!("Installation complete. Please run the launcher again or use a shortcut to launch the game.");
|
||||||
|
std::io::stdin().read_line(&mut String::new()).unwrap();
|
||||||
|
std::process::exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_dir(
|
||||||
|
cdn_info: &Vec<CdnFile>,
|
||||||
|
remote_dir: &str,
|
||||||
|
dir: &Path,
|
||||||
|
hashes: &mut HashMap<String, String>,
|
||||||
|
) {
|
||||||
|
let remote_dir = format!("{}/", remote_dir);
|
||||||
|
|
||||||
|
for file in cdn_info {
|
||||||
|
if !file.name.starts_with(&remote_dir) || file.name == "iw4/iw4x.dll" {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sha1_remote = file.hash.to_lowercase();
|
||||||
|
let file_name = &file.name.replace(remote_dir.as_str(), "");
|
||||||
|
let file_path = dir.join(file_name);
|
||||||
|
if file_path.exists() {
|
||||||
|
let sha1_local = hashes
|
||||||
|
.get(file_name)
|
||||||
|
.map(Cow::Borrowed)
|
||||||
|
.unwrap_or_else(|| Cow::Owned(misc::get_file_sha1(&file_path)))
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
if sha1_local != sha1_remote {
|
||||||
|
println!(
|
||||||
|
"[{}] {}",
|
||||||
|
"Updating".bright_yellow(),
|
||||||
|
file_path.display()
|
||||||
|
);
|
||||||
|
http::download_file(&format!("{}/{}", MASTER, file.name), &file_path);
|
||||||
|
} else {
|
||||||
|
println!("[{}] {}", "Checked".bright_blue(), file_path.display());
|
||||||
|
}
|
||||||
|
hashes.insert(file_name.to_owned(), sha1_remote.to_owned());
|
||||||
|
} else {
|
||||||
|
println!(
|
||||||
|
"[{}] {}",
|
||||||
|
"Downloading".bright_yellow(),
|
||||||
|
file_path.display()
|
||||||
|
);
|
||||||
|
if let Some(parent) = file_path.parent() {
|
||||||
|
if !parent.exists() {
|
||||||
|
fs::create_dir_all(parent).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
http::download_file(&format!("{}/{}", MASTER, file.name), &file_path);
|
||||||
|
hashes.insert(file_name.to_owned(), sha1_remote.to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(game: &Game, dir: &Path, bonus_content: bool, force: bool) {
|
||||||
|
let cdn_info: Vec<CdnFile> = serde_json::from_str(&http::get_body_string(
|
||||||
|
format!("{}/files.json", MASTER).as_str(),
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut hashes = HashMap::new();
|
||||||
|
let hash_file = dir.join(".sha-sums");
|
||||||
|
if hash_file.exists() && !force {
|
||||||
|
let hash_file = fs::read_to_string(hash_file).unwrap();
|
||||||
|
for line in hash_file.lines() {
|
||||||
|
let mut split = line.split_whitespace();
|
||||||
|
let hash = split.next().unwrap();
|
||||||
|
let file = split.next().unwrap();
|
||||||
|
hashes.insert(file.to_owned(), hash.to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
update_dir(&cdn_info, game.engine, dir, &mut hashes);
|
||||||
|
|
||||||
|
if game.engine == "iw4" {
|
||||||
|
iw4x::update(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
if bonus_content && !game.bonus.is_empty() {
|
||||||
|
for bonus in game.bonus.iter() {
|
||||||
|
update_dir(&cdn_info, bonus, dir, &mut hashes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut hash_file_content = String::new();
|
||||||
|
for (file, hash) in hashes.iter() {
|
||||||
|
hash_file_content.push_str(&format!("{} {}\n", hash, file));
|
||||||
|
}
|
||||||
|
fs::write(dir.join(".sha-sums"), hash_file_content).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn launch(file_path: &PathBuf, args: &str) {
|
||||||
|
println!("Launching {} {}", file_path.display(), args);
|
||||||
|
std::process::Command::new(file_path)
|
||||||
|
.args(args.trim().split(' '))
|
||||||
|
.current_dir(file_path.parent().unwrap())
|
||||||
|
.spawn()
|
||||||
|
.expect("Failed to launch the game")
|
||||||
|
.wait()
|
||||||
|
.expect("Failed to wait for the game process to finish");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn setup_env() {
|
||||||
|
colored::control::set_virtual_terminal(true).unwrap_or_else(|error| {
|
||||||
|
println!("{:#?}", error);
|
||||||
|
colored::control::SHOULD_COLORIZE.set_override(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn arg_value(args: &[String], arg: &str) -> Option<String> {
|
||||||
|
args.iter()
|
||||||
|
.position(|r| r == arg)
|
||||||
|
.map(|e| args[e + 1].clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn arg_bool(args: &[String], arg: &str) -> bool {
|
||||||
|
args.iter().any(|r| r == arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn arg_remove(args: &mut Vec<String>, arg: &str) {
|
||||||
|
args.iter().position(|r| r == arg).map(|e| args.remove(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn arg_remove_value(args: &mut Vec<String>, arg: &str) {
|
||||||
|
if let Some(e) = args.iter().position(|r| r == arg) {
|
||||||
|
args.remove(e);
|
||||||
|
args.remove(e);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
#[cfg(windows)]
|
||||||
|
setup_env();
|
||||||
|
|
||||||
|
let mut args: Vec<String> = std::env::args().collect();
|
||||||
|
|
||||||
|
if arg_bool(&args, "--help") {
|
||||||
|
println!("CLI Args:");
|
||||||
|
println!(" <client>: Specify the client to launch");
|
||||||
|
println!(" --help: Display this help message");
|
||||||
|
println!(" --version: Display the launcher version");
|
||||||
|
println!(" --path/-p <path>: Specify the game directory");
|
||||||
|
println!(" --update/-u: Update only, don't launch the game");
|
||||||
|
println!(" --bonus: Download bonus content");
|
||||||
|
println!(" --force/-f: Force file hash recheck");
|
||||||
|
println!(" --pass <args>: Pass arguments to the game");
|
||||||
|
println!(" --skip-launcher-update: Skip launcher self-update");
|
||||||
|
println!(
|
||||||
|
"\nExample:\n alterware-launcher.exe iw4x --bonus --pass \"-console -nointro\""
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if arg_bool(&args, "--version") || arg_bool(&args, "-v") {
|
||||||
|
println!(
|
||||||
|
"{} v{}",
|
||||||
|
"AlterWare Launcher".bright_green(),
|
||||||
|
env!("CARGO_PKG_VERSION")
|
||||||
|
);
|
||||||
|
println!("https://github.com/{}/{}", GH_OWNER, GH_REPO);
|
||||||
|
println!(
|
||||||
|
"\n{}{}{}{}{}{}{}",
|
||||||
|
"For ".on_black(),
|
||||||
|
"Alter".bright_blue().on_black().underline(),
|
||||||
|
"Ware".white().on_black().underline(),
|
||||||
|
".dev".on_black().underline(),
|
||||||
|
" by ".on_black(),
|
||||||
|
"mxve".bright_magenta().on_black().underline(),
|
||||||
|
".de".on_black().underline()
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let install_path: PathBuf;
|
||||||
|
if let Some(path) = arg_value(&args, "--path") {
|
||||||
|
install_path = PathBuf::from(path);
|
||||||
|
arg_remove_value(&mut args, "--path");
|
||||||
|
} else if let Some(path) = arg_value(&args, "-p") {
|
||||||
|
install_path = PathBuf::from(path);
|
||||||
|
arg_remove_value(&mut args, "-p");
|
||||||
|
} else {
|
||||||
|
install_path = std::env::current_dir().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut cfg = config::load(install_path.join("alterware-launcher.json"));
|
||||||
|
|
||||||
|
if !arg_bool(&args, "--skip-launcher-update") && !cfg.skip_self_update {
|
||||||
|
self_update::run(cfg.update_only);
|
||||||
|
} else {
|
||||||
|
arg_remove(&mut args, "--skip-launcher-update");
|
||||||
|
}
|
||||||
|
|
||||||
|
if arg_bool(&args, "--update") || arg_bool(&args, "-u") {
|
||||||
|
cfg.update_only = true;
|
||||||
|
arg_remove(&mut args, "--update");
|
||||||
|
arg_remove(&mut args, "-u");
|
||||||
|
}
|
||||||
|
|
||||||
|
if arg_bool(&args, "--bonus") {
|
||||||
|
cfg.download_bonus_content = true;
|
||||||
|
arg_remove(&mut args, "--bonus");
|
||||||
|
}
|
||||||
|
|
||||||
|
if arg_bool(&args, "--force") || arg_bool(&args, "-f") {
|
||||||
|
cfg.force_update = true;
|
||||||
|
arg_remove(&mut args, "--force");
|
||||||
|
arg_remove(&mut args, "-f");
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(pass) = arg_value(&args, "--pass") {
|
||||||
|
cfg.args = pass;
|
||||||
|
arg_remove_value(&mut args, "--pass");
|
||||||
|
} else if cfg.args.is_empty() {
|
||||||
|
cfg.args = String::from("");
|
||||||
|
}
|
||||||
|
|
||||||
|
let games_json = http::get_body_string(format!("{}/games.json", MASTER).as_str());
|
||||||
|
let games: Vec<Game> = serde_json::from_str(&games_json).unwrap();
|
||||||
|
|
||||||
|
let mut game: String = String::new();
|
||||||
|
if args.len() > 1 {
|
||||||
|
game = String::from(&args[1]);
|
||||||
|
} else {
|
||||||
|
'main: for g in games.iter() {
|
||||||
|
for r in g.references.iter() {
|
||||||
|
if install_path.join(r).exists() {
|
||||||
|
if g.client.len() > 1 {
|
||||||
|
if cfg.update_only {
|
||||||
|
game = String::from(g.client[0]);
|
||||||
|
break 'main;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
setup_client_links(g, &std::env::current_dir().unwrap());
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
println!("Multiple clients installed, set the client as the first argument to launch a specific client.");
|
||||||
|
println!("Select a client to launch:");
|
||||||
|
for (i, c) in g.client.iter().enumerate() {
|
||||||
|
println!("{}: {}", i, c);
|
||||||
|
}
|
||||||
|
game = String::from(g.client[misc::stdin().parse::<usize>().unwrap()]);
|
||||||
|
break 'main;
|
||||||
|
}
|
||||||
|
game = String::from(g.client[0]);
|
||||||
|
break 'main;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for g in games.iter() {
|
||||||
|
for c in g.client.iter() {
|
||||||
|
if c == &game {
|
||||||
|
if cfg.ask_bonus_content && !g.bonus.is_empty() {
|
||||||
|
println!("Download bonus content? (Y/n)");
|
||||||
|
let input = misc::stdin().to_ascii_lowercase();
|
||||||
|
cfg.download_bonus_content = input != "n";
|
||||||
|
config::save_value(
|
||||||
|
install_path.join("alterware-launcher.json"),
|
||||||
|
"download_bonus_content",
|
||||||
|
cfg.download_bonus_content,
|
||||||
|
);
|
||||||
|
config::save_value(
|
||||||
|
install_path.join("alterware-launcher.json"),
|
||||||
|
"ask_bonus_content",
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
update(
|
||||||
|
g,
|
||||||
|
install_path.as_path(),
|
||||||
|
cfg.download_bonus_content,
|
||||||
|
cfg.force_update,
|
||||||
|
);
|
||||||
|
if !cfg.update_only {
|
||||||
|
launch(&install_path.join(format!("{}.exe", c)), &cfg.args);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
windows_launcher_install(&games);
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
manual_install(&games);
|
||||||
|
|
||||||
|
println!("{}", "Game not found!".bright_red());
|
||||||
|
println!("Place the launcher in the game folder, if that doesn't work specify the client on the command line (ex. alterware-launcher.exe iw4-sp)");
|
||||||
|
println!("Press enter to exit...");
|
||||||
|
std::io::stdin().read_line(&mut String::new()).unwrap();
|
||||||
|
}
|
20
src/misc.rs
Normal file
20
src/misc.rs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
use std::{fs, path::PathBuf};
|
||||||
|
|
||||||
|
pub fn get_file_sha1(path: &PathBuf) -> String {
|
||||||
|
let mut sha1 = sha1_smol::Sha1::new();
|
||||||
|
sha1.update(&fs::read(path).unwrap());
|
||||||
|
sha1.digest().to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stdin() -> String {
|
||||||
|
let mut input = String::new();
|
||||||
|
std::io::stdin().read_line(&mut input).unwrap();
|
||||||
|
input.trim().to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rev_to_int(rev: &str) -> u16 {
|
||||||
|
rev.strip_prefix('r')
|
||||||
|
.unwrap_or("0")
|
||||||
|
.parse::<u16>()
|
||||||
|
.unwrap_or(0)
|
||||||
|
}
|
84
src/self_update.rs
Normal file
84
src/self_update.rs
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
use crate::github;
|
||||||
|
use crate::global::*;
|
||||||
|
|
||||||
|
use semver::Version;
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
use std::{thread, time};
|
||||||
|
|
||||||
|
pub fn self_update_available() -> bool {
|
||||||
|
let current_version: Version = Version::parse(env!("CARGO_PKG_VERSION")).unwrap();
|
||||||
|
let latest_version = github::latest_version(GH_OWNER, GH_REPO);
|
||||||
|
|
||||||
|
current_version < latest_version
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
pub fn run(_update_only: bool) {
|
||||||
|
if self_update_available() {
|
||||||
|
println!("A new version of the AlterWare launcher is available.");
|
||||||
|
println!(
|
||||||
|
"Download it at {}",
|
||||||
|
github::latest_release_url(GH_OWNER, GH_REPO)
|
||||||
|
);
|
||||||
|
println!("Launching in 10 seconds..");
|
||||||
|
thread::sleep(time::Duration::from_secs(10));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub fn run(update_only: bool) {
|
||||||
|
use std::{fs, path::PathBuf};
|
||||||
|
|
||||||
|
use crate::http;
|
||||||
|
|
||||||
|
let working_dir = std::env::current_dir().unwrap();
|
||||||
|
let files = fs::read_dir(&working_dir).unwrap();
|
||||||
|
|
||||||
|
for file in files {
|
||||||
|
let file = file.unwrap();
|
||||||
|
let file_name = file.file_name().into_string().unwrap();
|
||||||
|
|
||||||
|
if file_name.contains("alterware-launcher")
|
||||||
|
&& (file_name.contains(".__relocated__.exe")
|
||||||
|
|| file_name.contains(".__selfdelete__.exe"))
|
||||||
|
{
|
||||||
|
fs::remove_file(file.path()).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self_update_available() {
|
||||||
|
println!("Performing launcher self-update.");
|
||||||
|
println!(
|
||||||
|
"If you run into any issues, please download the latest version at {}",
|
||||||
|
github::latest_release_url(GH_OWNER, GH_REPO)
|
||||||
|
);
|
||||||
|
|
||||||
|
let update_binary = PathBuf::from("alterware-launcher-update.exe");
|
||||||
|
let file_path = working_dir.join(&update_binary);
|
||||||
|
|
||||||
|
if update_binary.exists() {
|
||||||
|
fs::remove_file(&update_binary).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
http::download_file(
|
||||||
|
&format!(
|
||||||
|
"{}/download/alterware-launcher.exe",
|
||||||
|
github::latest_release_url(GH_OWNER, GH_REPO)
|
||||||
|
),
|
||||||
|
&file_path,
|
||||||
|
);
|
||||||
|
|
||||||
|
if !file_path.exists() {
|
||||||
|
println!("Failed to download launcher update.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self_replace::self_replace("alterware-launcher-update.exe").unwrap();
|
||||||
|
fs::remove_file(&file_path).unwrap();
|
||||||
|
println!("Launcher updated. Please run it again.");
|
||||||
|
if !update_only {
|
||||||
|
std::io::stdin().read_line(&mut String::new()).unwrap();
|
||||||
|
}
|
||||||
|
std::process::exit(201);
|
||||||
|
}
|
||||||
|
}
|
38
src/structs.rs
Normal file
38
src/structs.rs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||||||
|
pub struct CdnFile {
|
||||||
|
pub name: String,
|
||||||
|
pub size: u32,
|
||||||
|
pub hash: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||||||
|
pub struct Game<'a> {
|
||||||
|
pub engine: &'a str,
|
||||||
|
pub client: Vec<&'a str>,
|
||||||
|
pub references: Vec<&'a str>,
|
||||||
|
pub app_id: u32,
|
||||||
|
pub bonus: Vec<&'a str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||||||
|
pub struct Config {
|
||||||
|
pub update_only: bool,
|
||||||
|
pub skip_self_update: bool,
|
||||||
|
pub download_bonus_content: bool,
|
||||||
|
pub ask_bonus_content: bool,
|
||||||
|
pub force_update: bool,
|
||||||
|
pub args: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Config {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
update_only: false,
|
||||||
|
skip_self_update: false,
|
||||||
|
download_bonus_content: false,
|
||||||
|
ask_bonus_content: true,
|
||||||
|
force_update: false,
|
||||||
|
args: String::from(""),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user