From 4ebd4d2c0d1dba36c4b4083110aec77c31b376b4 Mon Sep 17 00:00:00 2001 From: faroukbmiled Date: Fri, 3 Apr 2026 08:59:13 +0100 Subject: [PATCH] - Renamed project from SCInsta to RyukGram across all user-facing text, settings UI, build scripts, workflows, control file, and Makefile - Added built-in sideload compatibility patch: keychain access group discovery, SecItem rebinding via fishhook, NSFileManager app group fallback, Cloud Kit entitlement patches - Added fishhook library (modules/fishhook/) for C function rebinding - Updated README with new features, repo links, and credits - Updated GitHub Actions workflows for RyukGram naming - Plist renamed from SCInsta.plist to RyukGram.plist --- .github/workflows/buildapp.yml | 39 ++-- .github/workflows/buildtweak.yml | 29 ++- Makefile | 4 +- README.md | 67 +++--- SCInsta.plist => RyukGram.plist | 0 build.sh | 37 ++-- control | 14 +- modules/fishhook/fishhook.c | 264 +++++++++++++++++++++++ modules/fishhook/fishhook.h | 76 +++++++ src/Features/Media/MediaDownload.xm | 2 +- src/Settings/SCISettingsViewController.m | 4 +- src/Settings/TweakSettings.m | 14 +- src/SideloadPatch/SideloadPatch.xm | 216 +++++++++++++++++++ src/Tweak.x | 2 +- 14 files changed, 656 insertions(+), 112 deletions(-) rename SCInsta.plist => RyukGram.plist (100%) create mode 100644 modules/fishhook/fishhook.c create mode 100644 modules/fishhook/fishhook.h create mode 100644 src/SideloadPatch/SideloadPatch.xm diff --git a/.github/workflows/buildapp.yml b/.github/workflows/buildapp.yml index 56e583e..d2b8df4 100644 --- a/.github/workflows/buildapp.yml +++ b/.github/workflows/buildapp.yml @@ -3,7 +3,7 @@ # https://github.com/ISnackable/YTCubePlus/blob/main/.github/workflows/Build.yml # https://github.com/BandarHL/BHTwitter/actions/workflows/build.yml -name: Build and Package SCInsta +name: Build and Package RyukGram on: workflow_dispatch: @@ -18,13 +18,13 @@ on: default: true required: false type: boolean - + concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: build: - name: Build SCInsta + name: Build RyukGram runs-on: macos-latest permissions: contents: write @@ -40,7 +40,7 @@ jobs: run: brew install ldid dpkg make - name: Set PATH environment variable - run: echo "$(brew --prefix make)/libexec/gnubin" >> $GITHUB_PATH + run: echo "$(brew --prefix make)/libexec/gnubin" >> $GITHUB_PATH - name: Setup Theos uses: actions/checkout@v4 @@ -49,7 +49,7 @@ jobs: ref: master path: ${{ github.workspace }}/theos submodules: recursive - + - name: SDK Caching id: SDK uses: actions/cache@v4 @@ -74,28 +74,24 @@ jobs: - name: Prepare Instagram IPA run: | cd main - mkdir -p packages wget "$Instagram_URL" --no-verbose -O packages/com.burbn.instagram.ipa - ls -la packages - env: THEOS: ${{ github.workspace }}/theos Instagram_URL: ${{ inputs.decrypted_instagram_url }} - - name: Get SCInsta Version - id: scinsta_version + - name: Get Version + id: version run: | - SCINSTA_VERSION=$(awk '/Version:/ {print $2}' main/control) + VERSION=$(awk '/Version:/ {print $2}' main/control) + echo "VERSION=${VERSION}" >> "$GITHUB_ENV" + echo "version=${VERSION}" >> "$GITHUB_OUTPUT" - echo "SCINSTA_VERSION=${SCINSTA_VERSION}" >> "$GITHUB_ENV" - echo "version=${SCINSTA_VERSION}" >> "$GITHUB_OUTPUT" - - - name: Build SCInsta tweak for sideloading (as IPA) + - name: Build RyukGram tweak for sideloading (as IPA) run: | pip install --force-reinstall https://github.com/asdfzxcvbn/pyzule-rw/archive/main.zip - + cd main curl -Lo ipapatch https://github.com/asdfzxcvbn/ipapatch/releases/download/v2.1.3/ipapatch.macos-arm64 chmod +x ipapatch @@ -104,33 +100,32 @@ jobs: ./build.sh sideload ls -la packages - env: THEOS: ${{ github.workspace }}/theos - name: Rename IPA to include version info run: | cd main/packages - mv "$(ls -t | head -n1)" "SCInsta_sideloaded_v${SCINSTA_VERSION}.ipa" + mv "$(ls -t | head -n1)" "RyukGram_sideloaded_v${VERSION}.ipa" - name: Pass package name to upload action id: package_name run: | - echo "package=$(ls -t main/packages | head -n1)" >> "$GITHUB_OUTPUT" + echo "package=$(ls -t main/packages | head -n1)" >> "$GITHUB_OUTPUT" - name: Upload Artifact if: ${{ inputs.upload_artifact }} uses: actions/upload-artifact@v4 with: - name: SCInsta_sideloaded_v${{ steps.scinsta_version.outputs.version }} + name: RyukGram_sideloaded_v${{ steps.version.outputs.version }} path: ${{ github.workspace }}/main/packages/${{ steps.package_name.outputs.package }} if-no-files-found: error - name: Create Release uses: softprops/action-gh-release@v2.0.6 with: - name: SCInsta_sideloaded_v${{ steps.scinsta_version.outputs.version }} - files: ${{ github.workspace }}/main/packages/SCInsta_sideloaded_v*.ipa + name: RyukGram_sideloaded_v${{ steps.version.outputs.version }} + files: ${{ github.workspace }}/main/packages/RyukGram_sideloaded_v*.ipa draft: true - name: Output Release URL diff --git a/.github/workflows/buildtweak.yml b/.github/workflows/buildtweak.yml index b08ce0a..87f5f7e 100644 --- a/.github/workflows/buildtweak.yml +++ b/.github/workflows/buildtweak.yml @@ -1,4 +1,4 @@ -name: Build SCInsta tweak for Rootless +name: Build RyukGram tweak for Rootless on: push: @@ -6,13 +6,13 @@ on: - 'main' - 'dev' workflow_dispatch: - + concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: build: - name: Build SCInsta Rootless + name: Build RyukGram Rootless runs-on: macos-latest permissions: contents: write @@ -27,7 +27,7 @@ jobs: run: brew install ldid dpkg make - name: Set PATH environment variable - run: echo "$(brew --prefix make)/libexec/gnubin" >> $GITHUB_PATH + run: echo "$(brew --prefix make)/libexec/gnubin" >> $GITHUB_PATH - name: Setup Theos uses: actions/checkout@v4 @@ -36,7 +36,7 @@ jobs: ref: master path: ${{ github.workspace }}/theos submodules: recursive - + - name: SDK Caching id: SDK uses: actions/cache@v4 @@ -58,29 +58,26 @@ jobs: env: THEOS: ${{ github.workspace }}/theos - - name: Get SCInsta Version - id: scinsta_version + - name: Get Version + id: version run: | - SCINSTA_VERSION=$(awk '/Version:/ {print $2}' main/control) + VERSION=$(awk '/Version:/ {print $2}' main/control) + echo "VERSION=${VERSION}" >> "$GITHUB_ENV" + echo "version=${VERSION}" >> "$GITHUB_OUTPUT" - echo "SCINSTA_VERSION=${SCINSTA_VERSION}" >> "$GITHUB_ENV" - echo "version=${SCINSTA_VERSION}" >> "$GITHUB_OUTPUT" - - - name: Build SCInsta tweak for Rootless - run: | + - name: Build RyukGram tweak for Rootless + run: | cd main ls -la - ./build.sh rootless ls -la packages - env: THEOS: ${{ github.workspace }}/theos - name: Rename deb to include version info run: | cd main/packages - mv "$(ls -t | head -n1)" "com.socuul.scinsta_${SCINSTA_VERSION}+debug-rootless.deb" + mv "$(ls -t | head -n1)" "com.faroukbmiled.ryukgram_${VERSION}+debug-rootless.deb" - name: Pass package name to upload action id: package_name diff --git a/Makefile b/Makefile index 0986509..0c9ff0f 100644 --- a/Makefile +++ b/Makefile @@ -4,9 +4,9 @@ ARCHS = arm64 include $(THEOS)/makefiles/common.mk -TWEAK_NAME = SCInsta +TWEAK_NAME = RyukGram -$(TWEAK_NAME)_FILES = $(shell find src -type f \( -iname \*.x -o -iname \*.xm -o -iname \*.m \)) $(wildcard modules/JGProgressHUD/*.m) +$(TWEAK_NAME)_FILES = $(shell find src -type f \( -iname \*.x -o -iname \*.xm -o -iname \*.m \)) $(wildcard modules/JGProgressHUD/*.m) modules/fishhook/fishhook.c $(TWEAK_NAME)_FRAMEWORKS = UIKit Foundation CoreGraphics Photos CoreServices SystemConfiguration SafariServices Security QuartzCore $(TWEAK_NAME)_PRIVATE_FRAMEWORKS = Preferences $(TWEAK_NAME)_CFLAGS = -fobjc-arc -Wno-unsupported-availability-guard -Wno-unused-value -Wno-deprecated-declarations -Wno-nullability-completeness -Wno-unused-function -Wno-incompatible-pointer-types diff --git a/README.md b/README.md index 679c491..5656588 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,23 @@ -# SCInsta - -A feature-rich iOS tweak for Instagram. - -**Version:** v1.1.4 -**Tested on:** Instagram 423.1.0 - -A more actively maintained fork of Scinsta, featuring enhancements, fixes, and improved stability. +# RyukGram +A feature-rich iOS tweak for Instagram, forked from [SCInsta](https://github.com/SoCuul/SCInsta) with additional features and fixes.\ +`Version v1.1.4` | `Tested on Instagram 423.1.0` --- > [!NOTE] -> ⚙️  To modify SCInsta's settings, check out [this section below](https://github.com/SoCuul/SCInsta#Opening-Tweak-Settings) for help\ -> ❓  If you have any questions or need help with the tweak, visit the [Discussions](https://github.com/SoCuul/SCInsta/discussions) tab -> -> ✨  If you have a feature request, [click here](https://github.com/SoCuul/SCInsta/issues/new/choose)\ -> 🐛  If you have a bug report, [click here](https://github.com/SoCuul/SCInsta/issues/new/choose) -> +> To modify RyukGram's settings, check out [this section below](#opening-tweak-settings) for help --- # Installation >[!IMPORTANT] > Which type of device are you planning on installing this tweak on? -> - Jailbroken/TrollStore device -> [Download pre-built tweak](https://github.com/SoCuul/SCInsta/releases/latest) -> - Standard iOS device -> [Visit the wiki to create an IPA file](https://github.com/SoCuul/SCInsta/wiki/Building-IPA) +> - Jailbroken/TrollStore device -> [Download pre-built tweak](https://github.com/faroukbmiled/RyukGram/releases/latest) +> - Standard iOS device -> Sideload the dylib using Feather or similar # Features +> Features marked with **\*** are new or improved in RyukGram + ### General - Hide ads - Hide Meta AI @@ -57,18 +49,30 @@ A more actively maintained fork of Scinsta, featuring enhancements, fixes, and i ### Reels - Modify tap controls - Always show progress scrubber -- Disable auto-unmuting reels +- Disable auto-unmuting reels (properly blocks mute switch, volume buttons, and announcer broadcasts) **\*** - Confirm reel refresh - Hide reels header - Hide reels blend button - Disable scrolling reels - Prevent doom scrolling (limit maximum viewable reels) +- Enhanced Pause/Play mode (when Pause/Play tap control is set): **\*** + - Mute toggle auto-hidden, only play/pause icon visible + - Audio forced on in reels tab + - Play indicator properly hidden when video plays (fixes IG bug after hold/zoom) + - Playback toggle synced with overlay during hold/zoom + - Works across IG A/B test variants ### Saving -- Download feed posts +- Download feed posts (photo + video) - Download reels - Download stories - Save profile picture +- Download buttons on media — tap a button directly on feed posts, reels sidebar, and story overlay **\*** +- Download method — choose between download button or long-press gesture **\*** +- Save action — choose between share sheet or save directly to Photos **\*** +- Download confirmation — optional confirmation dialog before downloading **\*** +- Non-blocking download HUD — pill-style progress at the top, tap to cancel **\*** +- Debug fallback — if IG updates break downloads, shows diagnostic info instead of crashing **\*** - *Customize finger count for long-press* - *Customize hold time for long-press* @@ -79,7 +83,11 @@ A more actively maintained fork of Scinsta, featuring enhancements, fixes, and i - Unlimited replay of direct stories - Disable view-once limitations - Disable screenshot detection -- Disable story seen receipt +- Disable story seen receipt (blocks network upload, toggleable at runtime without restart) **\*** +- Keep stories visually unseen — keeps the colorful ring in the tray after viewing **\*** +- Manual mark story as seen — button on story overlay to selectively mark stories as seen **\*** +- Stop story auto-advance — stories won't auto-skip when the timer ends **\*** +- Story download button — download directly from the story overlay **\*** - Disable instants creation ### Navigation @@ -126,9 +134,9 @@ A more actively maintained fork of Scinsta, featuring enhancements, fixes, and i 1. Install iOS 16.2 frameworks for theos 1. [Click to download iOS SDKs](https://github.com/xybp888/iOS-SDKs/archive/refs/heads/master.zip) 2. Unzip, then copy the `iPhoneOS16.2.sdk` folder into `~/theos/sdks` -2. Clone SCInsta repo from GitHub: `git clone --recurse-submodules https://github.com/SoCuul/SCInsta` +2. Clone repo: `git clone --recurse-submodules https://github.com/faroukbmiled/RyukGram` 3. **For sideloading**: Download a decrypted Instagram IPA from a trusted source, making sure to rename it to `com.burbn.instagram.ipa`. - Then create a folder called `packages` inside of the `SCInsta` folder, and move the Instagram IPA file into it. + Then create a folder called `packages` inside of the project folder, and move the Instagram IPA file into it. ### Run build script ```sh @@ -136,18 +144,7 @@ $ chmod +x build.sh $ ./build.sh ``` -# Contributing -Contributions to this tweak are greatly appreciated. Feel free to create a pull request if you would like to contribute. - -If you do not have the technical knowledge to contribute to the codebase, improvements to the documentation are always welcome! - -# Support the project -SCInsta takes a lot of time to develop, as the Instagram app is ever-changing and difficult to keep up with. Additionally, I'm still a student which doesn't leave me much time to work on this tweak. - -If you'd like to support my work, you can donate to my [ko-fi page](https://ko-fi.com/socuul)!\ -There's many other ways to support this project however, by simply sharing a link to this tweak with others who would like it! - -Seeing people use this tweak is what keeps me motivated to keep working on it ❤️ - # Credits -- Huge thanks to [@BandarHL](https://github.com/BandarHL) for creating the original BHInstagram project, which SCInsta is based upon. +- [SCInsta](https://github.com/SoCuul/SCInsta) by [@SoCuul](https://github.com/SoCuul) — original tweak this fork is based on +- [@BandarHL](https://github.com/BandarHL) — creator of the original BHInstagram project +- [@faroukbmiled](https://github.com/faroukbmiled) — RyukGram modifications and additional features diff --git a/SCInsta.plist b/RyukGram.plist similarity index 100% rename from SCInsta.plist rename to RyukGram.plist diff --git a/build.sh b/build.sh index fbeaef9..54b9404 100755 --- a/build.sh +++ b/build.sh @@ -55,7 +55,7 @@ then exit 1 fi - echo -e '\033[1m\033[32mBuilding SCInsta tweak for sideloading (as IPA)\033[0m' + echo -e '\033[1m\033[32mBuilding RyukGram tweak for sideloading (as IPA)\033[0m' make $MAKEARGS @@ -65,36 +65,35 @@ then exit fi - SCINSTAPATH=".theos/obj/debug/SCInsta.dylib" + TWEAKPATH=".theos/obj/debug/RyukGram.dylib" if [ "$2" == "--devquick" ]; then - # Exclude SCInsta.dylib from ipa for livecontainer quick builds - SCINSTAPATH="" + TWEAKPATH="" fi # Create IPA File echo -e '\033[1m\033[32mCreating the IPA file...\033[0m' - rm -f packages/SCInsta-sideloaded.ipa - cyan -i "packages/${ipaFile}" -o packages/SCInsta-sideloaded.ipa -f $SCINSTAPATH $FLEXPATH -c $COMPRESSION -m 15.0 -du - - # Patch IPA for sideloading - ipapatch --input "packages/SCInsta-sideloaded.ipa" --inplace --noconfirm + rm -f packages/RyukGram-sideloaded.ipa + cyan -i "packages/${ipaFile}" -o packages/RyukGram-sideloaded.ipa -f $TWEAKPATH $FLEXPATH -c $COMPRESSION -m 15.0 -du - echo -e "\033[1m\033[32mDone, we hope you enjoy SCInsta!\033[0m\n\nYou can find the ipa file at: $(pwd)/packages" + # Patch IPA for sideloading + ipapatch --input "packages/RyukGram-sideloaded.ipa" --inplace --noconfirm + + echo -e "\033[1m\033[32mDone, enjoy RyukGram!\033[0m\n\nYou can find the ipa file at: $(pwd)/packages" elif [ "$1" == "rootless" ]; then - + # Clean build artifacts make clean rm -rf .theos - echo -e '\033[1m\033[32mBuilding SCInsta tweak for rootless\033[0m' + echo -e '\033[1m\033[32mBuilding RyukGram tweak for rootless\033[0m' export THEOS_PACKAGE_SCHEME=rootless make package - echo -e "\033[1m\033[32mDone, we hope you enjoy SCInsta!\033[0m\n\nYou can find the deb file at: $(pwd)/packages" + echo -e "\033[1m\033[32mDone, enjoy RyukGram!\033[0m\n\nYou can find the deb file at: $(pwd)/packages" elif [ "$1" == "rootful" ]; then @@ -103,18 +102,18 @@ then make clean rm -rf .theos - echo -e '\033[1m\033[32mBuilding SCInsta tweak for rootful\033[0m' + echo -e '\033[1m\033[32mBuilding RyukGram tweak for rootful\033[0m' unset THEOS_PACKAGE_SCHEME make package - echo -e "\033[1m\033[32mDone, we hope you enjoy SCInsta!\033[0m\n\nYou can find the deb file at: $(pwd)/packages" + echo -e "\033[1m\033[32mDone, enjoy RyukGram!\033[0m\n\nYou can find the deb file at: $(pwd)/packages" else - echo '+--------------------+' - echo '|SCInsta Build Script|' - echo '+--------------------+' + echo '+----------------------+' + echo '|RyukGram Build Script |' + echo '+----------------------+' echo echo 'Usage: ./build.sh ' exit 1 -fi \ No newline at end of file +fi diff --git a/control b/control index 4dd7cc1..c90a213 100644 --- a/control +++ b/control @@ -1,10 +1,10 @@ -Package: com.socuul.scinsta -Name: SCInsta +Package: com.faroukbmiled.ryukgram +Name: RyukGram Version: 1.1.4 Architecture: iphoneos-arm -Description: A feature-rich tweak for Instagram on iOS! -Homepage: https://github.com/SoCuul/SCInsta -Maintainer: SoCuul -Author: SoCuul +Description: A feature-rich tweak for Instagram on iOS, based on SCInsta +Homepage: https://github.com/faroukbmiled/RyukGram +Maintainer: Ryuk +Author: Ryuk Section: Tweaks -Depends: mobilesubstrate \ No newline at end of file +Depends: mobilesubstrate diff --git a/modules/fishhook/fishhook.c b/modules/fishhook/fishhook.c new file mode 100644 index 0000000..fb41e8e --- /dev/null +++ b/modules/fishhook/fishhook.c @@ -0,0 +1,264 @@ +// Copyright (c) 2013, Facebook, Inc. +// All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name Facebook nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific +// prior written permission. +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "fishhook.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __LP64__ +typedef struct mach_header_64 mach_header_t; +typedef struct segment_command_64 segment_command_t; +typedef struct section_64 section_t; +typedef struct nlist_64 nlist_t; +#define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT_64 +#else +typedef struct mach_header mach_header_t; +typedef struct segment_command segment_command_t; +typedef struct section section_t; +typedef struct nlist nlist_t; +#define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT +#endif + +#ifndef SEG_DATA_CONST +#define SEG_DATA_CONST "__DATA_CONST" +#endif + +struct rebindings_entry { + struct rebinding *rebindings; + size_t rebindings_nel; + struct rebindings_entry *next; +}; + +static struct rebindings_entry *_rebindings_head; + +static int prepend_rebindings(struct rebindings_entry **rebindings_head, + struct rebinding rebindings[], + size_t nel) { + struct rebindings_entry *new_entry = (struct rebindings_entry *) malloc(sizeof(struct rebindings_entry)); + if (!new_entry) { + return -1; + } + new_entry->rebindings = (struct rebinding *) malloc(sizeof(struct rebinding) * nel); + if (!new_entry->rebindings) { + free(new_entry); + return -1; + } + memcpy(new_entry->rebindings, rebindings, sizeof(struct rebinding) * nel); + new_entry->rebindings_nel = nel; + new_entry->next = *rebindings_head; + *rebindings_head = new_entry; + return 0; +} + +#if 0 +static int get_protection(void *addr, vm_prot_t *prot, vm_prot_t *max_prot) { + mach_port_t task = mach_task_self(); + vm_size_t size = 0; + vm_address_t address = (vm_address_t)addr; + memory_object_name_t object; +#ifdef __LP64__ + mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64; + vm_region_basic_info_data_64_t info; + kern_return_t info_ret = vm_region_64( + task, &address, &size, VM_REGION_BASIC_INFO_64, (vm_region_info_64_t)&info, &count, &object); +#else + mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT; + vm_region_basic_info_data_t info; + kern_return_t info_ret = vm_region(task, &address, &size, VM_REGION_BASIC_INFO, (vm_region_info_t)&info, &count, &object); +#endif + if (info_ret == KERN_SUCCESS) { + if (prot != NULL) + *prot = info.protection; + + if (max_prot != NULL) + *max_prot = info.max_protection; + + return 0; + } + + return -1; +} +#endif + +static void perform_rebinding_with_section(struct rebindings_entry *rebindings, + section_t *section, + intptr_t slide, + nlist_t *symtab, + char *strtab, + uint32_t *indirect_symtab) { + uint32_t *indirect_symbol_indices = indirect_symtab + section->reserved1; + void **indirect_symbol_bindings = (void **)((uintptr_t)slide + section->addr); + + for (uint i = 0; i < section->size / sizeof(void *); i++) { + uint32_t symtab_index = indirect_symbol_indices[i]; + if (symtab_index == INDIRECT_SYMBOL_ABS || symtab_index == INDIRECT_SYMBOL_LOCAL || + symtab_index == (INDIRECT_SYMBOL_LOCAL | INDIRECT_SYMBOL_ABS)) { + continue; + } + uint32_t strtab_offset = symtab[symtab_index].n_un.n_strx; + char *symbol_name = strtab + strtab_offset; + bool symbol_name_longer_than_1 = symbol_name[0] && symbol_name[1]; + struct rebindings_entry *cur = rebindings; + while (cur) { + for (uint j = 0; j < cur->rebindings_nel; j++) { + if (symbol_name_longer_than_1 && strcmp(&symbol_name[1], cur->rebindings[j].name) == 0) { + kern_return_t err; + + if (cur->rebindings[j].replaced != NULL && indirect_symbol_bindings[i] != cur->rebindings[j].replacement) + *(cur->rebindings[j].replaced) = indirect_symbol_bindings[i]; + + /** + * 1. Moved the vm protection modifying codes to here to reduce the + * changing scope. + * 2. Adding VM_PROT_WRITE mode unconditionally because vm_region + * API on some iOS/Mac reports mismatch vm protection attributes. + * -- Lianfu Hao Jun 16th, 2021 + **/ + err = vm_protect (mach_task_self (), (uintptr_t)indirect_symbol_bindings, section->size, 0, VM_PROT_READ | VM_PROT_WRITE | VM_PROT_COPY); + if (err == KERN_SUCCESS) { + /** + * Once we failed to change the vm protection, we + * MUST NOT continue the following write actions! + * iOS 15 has corrected the const segments prot. + * -- Lionfore Hao Jun 11th, 2021 + **/ + indirect_symbol_bindings[i] = cur->rebindings[j].replacement; + } + goto symbol_loop; + } + } + cur = cur->next; + } + symbol_loop:; + } +} + +static void rebind_symbols_for_image(struct rebindings_entry *rebindings, + const struct mach_header *header, + intptr_t slide) { + Dl_info info; + if (dladdr(header, &info) == 0) { + return; + } + + segment_command_t *cur_seg_cmd; + segment_command_t *linkedit_segment = NULL; + struct symtab_command* symtab_cmd = NULL; + struct dysymtab_command* dysymtab_cmd = NULL; + + uintptr_t cur = (uintptr_t)header + sizeof(mach_header_t); + for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) { + cur_seg_cmd = (segment_command_t *)cur; + if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) { + if (strcmp(cur_seg_cmd->segname, SEG_LINKEDIT) == 0) { + linkedit_segment = cur_seg_cmd; + } + } else if (cur_seg_cmd->cmd == LC_SYMTAB) { + symtab_cmd = (struct symtab_command*)cur_seg_cmd; + } else if (cur_seg_cmd->cmd == LC_DYSYMTAB) { + dysymtab_cmd = (struct dysymtab_command*)cur_seg_cmd; + } + } + + if (!symtab_cmd || !dysymtab_cmd || !linkedit_segment || + !dysymtab_cmd->nindirectsyms) { + return; + } + + // Find base symbol/string table addresses + uintptr_t linkedit_base = (uintptr_t)slide + linkedit_segment->vmaddr - linkedit_segment->fileoff; + nlist_t *symtab = (nlist_t *)(linkedit_base + symtab_cmd->symoff); + char *strtab = (char *)(linkedit_base + symtab_cmd->stroff); + + // Get indirect symbol table (array of uint32_t indices into symbol table) + uint32_t *indirect_symtab = (uint32_t *)(linkedit_base + dysymtab_cmd->indirectsymoff); + + cur = (uintptr_t)header + sizeof(mach_header_t); + for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) { + cur_seg_cmd = (segment_command_t *)cur; + if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) { + if (strcmp(cur_seg_cmd->segname, SEG_DATA) != 0 && + strcmp(cur_seg_cmd->segname, SEG_DATA_CONST) != 0) { + continue; + } + for (uint j = 0; j < cur_seg_cmd->nsects; j++) { + section_t *sect = + (section_t *)(cur + sizeof(segment_command_t)) + j; + if ((sect->flags & SECTION_TYPE) == S_LAZY_SYMBOL_POINTERS) { + perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab); + } + if ((sect->flags & SECTION_TYPE) == S_NON_LAZY_SYMBOL_POINTERS) { + perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab); + } + } + } + } +} + +static void _rebind_symbols_for_image(const struct mach_header *header, + intptr_t slide) { + rebind_symbols_for_image(_rebindings_head, header, slide); +} + +int rebind_symbols_image(void *header, + intptr_t slide, + struct rebinding rebindings[], + size_t rebindings_nel) { + struct rebindings_entry *rebindings_head = NULL; + int retval = prepend_rebindings(&rebindings_head, rebindings, rebindings_nel); + rebind_symbols_for_image(rebindings_head, (const struct mach_header *) header, slide); + if (rebindings_head) { + free(rebindings_head->rebindings); + } + free(rebindings_head); + return retval; +} + +int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel) { + int retval = prepend_rebindings(&_rebindings_head, rebindings, rebindings_nel); + if (retval < 0) { + return retval; + } + // If this was the first call, register callback for image additions (which is also invoked for + // existing images, otherwise, just run on existing images + if (!_rebindings_head->next) { + _dyld_register_func_for_add_image(_rebind_symbols_for_image); + } else { + uint32_t c = _dyld_image_count(); + for (uint32_t i = 0; i < c; i++) { + _rebind_symbols_for_image(_dyld_get_image_header(i), _dyld_get_image_vmaddr_slide(i)); + } + } + return retval; +} diff --git a/modules/fishhook/fishhook.h b/modules/fishhook/fishhook.h new file mode 100644 index 0000000..0d8e36a --- /dev/null +++ b/modules/fishhook/fishhook.h @@ -0,0 +1,76 @@ +// Copyright (c) 2013, Facebook, Inc. +// All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name Facebook nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific +// prior written permission. +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef fishhook_h +#define fishhook_h + +#include +#include + +#if !defined(FISHHOOK_EXPORT) +#define FISHHOOK_VISIBILITY __attribute__((visibility("hidden"))) +#else +#define FISHHOOK_VISIBILITY __attribute__((visibility("default"))) +#endif + +#ifdef __cplusplus +extern "C" { +#endif //__cplusplus + +/* + * A structure representing a particular intended rebinding from a symbol + * name to its replacement + */ +struct rebinding { + const char *name; + void *replacement; + void **replaced; +}; + +/* + * For each rebinding in rebindings, rebinds references to external, indirect + * symbols with the specified name to instead point at replacement for each + * image in the calling process as well as for all future images that are loaded + * by the process. If rebind_functions is called more than once, the symbols to + * rebind are added to the existing list of rebindings, and if a given symbol + * is rebound more than once, the later rebinding will take precedence. + */ +FISHHOOK_VISIBILITY +int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel); + +/* + * Rebinds as above, but only in the specified image. The header should point + * to the mach-o header, the slide should be the slide offset. Others as above. + */ +FISHHOOK_VISIBILITY +int rebind_symbols_image(void *header, + intptr_t slide, + struct rebinding rebindings[], + size_t rebindings_nel); + +#ifdef __cplusplus +} +#endif //__cplusplus + +#endif //fishhook_h + diff --git a/src/Features/Media/MediaDownload.xm b/src/Features/Media/MediaDownload.xm index 554a456..3a35709 100644 --- a/src/Features/Media/MediaDownload.xm +++ b/src/Features/Media/MediaDownload.xm @@ -162,7 +162,7 @@ static void sciShowDebugIvarDump(UIView *cell) { NSLog(@"[SCInsta] Debug: %@", debug); dispatch_async(dispatch_get_main_queue(), ^{ - UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"SCInsta Debug" + UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"RyukGram Debug" message:debug preferredStyle:UIAlertControllerStyleAlert]; [alert addAction:[UIAlertAction actionWithTitle:@"Copy & Close" style:UIAlertActionStyleDefault handler:^(UIAlertAction *a) { diff --git a/src/Settings/SCISettingsViewController.m b/src/Settings/SCISettingsViewController.m index cb211c9..5df6860 100644 --- a/src/Settings/SCISettingsViewController.m +++ b/src/Settings/SCISettingsViewController.m @@ -70,8 +70,8 @@ static char rowStaticRef[] = "row"; [super viewWillDisappear:animated]; if (![[[NSUserDefaults standardUserDefaults] objectForKey:@"SCInstaFirstRun"] isEqualToString:SCIVersionString]) { - UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"SCInsta Settings Info" - message:@"In the future: Hold down on the three lines at the top right of your profile page, to re-open SCInsta settings." + UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"RyukGram Settings Info" + message:@"In the future: Hold down on the three lines at the top right of your profile page, to re-open RyukGram settings." preferredStyle:UIAlertControllerStyleAlert]; [alert addAction:[UIAlertAction actionWithTitle:@"I understand!" diff --git a/src/Settings/TweakSettings.m b/src/Settings/TweakSettings.m index 17cb1de..9edde2c 100644 --- a/src/Settings/TweakSettings.m +++ b/src/Settings/TweakSettings.m @@ -233,10 +233,10 @@ ] }, @{ - @"header": @"SCInsta", + @"header": @"RyukGram", @"rows": @[ - [SCISetting switchCellWithTitle:@"Enable tweak settings quick-access" subtitle:@"Allows you to hold on the home tab to open the SCInsta settings" defaultsKey:@"settings_shortcut" requiresRestart:YES], - [SCISetting switchCellWithTitle:@"Show tweak settings on app launch" subtitle:@"Automatically opens the SCInsta settings when the app launches" defaultsKey:@"tweak_settings_app_launch"], + [SCISetting switchCellWithTitle:@"Enable tweak settings quick-access" subtitle:@"Allows you to hold on the home tab to open the RyukGram settings" defaultsKey:@"settings_shortcut" requiresRestart:YES], + [SCISetting switchCellWithTitle:@"Show tweak settings on app launch" subtitle:@"Automatically opens the RyukGram settings when the app launches" defaultsKey:@"tweak_settings_app_launch"], [SCISetting buttonCellWithTitle:@"Reset onboarding completion state" subtitle:@"" icon:nil @@ -283,11 +283,11 @@ @{ @"header": @"Credits", @"rows": @[ - [SCISetting linkCellWithTitle:@"Developer" subtitle:@"SoCuul" imageUrl:@"https://i.imgur.com/c9CbytZ.png" url:@"https://socuul.dev"], + [SCISetting linkCellWithTitle:@"Original Developer" subtitle:@"SoCuul (SCInsta)" imageUrl:@"https://i.imgur.com/c9CbytZ.png" url:@"https://github.com/SoCuul/SCInsta"], [SCISetting linkCellWithTitle:@"Modded by" subtitle:@"Ryuk" imageUrl:@"https://github.com/faroukbmiled.png" url:@"https://github.com/faroukbmiled"], - [SCISetting linkCellWithTitle:@"View Repo" subtitle:@"View the tweak's source code on GitHub" imageUrl:@"https://i.imgur.com/BBUNzeP.png" url:@"https://github.com/SoCuul/SCInsta"] + [SCISetting linkCellWithTitle:@"View Repo" subtitle:@"View the source code on GitHub" imageUrl:@"https://i.imgur.com/BBUNzeP.png" url:@"https://github.com/faroukbmiled/RyukGram"] ], - @"footer": [NSString stringWithFormat:@"SCInsta %@\n\nInstagram v%@\n\nModded by Ryuk", SCIVersionString, [SCIUtils IGVersionString]] + @"footer": [NSString stringWithFormat:@"RyukGram %@\n\nInstagram v%@\n\nBased on SCInsta by SoCuul", SCIVersionString, [SCIUtils IGVersionString]] } ]; } @@ -300,7 +300,7 @@ /// + (NSString *)title { - return @"SCInsta Settings"; + return @"RyukGram Settings"; } diff --git a/src/SideloadPatch/SideloadPatch.xm b/src/SideloadPatch/SideloadPatch.xm new file mode 100644 index 0000000..f916be4 --- /dev/null +++ b/src/SideloadPatch/SideloadPatch.xm @@ -0,0 +1,216 @@ +// Sideload compatibility patch for Instagram. +// Fixes keychain, app groups, CloudKit, and container access when sideloaded. + +#import +#import +#import +#import +#import "../../modules/fishhook/fishhook.h" + +static NSString *bundleId = nil; +static NSString *accessGroupId = nil; + +static OSStatus (*orig_SecItemAdd)(CFDictionaryRef, CFTypeRef *) = NULL; +static OSStatus (*orig_SecItemCopyMatching)(CFDictionaryRef, CFTypeRef *) = NULL; +static OSStatus (*orig_SecItemUpdate)(CFDictionaryRef, CFDictionaryRef) = NULL; +static OSStatus (*orig_SecItemDelete)(CFDictionaryRef) = NULL; + +static IMP orig_CKEntitlements_initWithEntitlementsDict __attribute__((unused)) = NULL; +static IMP orig_CKContainer_setupWithContainerID __attribute__((unused)) = NULL; +static IMP orig_CKContainer_initWithContainerIdentifier __attribute__((unused)) = NULL; +static IMP orig_NSFileManager_containerURL __attribute__((unused)) = NULL; + +// -- app group path -- + +static NSString *_appGroupPath = nil; +static dispatch_once_t _appGroupOnce = 0; + +static NSString *getAppGroupPathIfExists(void) { + dispatch_once(&_appGroupOnce, ^{ + Class LSBundleProxy = objc_getClass("LSBundleProxy"); + if (!LSBundleProxy) return; + + id proxy = ((id(*)(id, SEL))objc_msgSend)( + (id)LSBundleProxy, sel_registerName("bundleProxyForCurrentProcess")); + if (!proxy) return; + + NSDictionary *ents = ((NSDictionary *(*)(id, SEL))objc_msgSend)( + proxy, sel_registerName("entitlements")); + if (!ents || ![ents isKindOfClass:[NSDictionary class]]) return; + + NSArray *groups = ents[@"com.apple.security.application-groups"]; + if (!groups || groups.count == 0) return; + + NSDictionary *urls = ((NSDictionary *(*)(id, SEL))objc_msgSend)( + proxy, sel_registerName("groupContainerURLs")); + if (!urls || ![urls isKindOfClass:[NSDictionary class]]) return; + + NSURL *url = urls[groups.firstObject]; + if (url) _appGroupPath = [url path]; + }); + return _appGroupPath; +} + +static BOOL createDirectoryIfNotExists(NSString *path) { + NSFileManager *fm = [NSFileManager defaultManager]; + if ([fm fileExistsAtPath:path]) return YES; + return [fm createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil]; +} + +// -- SecItem replacements: set the correct access group on every call -- + +static OSStatus replaced_SecItemAdd(CFDictionaryRef attributes, CFTypeRef *result) { + if (attributes && accessGroupId) { + NSMutableDictionary *q = [(__bridge NSDictionary *)attributes mutableCopy]; + q[(__bridge id)kSecAttrAccessGroup] = accessGroupId; + return orig_SecItemAdd((__bridge CFDictionaryRef)q, result); + } + return orig_SecItemAdd(attributes, result); +} + +static OSStatus replaced_SecItemCopyMatching(CFDictionaryRef query, CFTypeRef *result) { + if (query && accessGroupId) { + NSMutableDictionary *q = [(__bridge NSDictionary *)query mutableCopy]; + q[(__bridge id)kSecAttrAccessGroup] = accessGroupId; + return orig_SecItemCopyMatching((__bridge CFDictionaryRef)q, result); + } + return orig_SecItemCopyMatching(query, result); +} + +static OSStatus replaced_SecItemUpdate(CFDictionaryRef query, CFDictionaryRef attrs) { + if (query && accessGroupId) { + NSMutableDictionary *q = [(__bridge NSDictionary *)query mutableCopy]; + q[(__bridge id)kSecAttrAccessGroup] = accessGroupId; + return orig_SecItemUpdate((__bridge CFDictionaryRef)q, attrs); + } + return orig_SecItemUpdate(query, attrs); +} + +static OSStatus replaced_SecItemDelete(CFDictionaryRef query) { + if (query && accessGroupId) { + NSMutableDictionary *q = [(__bridge NSDictionary *)query mutableCopy]; + q[(__bridge id)kSecAttrAccessGroup] = accessGroupId; + return orig_SecItemDelete((__bridge CFDictionaryRef)q); + } + return orig_SecItemDelete(query); +} + +// -- CloudKit patches: strip iCloud entitlements, disable container init -- + +static id replaced_CKEntitlements_init(id self, SEL _cmd, NSDictionary *dict) { + NSMutableDictionary *d = [dict mutableCopy]; + [d removeObjectForKey:@"com.apple.developer.icloud-container-environment"]; + [d removeObjectForKey:@"com.apple.developer.icloud-services"]; + return ((id(*)(id, SEL, NSDictionary *))orig_CKEntitlements_initWithEntitlementsDict)(self, _cmd, [d copy]); +} + +static id replaced_CKContainer_setup(id self, SEL _cmd, id containerID, id options) { + return nil; +} + +static id replaced_CKContainer_init(id self, SEL _cmd, id identifier) { + return nil; +} + +// -- NSFileManager: redirect app group container to a local fallback -- + +static NSURL *replaced_containerURL(id self, SEL _cmd, NSString *groupId) { + NSString *groupPath = getAppGroupPathIfExists(); + if (!groupPath) { + NSString *docs = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject; + NSString *fallback = [docs stringByAppendingPathComponent:groupId]; + createDirectoryIfNotExists(fallback); + return [NSURL fileURLWithPath:fallback]; + } + NSURL *url = [[NSURL fileURLWithPath:groupPath] URLByAppendingPathComponent:groupId]; + createDirectoryIfNotExists([url path]); + return url; +} + +// -- swizzle helper: walks class hierarchy, handles inherited methods -- + +static void swizzleMethod(Class cls, SEL sel, IMP newIMP, IMP *outOrig) { + if (!cls) return; + Class cur = cls; + while (cur) { + unsigned int count = 0; + Method *list = class_copyMethodList(cur, &count); + for (unsigned int i = 0; i < count; i++) { + if (method_getName(list[i]) == sel) { + if (cur == cls) { + *outOrig = method_setImplementation(list[i], newIMP); + } else { + *outOrig = method_getImplementation(list[i]); + class_addMethod(cls, sel, newIMP, method_getTypeEncoding(list[i])); + } + free(list); + return; + } + } + free(list); + cur = class_getSuperclass(cur); + } +} + +// -- keychain bootstrap: discover the access group assigned to this app -- + +static void bootstrapKeychainAccessGroup(void) { + NSDictionary *query = @{ + (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, + (__bridge id)kSecAttrAccount: @"RyukGramSideloadPatch", + (__bridge id)kSecAttrService: @"", + (__bridge id)kSecReturnAttributes: @YES, + }; + + CFTypeRef result = NULL; + OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result); + if (status == errSecItemNotFound) + status = SecItemAdd((__bridge CFDictionaryRef)query, &result); + + if (status == errSecSuccess && result) { + bundleId = [[NSBundle mainBundle] bundleIdentifier]; + NSDictionary *attrs = (__bridge NSDictionary *)result; + NSString *group = attrs[(__bridge id)kSecAttrAccessGroup]; + if (group) accessGroupId = [group copy]; + CFRelease(result); + } +} + +// -- init -- + +%ctor { + @autoreleasepool { + bootstrapKeychainAccessGroup(); + + // rebind SecItem functions so keychain calls use the right access group + struct rebinding rebindings[] = { + {"SecItemAdd", (void *)replaced_SecItemAdd, (void **)&orig_SecItemAdd}, + {"SecItemCopyMatching", (void *)replaced_SecItemCopyMatching, (void **)&orig_SecItemCopyMatching}, + {"SecItemUpdate", (void *)replaced_SecItemUpdate, (void **)&orig_SecItemUpdate}, + {"SecItemDelete", (void *)replaced_SecItemDelete, (void **)&orig_SecItemDelete}, + }; + rebind_symbols(rebindings, 4); + + // patch NSFileManager for app group container fallback + Class fm = objc_getClass("NSFileManager"); + if (fm) swizzleMethod(fm, sel_registerName("containerURLForSecurityApplicationGroupIdentifier:"), + (IMP)replaced_containerURL, &orig_NSFileManager_containerURL); + + // patch CloudKit to prevent crashes from missing entitlements + Class ckEnt = objc_getClass("CKEntitlements"); + if (ckEnt) swizzleMethod(ckEnt, sel_registerName("initWithEntitlementsDict:"), + (IMP)replaced_CKEntitlements_init, &orig_CKEntitlements_initWithEntitlementsDict); + + Class ckCon = objc_getClass("CKContainer"); + if (ckCon) { + swizzleMethod(ckCon, sel_registerName("_setupWithContainerID:options:"), + (IMP)replaced_CKContainer_setup, &orig_CKContainer_setupWithContainerID); + swizzleMethod(ckCon, sel_registerName("_initWithContainerIdentifier:"), + (IMP)replaced_CKContainer_init, &orig_CKContainer_initWithContainerIdentifier); + } + + // NSUserDefaults _initWithSuiteName:container: intentionally not patched — + // crashes on current IG versions. the NSFileManager patch covers the + // group container redirect which is what actually matters. + } +} diff --git a/src/Tweak.x b/src/Tweak.x index af8d5ff..4af1f51 100644 --- a/src/Tweak.x +++ b/src/Tweak.x @@ -73,7 +73,7 @@ BOOL dmVisualMsgsViewedButtonEnabled = false; NSLog(@"[SCInsta] First run, initializing"); // Display settings modal on screen - NSLog(@"[SCInsta] Displaying SCInsta first-time settings modal"); + NSLog(@"[SCInsta] Displaying RyukGram first-time settings modal"); [SCIUtils showSettingsVC:[self window]]; } });