diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a904141..3949aaf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,6 +4,10 @@ on: push: tags: - "v[0-9]+.*" + workflow_dispatch: + inputs: + tags: + description: 'Tags' jobs: prepare: @@ -92,13 +96,14 @@ jobs: pip3 install cargo-zigbuild - name: Build - run: cargo zigbuild --target ${{ matrix.target }} --package lovely-mac --release + run: cargo zigbuild --target ${{ matrix.target }} --package lovely-unix --release - name: Compress tar.gz run: | - cp ./crates/lovely-mac/run_lovely.sh ./target/${{ matrix.target }}/release/ + cp ./crates/lovely-unix/run_lovely_macos.sh ./target/${{ matrix.target }}/release/run_lovely.sh + cp -R ./crates/lovely-unix/resources-mac ./target/${{ matrix.target }}/release/Resources cd ./target/${{ matrix.target }}/release/ - tar czfv lovely-${{ matrix.target }}.tar.gz liblovely.dylib run_lovely.sh + tar czfv lovely-${{ matrix.target }}.tar.gz liblovely.dylib run_lovely.sh Resources mv "lovely-${{ matrix.target }}.tar.gz" ${{ github.workspace }} - name: Submit build artifact diff --git a/README.md b/README.md index f2d1a36..b34a9db 100644 --- a/README.md +++ b/README.md @@ -14,13 +14,27 @@ Lovely is a lua injector which embeds code into a [LÖVE 2d](https://love2d.org/ ### Mac -1. Download the [latest release](https://github.com/ethangreen-dev/lovely-injector/releases) for Mac. If you have an M-series CPU (M1, M2, etc.) then this will be `lovely-aarch64-apple-darwin.tar.gz`. If you have an Intel CPU then it will be `lovely-x86_64-apple-darwin.tar.gz` -2. Open the .zip archive, copy `liblovely.dylib` and `run_lovely.sh` into the game directory. You can navigate to the game's directory by right-clicking the game in Steam, hovering "Manage", and selecting "Browse local files". -3. Put one or more mods into the Mac mod directory (NOT the same as the game directory). This should be `/Users/$USER/Library/Application Support/Balatro/Mods` where `$USER` is your username (if you are modding Balatro).\ -If you can't find this folder, try pressing `Shift-Command-.` (period) to show hidden files in Finder. -4. Run the game by either dragging and dropping `run_lovely.sh` onto `Terminal.app` in Applications > Utilities and then pressing enter, or by executing `sh run_lovely.sh` in the terminal within the game directory. - -Note: You cannot run your game through Steam on Mac due to a bug within the Steam client. You must run it with the `run_lovely.sh` script. +1. Put your desired mods into the mod directory for your game. NOTE: this is **not** the same folder as the game directory. + - For Balatro, this should be in `~/Library/Application Support/Balatro/Mods`. + - To navigate to this folder directly, open Finder and press `⇧ Shift + ⌘ Command + G`. Then paste `~/Library/Application Support/Balatro` and press `Enter`. Then, double-click on (or create, if it does not exist) the folder `Mods`. +2. Download the [latest release](https://github.com/ethangreen-dev/lovely-injector/releases) of Lovely Injector for Mac. + - If you have an M-series CPU (M1, M2, etc.) then this will be `lovely-aarch64-apple-darwin.tar.gz`. + - If you have an Intel CPU then it will be `lovely-x86_64-apple-darwin.tar.gz`. +3. Extract the `.tar.gz` archive to a folder of your chosing and open it. NOTE: Preferably the archive will be extracted **outside** of any game directories, in a neutral place, such as `Downloads`. +4. Run `run_lovely.sh` and, following directions, select the option to either... + - *Run a game with the injector, directly*, one time, or... + - *Create a modded application bundle*, which will allow you to run and inject Lovely automatically, using a convenient shortcut in Applications. + +#### Notes for Steam Games: + +- When asked for the `Path to Application`, you can find this by navigating to the game's directory in Finder using Steam: + 1. Open your Steam library + 2. Right-click the game in the left-hand sidebar + 3. Select "Manage" from the context-menu + 4. Select "Browse local files" from the window that appears. + 5. Drag-and-drop the `.app` in the folder into the terminal window. E.G `Balatro.app` +- You cannot run your game through Steam directly on Mac due to a limitation of macOS applications. You must run it with the `run_lovely.sh` script, or alternatively, using a modded application bundle created with `run_lovely.sh`, which serves as a direct launcher for the modded version of the game. + - You can, however, add modded application bundles to your Steam library as a "Non-Steam Game" from the `Game` menu of the Steam client. **Important**: Mods with Lovely patch files (`lovely.toml` or in `lovely/*.toml`) **must** be installed into their own folder within the mod directory. No exceptions! diff --git a/crates/lovely-unix/resources-mac/Icons/com.Balatro.localthunk.icns b/crates/lovely-unix/resources-mac/Icons/com.Balatro.localthunk.icns new file mode 100644 index 0000000..1c4436e Binary files /dev/null and b/crates/lovely-unix/resources-mac/Icons/com.Balatro.localthunk.icns differ diff --git a/crates/lovely-unix/resources-mac/Info.plist b/crates/lovely-unix/resources-mac/Info.plist new file mode 100644 index 0000000..dbbfcdb --- /dev/null +++ b/crates/lovely-unix/resources-mac/Info.plist @@ -0,0 +1,38 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + run_lovely + CFBundleIconFile + application.icns + CFBundleIdentifier + + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1.0 + CFBundleSignature + + LSApplicationCategoryType + public.app-category.games + LSMinimumSystemVersion + 10.7 + NSHighResolutionCapable + + NSHumanReadableCopyright + + NSPrincipalClass + NSApplication + NSSupportsAutomaticGraphicsSwitching + + + \ No newline at end of file diff --git a/crates/lovely-unix/run_lovely_macos.sh b/crates/lovely-unix/run_lovely_macos.sh index cfd3a96..da9d9f1 100755 --- a/crates/lovely-unix/run_lovely_macos.sh +++ b/crates/lovely-unix/run_lovely_macos.sh @@ -1,8 +1,409 @@ -#!/bin/bash -gamename="Balatro" -defaultpath="/Users/$USER/Library/Application Support/Steam/steamapps/common/$gamename" +#! /bin/bash +shopt -s extglob -export DYLD_INSERT_LIBRARIES=liblovely.dylib +# GLOBAL VARIABLES + # Paths + workingPath=$(cd "$(dirname "$0")"; pwd) + liblovelyPath="${workingPath}/liblovely.dylib" + resourcePath="${workingPath}/Resources" + userAppsPath="/Users/${USER}/Applications" + propertyFile="${resourcePath}/Info.plist" + bundlePath="" + binaryPath="" + iconPath="" + # App Bundle Information + appInfo="" # ${bundlePath}/Contents/Info.plist + appName="" # CFBundleName + appNameSuffix="+ Mods" + appID="" # CFBundleIdentifier + appIDSuffix=".modded" + appIcon="" # CFBundleIconFile + appSignature="" # CFBundleSignature + appBinary="" # CFBundleExecutable + appCopyright="" # NSHumanReadableCopyright + appGraphicsSwitching="" # NSSupportsAutomaticGraphicsSwitching + appRegion="" # CFBundleDevelopmentRegion + appRetina="" # NSHighResolutionCapable + appTarget="" # LSMinimumSystemVersion + # Options + useCustomIcon=false + useDefaultIcon=true + overwriteBundle=false + # Other + binaryName="" -cd "$defaultpath" -./$gamename.app/Contents/MacOS/love "$@" +# Main Menu Loop +menuOption() { + read -n 1 -s method + case $method in + 1) + # User wishes to inject lovely and run once + injectBundle + ;; + 2) + # User wishes to build a bundle to repeatedly inject and run lovely + buildBundle + ;; + 3) + # User wishes to exit + exit + ;; + *) + # Invalid input; Loop around + echo "Invalid option. Please try again." + echo + menuOption + esac +} + +# Get source application path +getApplication() { + echo + echo "Please drag-and-drop the .app bundle to be modded, or enter its location below, then press enter." + echo + read -p "Path to Application: " inputPath + echo + if [[ -d "${inputPath}" ]]; then + if [[ $inputPath == *.[Aa][Pp][Pp] ]]; then + + # Set path information + bundlePath="${inputPath}" + appInfo="${bundlePath}/Contents/Info.plist" + binaryName=$(defaults read "${appInfo}" CFBundleExecutable) + appBinary=$(basename "$bundlePath")"/Contents/MacOS/${binaryName}" + else + # File is not an app bundle + echo + echo "The provided input is not a valid macOS application bundle." + bundlePath="" + return + fi + else + # File not found + echo + echo "The provided file could not be found. Please check the path and try agian." + bundlePath="" + return + fi +} + +# Run & inject without building +injectBundle() { + getApplication + + # Return in the event no source bundle was set. + if [[ bundlePath == "" ]]; then + return + fi + + export DYLD_INSERT_LIBRARIES=liblovely.dylib + echo + eval "${bundlePath// /\\ }/Contents/MacOS/${binaryName} "'"@"' + clearVariables +} + +# Create bundle launcher +buildBundle() { + getApplication + + # Return in the event no source bundle was set. + if [[ bundlePath == "" ]]; then + return + fi + + if [[ -f "${propertyFile}.original" ]]; then + # If a backup property file exists, assume script exited in a failed state previously, and restore backup + cp -f "${propertyFile}.original" "${propertyFile}" + else + # Create a backup property file + cp -f "${propertyFile}" "${propertyFile}.original" + fi + + echo + echo "Setting bundle information..." + + # Use 2> /dev/null to silence errors and make checks easier to script + + # Bundle Name + appName=$(defaults read "${appInfo}" CFBundleName 2> /dev/null) + if [[ $appName == "" ]]; then + appName="LÖVE 2d Application" + fi + defaults write "${propertyFile}" CFBundleName "${appName}" + + # Bundle Identifier + appID=$(defaults read "${appInfo}" CFBundleIdentifier 2> /dev/null) + if [[ $appID == "" ]]; then + echo "Error: Bundle identifier missing. (CFBundleIdentifier) Are you sure this is a valid application?" + exitScript + fi + defaults write "${propertyFile}" CFBundleIdentifier "${appID}${appIDSuffix}" + + # Ask for user icon path with read and set appIcon to it + askForIcon + + # If a custom icon is not in use... + if [[ $useCustomIcon == false ]]; then + if [[ -f "${resourcePath}/Icons/${appID}.icns" ]]; then + # ...Use tailored icon if it is available. This may be extended or removed depending on scope of the project in the future. + useDefaultIcon=false + appIcon="${appID}.icns" + iconPath="${resourcePath}/Icons/${appIcon}" + elif ! [[ $appID == "" ]]; then + # ...Use the original app's icon + appIcon=$(defaults read "${appInfo}" CFBundleIconFile 2> /dev/null) + iconPath="${bundlePath}/Contents/Resources/${appIcon}" + else + # Well... that happened. + echo + echo "An unknown error has occured..." + exitScript + fi + fi + + # Use icon if present or remove icon key + if ! [[ $appIcon == "" ]]; then + defaults write "${propertyFile}" CFBundleIconFile "${appIcon}" + else + defaults write "${propertyFile}" CFBundleIconFile "" + fi + + # Bundle Signature + appSignature=$(defaults read "${appInfo}" CFBundleSignature 2> /dev/null) + if [[ $appSignature == "" ]]; then + appSignature="????" + fi + defaults write "${propertyFile}" CFBundleSignature "${appSignature}" + + # Bundle Copyright + appCopyright=$(defaults read "${appInfo}" NSHumanReadableCopyright 2> /dev/null) + if ! [[ $appCopyright == "" ]]; then + defaults write "${propertyFile}" NSHumanReadableCopyright "© ${appCopyright//\\+([0-9]) /}" + else + defaults delete "${propertyFile}" NSHumanReadableCopyright + fi + + # Bundle Graphics Switching + appGraphicsSwitching=$(defaults read "${appInfo}" NSSupportsAutomaticGraphicsSwitching 2> /dev/null) + if ! [[ $appGraphicsSwitching == "" ]]; then + case $appGraphicsSwitching in + 0) + defaults write "${propertyFile}" NSSupportsAutomaticGraphicsSwitching -boolean FALSE + ;; + 1) + defaults write "${propertyFile}" NSSupportsAutomaticGraphicsSwitching -boolean TRUE + ;; + esac + fi + + # Bundle Language + appRegion=$(defaults read "${appInfo}" CFBundleDevelopmentRegion 2> /dev/null) + if ! [[ $appRegion == "" ]]; then + defaults write "${propertyFile}" CFBundleDevelopmentRegion "${appRegion}" + fi + + # Bundle Retina Capable + appRetina=$(defaults read "${appInfo}" NSHighResolutionCapable 2> /dev/null) + if ! [[ $appRetina == "" ]]; then + case $appRetina in + 0) + defaults write "${propertyFile}" NSHighResolutionCapable -boolean FALSE + ;; + 1) + defaults write "${propertyFile}" NSHighResolutionCapable -boolean TRUE + ;; + esac + fi + + # Target macOS + appTarget=$(defaults read "${appInfo}" LSMinimumSystemVersion 2> /dev/null) + if ! [[ $appTarget == "" ]]; then + defaults write "${propertyFile}" LSMinimumSystemVersion "${appTarget}" + fi + + # Ensure PLIST is XML format + plutil -convert xml1 "${propertyFile}" + + echo + echo "Generating bundle..." + local moddedPath="${userAppsPath}/${appName} ${appNameSuffix}.app" + local contentPath="${moddedPath}/Contents" + + # Handle existing launcher bundle + if [[ -d "${moddedPath}" ]]; then + askForOverwrite + if [[ $overwriteBundle == false ]]; then + return + else + rm -rf "${moddedPath}" + fi + fi + + # Create bundle structure + mkdir -p "${contentPath}/MacOS" + mkdir -p "${contentPath}/Resources" + + echo + echo "Copying assets..." + # Copy bundle assets + ln -s "${bundlePath}" "${contentPath}/MacOS/" + cp "${propertyFile}" "${contentPath}/" + cp "${liblovelyPath}" "${contentPath}/MacOS/" + if ! [[ $iconPath == "" ]]; then + cp "${iconPath}" "${contentPath}/Resources/" + fi + + echo + echo "Building script..." + # Use echo to write out script contents + echo '#! /bin/bash' > "${contentPath}/MacOS/run_lovely" + echo 'workingpath=$(cd "$(dirname "$0")"; pwd)' >> "${contentPath}/MacOS/run_lovely" + echo 'export DYLD_INSERT_LIBRARIES=liblovely.dylib' >> "${contentPath}/MacOS/run_lovely" + echo 'cd "${workingpath}"' >> "${contentPath}/MacOS/run_lovely" + echo "./${appBinary} "'"$@"' >> "${contentPath}/MacOS/run_lovely" + + # Ensure script is owned by user and executable + chown -R -P $USER "${contentPath}" > /dev/null + chmod +x "${contentPath}/MacOS/run_lovely" + + echo + echo "Signing application..." + # Return .app extension to bundle before signing + # mv -f "${moddedPath//\.app/}" "${moddedPath}" + codesign -s - --deep "${moddedPath}/Contents/MacOS/run_lovely" + + # Remove duplicate property file + cp -f "${propertyFile}.original" "${propertyFile}" + rm -f "${propertyFile}.original" + + echo + read -n 1 -p "Installation complete! Press any key to return to menu..." -s key + open "${userAppsPath}" + clearVariables + return +} + +# Get ICNS File Loop +askForIcon() { + echo + read -p "Use custom icon file? Please say [Y] for Yes, or [N] for No." -n 1 -s key + case $key in + [Yy]) + # Do Nothing and continue + resume + ;; + [Nn]) + # Early return + echo + return 0 + ;; + *) + # Loop back and ask again + echo + echo "Invalid input received." + askForIcon + ;; + esac + + echo + echo "Please drag-and-drop the icon file to be used, or enter its location below, then press enter." + echo + read -p "Path to ICNS: " iconPath + if [[ -f "${iconPath}" ]] && [[ $iconPath == *.[Ii][Cc][Nn][Ss] ]]; then + appIcon="$(basename $iconPath)" + useCustomIcon=true + return + else + echo + echo "Invalid input received. Please make sure to input the path to a valid macOS Icon Format file (.icns)..." + echo + echo "Continuing with standard icon..." + return + fi +} + +# Bundle Overwrite Message Loop +askForOverwrite() { + echo + read -p "Modded bundle already found. Overwrite? (Y/N)" -n 1 -s overwrite + case $overwrite in + [Yy]) + overwriteBundle=true + echo + return + ;; + [Nn]) + echo + echo "Returning to menu..." + return + ;; + *) + echo "Invalid option. Please press [Y] for Yes, or [N] for No." + askForOverwrite + ;; + esac +} + +# Script exit command +exitScript() { + echo + read -p "Press any key to exit..." -n 1 -s + exit +} + +resume() { + # Do nothing command where needed; Used mostly as placeholder or for debugging. + echo > /dev/null +} + +# Set current path to the working directory of this script +cd "${workingPath}" + +# Erases dynamic variables for next pass +clearVariables() { + appInfo="" + appName="" + appID="" + appIcon="" + appSignature="" + appBinary="" + appCopyright="" + appGraphicsSwitching="" + appRegion="" + appRetina="" + appTarget="" + + bundlePath="" + binaryPath="" + iconPath="" + + useCustomIcon=false + useDefaultIcon=true + overwriteBundle=false + + binaryName="" +} + +# Main loop +while [ true ]; do + echo + echo "╔═════════════════════════════════════════════════════╗" + echo "║ ♡ Lovely Injector by Ethan Green ♡ ║" + echo "╠═════════════════════════════════════════════════════╣" + echo "║ ║" + echo "║ MAIN MENU : Please select an option... ║" + echo "║ ║" + echo "╠═════════════════════════════════════════════════════╣" + echo "║ ║" + echo "║ 1) Inject Lovely into application only & run once ║" + echo "║ ║" + echo "║ 2) Build modded application bundle for repeated use ║" + echo "║ ║" + echo "║ 3) Exit ║" + echo "║ ║" + echo "╚═════════════════════════════════════════════════════╝" + echo + + # Menu Input Loop + menuOption +done \ No newline at end of file