code:
//
//  InstallManager.swift
//  TrollApps // TrollAppsTV
//
//  Created by Cleopatra on 2023-12-18.
//  Modified for tvOS by Bonnie on 2024-07-31
//

import Combine
import SwiftUI

func InstallIPA(isTrollAppsUpdate: Bool) {
    print("INSTALL PROCESS STARTED")

    let destinationFolderURL = URL(fileURLWithPath: "/private/var/mobile/.TrollApps/tmp/")
    let IPAPathURL = destinationFolderURL
        //.appendingPathComponent(itemID.uuidString)
        .appendingPathExtension("install.ipa")

    if !FileManager.default.fileExists(atPath: IPAPathURL.path) {
        //completion(FunctionStatus(error: true, message: ErrorMessage(title: "FNF \(IPAPathURL.path)", body: "File does not exist")))
        print("ERROR: IPA PATH DOES NOT EXIST")
        return
    } else if let trollStoreApp = findTrollStoreApp() {
        let trollstoreHelperPath = trollStoreApp + "/trollstorehelper"
        let returnCode = spawnRoot(trollstoreHelperPath, ["install", IPAPathURL.path])

        if(isTrollAppsUpdate) {
            closeTrollApps()
        }

        if(returnCode != 0) {
            //completion(FunctionStatus(error: true, message: ErrorMessage(title: "FAILED_TO_INSTALL", body: "INSTALLATION_RETURNED_ERROR \(returnCode)")))
            print("ERROR: FAILED TO INSTALL APP. returnCode: " + String(returnCode))
            return
        } else {
            NotificationCenter.default.post(name: Notification.Name("ApplicationsChanged"), object: nil)
            PassthroughSubject<Void, Never>().send()

            if FileManager.default.fileExists(atPath: IPAPathURL.path) {
                do {
                    try FileManager.default.removeItem(atPath: IPAPathURL.path)
                    //completion(FunctionStatus(error: false))
                    print("FINISHED! CLEARING TEMP FOLDER")
                    clearTrollAppsFolder()
                    return
                } catch {
                    //completion(FunctionStatus(error: true, message: ErrorMessage(title: "ERROR_REMOVING_IPA_FILE_AFTER_INSTALL", body: "LIKELY_A_PERMS_ISSUE")))
                    print("ERROR: UNABLE TO REMOVE IPA FILE AFTER INSTALL, LIKELY A PERMS ISSUE")
                    return
                }
            } else {
                //completion(FunctionStatus(error: true, message: ErrorMessage(title: "MISSING_DOWNLOADED_IPA", body: "")))
                print("DOWNLOADED IPA WAS MISSING")
                return
            }
        }
    } else {
        print("UNABLE TO LOCATE TROLLSTOREHELPER, ENSURE TROLLSTORE IS INSTALLED")
        //completion(FunctionStatus(error: true, message: ErrorMessage(title: "TROLLSTORE_NOT_FOUND", body: "TROLLSTORE_IS_REQUIRED")))
        return
    }
}

func findTrollStoreApp() -> String? {
    let applicationDir = "/private/var/containers/Bundle/Application/"
    let fileManager = FileManager.default
    do {
        let folders = try fileManager.contentsOfDirectory(atPath: applicationDir)
        for folder in folders {
            let appPath = applicationDir + folder + "/TrollStore.app"
            if fileManager.fileExists(atPath: appPath) {
                return appPath
            }
        }
    } catch {
        print("Error reading contents of application directory: \(error)")
    }
    return nil
}

func DeleteIPA(_ itemID: UUID) -> Bool {
    let destinationFolderURL = URL(fileURLWithPath: "/var/mobile/.TrollApps/tmp/")
    let IPAPathURL = destinationFolderURL
        .appendingPathComponent(itemID.uuidString)
        .appendingPathExtension(".ipa")

    if !FileManager.default.fileExists(atPath: IPAPathURL.path) {
        return false
    } else {
        do {
            try FileManager.default.removeItem(atPath: IPAPathURL.path)
            return true
        } catch {
            return false
        }
    }
}


code:
//
//  QueueManager.swift
//  TrollApps
//
//  Created by Cleopatra on 2023-12-16.
//

import SwiftUI

enum ActionType: String, Decodable, Equatable, Hashable {
    case download
    case install
    case uninstall
    case finished
}

struct QueueItem: Equatable, Hashable {
    var id = UUID()

    var action: ActionType

    var icon: String
    var name: String

    var bundleIdentifier: String?
    var downloadURL: String?

    var progress : Double = 0

    var error :Bool = false
    var message : ErrorMessage = ErrorMessage(title: "", body: "")

    var queued = false
}

class QueueManager: ObservableObject {
    @Published var queue : [QueueItem] = []
    @Published var isProcessing : Bool = false
    @Published var hasFinished : Bool = false
    @Published var promptInstall : Bool = false

    @Published var canClose : Bool = true

    func addQueueItem(item: QueueItem) {
        queue.append(item)
    }

    func removeQueueItem(itemID: UUID) {
        if let index = queue.firstIndex(where: { $0.id == itemID }) {
            queue.remove(at: index)
        }
    }

    func removeQueueItem(bundleIdentifier: String) {
        if let index = queue.firstIndex(where: { $0.bundleIdentifier == bundleIdentifier }) {
            queue.remove(at: index)
        }
    }

    func hasQueueItem(bundleIdentifier: String) -> Bool {
        return queue.contains { $0.bundleIdentifier == bundleIdentifier }
    }

    func updateQueueItemProgress(itemID: UUID, progress: Double) {
        if let index = queue.firstIndex(where: { $0.id == itemID }) {
            queue[index].progress = progress
        }
    }

    func getQueueItem(bundleIdentifier: String) -> QueueItem? {
        if let index = queue.firstIndex(where: { $0.bundleIdentifier == bundleIdentifier }) {
            return queue[index]
        }

        return nil
    }
}

repo_name: Bonnie/TrollAppsTV

license: mit


code:
//
//  RepoManager.swift
//  TrollApps
//
//  Created by Cleopatra on 2023-12-02.
//

import SwiftUI

let decoder = JSONDecoder()

class RepositoryManager: ObservableObject {
    /*@AppStorage("repos") var RepoList: [String] = [
        "https://raw.githubusercontent.com/TheResonanceTeam/.default-sources/main/haxi0_2.0.json",
        "https://raw.githubusercontent.com/TheResonanceTeam/.default-sources/main/BonnieRepo_2.0.json",
        "https://raw.githubusercontent.com/TheResonanceTeam/.default-sources/main/ResonanceTeamRepo.json"
    ]*/
    //@Published var ReposData: [RepoMemory] = []
    //@Published var BadRepos: [BadRepoMemory] = []
    //@Published var InstalledApps = GetApps()

    @Published var hasFetchedRepos: Bool = false
    @Published var hasFinishedFetchingRepos: Bool = false

    @Published var isInstallingApp = false
    @Published var isDownloadingApp = false

    /*func IsAppInstalled(_ BundleID: String) -> Bool {
        let installedApps = InstalledApps
        return installedApps.contains { $0.id == BundleID }
    }

    func showUpdateButton(version1: String, BundleID: String) -> Int32 {
            let installedApps = InstalledApps
            if let installedApp = installedApps.first(where: { $0.id == BundleID }) {
                if (installedApp.version == version1) {
                    return 0
                } else {
                    return 0
                }
            } else {
                return 0
            }
        }*/
}


code:
//
//  Models.swift
//  TrollAppsTV
//
//  Created by Bonnie on 7/30/24.
//

import Foundation

// Model for an app's version
struct AppVersion: Identifiable, Codable {
    let id = UUID()
    let version: String
    let date: String
    let localizedDescription: String
    let downloadURL: String
    let size: Int
}

struct App: Identifiable, Codable {
    let id = UUID()
    let name: String
    let bundleIdentifier: String
    let bundleName: String
    let developerName: String
    let subtitle: String
    let localizedDescription: String
    let iconURL: String
    let headerURL: String
    let tintColor: String
    let screenshotURLs: [String]
    let versions: [AppVersion]
    let appPermissions: [String: String]
}

// Model for the repository
struct Repository: Identifiable, Codable {
    let id = UUID()
    let name: String
    let subtitle: String
    let description: String
    let iconURL: String
    let headerURL: String?
    let website: String
    let tintColor: String
    let featuredApps: [String]
    let apps: [TrollAppsTV.App]
    let news: [String]
}

struct Source: Identifiable, Codable {
    let id = UUID()
    let url: String
}


code:
//
//  SourcesViewModel.swift
//  TrollAppsTV
//
//  Created by Bonnie on 7/30/24.
//

import Foundation
import Combine

class SourcesViewModel: ObservableObject {
    @Published var sources: [Source] = []
    @Published var repositories: [Repository] = []
    private var cancellables = Set<AnyCancellable>()
    private var loadedRepositoryURLs: Set<String> = []

    private let userSourcesFilePath = "/private/var/mobile/.TrollApps/.userSources"

    init() {
        if !doesDefaultSourcesFileExist() {
            setDefaultSources()
            print("Initialized default sources.")
        }
        loadSourcesFromFile()
    }

    func setDefaultSources() {
        let demoURL1 = "https://raw.githubusercontent.com/TheResonanceTeam/.default-sources/main/tvOS/ResonanceTeamTV.json"
        let demoURL2 = "https://raw.githubusercontent.com/TheResonanceTeam/.default-sources/main/tvOS/UnofficialMojangArchive.json"

        sources.append(Source(url: demoURL1))
        sources.append(Source(url: demoURL2))

        //fetchRepository(from: demoURL1)
        //fetchRepository(from: demoURL2)

        saveSourcesToFile()
        createDefaultSourcesFile()
    }

    func addSource(url: String) {
        print("Adding source with URL: \(url)")
        sources.append(Source(url: url))
        fetchRepository(from: url)
        saveSourcesToFile()
    }

    func removeSource(at index: Int) {
        guard index < sources.count else { return }
        sources.remove(at: index)
        repositories.remove(at: index)
        saveSourcesToFile()
    }

    private func fetchRepository(from urlString: String) {
        guard let url = URL(string: urlString) else { return }

        if loadedRepositoryURLs.contains(urlString) {
            print("Repository already loaded for URL: \(urlString)")
            return
        }

        URLSession.shared.dataTaskPublisher(for: url)
            .map(\.data)
            .decode(type: Repository.self, decoder: JSONDecoder())
            .receive(on: DispatchQueue.main)
            .sink(receiveCompletion: { completion in
                switch completion {
                case .failure(let error):
                    print("Error fetching repository: \(error)")
                case .finished:
                    break
                }
            }, receiveValue: { [weak self] repository in
                print("Fetched repository: \(repository.name)")
                self?.repositories.append(repository)
                self?.loadedRepositoryURLs.insert(urlString)
            })
            .store(in: &cancellables)
    }

    func loadSourcesFromFile() {
        guard FileManager.default.fileExists(atPath: userSourcesFilePath),
              let fileContents = try? String(contentsOfFile: userSourcesFilePath) else {
            print("Could not read sources file")
            return
        }

        //  make absolutely sure we don't add any duplicates, and that we remove any deleted repos before reloading
        repositories.removeAll()
        loadedRepositoryURLs.removeAll()

        let urls = fileContents.split(separator: "\n").map { String($0) }
        sources = urls.map { Source(url: $0) }

        for url in urls {
            fetchRepository(from: url)
        }
    }

    private func saveSourcesToFile() {
        let urls = sources.map { $0.url }.joined(separator: "\n")
        do {
            try urls.write(toFile: userSourcesFilePath, atomically: true, encoding: .utf8)
        } catch {
            print("Error saving sources to file: \(error)")
        }
    }
}


code:
extern NSString *LSInstallTypeKey;

@interface LSBundleProxy
@property (nonatomic,readonly) NSString * bundleIdentifier;
@property (nonatomic) NSURL* dataContainerURL;
@property (nonatomic,readonly) NSURL* bundleContainerURL;
-(NSString*)localizedName;
@end

@interface LSApplicationProxy : LSBundleProxy
+ (instancetype)applicationProxyForIdentifier:(NSString*)identifier;
+ (instancetype)applicationProxyForBundleURL:(NSURL*)bundleURL;
@property NSURL* bundleURL;
@property NSString* bundleType;
@property NSString* canonicalExecutablePath;
@property (nonatomic,readonly) NSDictionary* groupContainerURLs;
@property (nonatomic,readonly) NSArray* plugInKitPlugins;
@property (getter=isInstalled,nonatomic,readonly) BOOL installed;
@property (getter=isPlaceholder,nonatomic,readonly) BOOL placeholder;
@property (getter=isRestricted,nonatomic,readonly) BOOL restricted;
@property (nonatomic,readonly) NSSet* claimedURLSchemes;
@property (nonatomic,readonly) NSString* applicationType;
@end

@interface LSApplicationWorkspace : NSObject
+ (instancetype)defaultWorkspace;
- (BOOL)registerApplicationDictionary:(NSDictionary*)dict;
- (BOOL)unregisterApplication:(id)arg1;
- (BOOL)_LSPrivateRebuildApplicationDatabasesForSystemApps:(BOOL)arg1 internal:(BOOL)arg2 user:(BOOL)arg3;
- (BOOL)openApplicationWithBundleID:(NSString *)arg1 ;
- (void)enumerateApplicationsOfType:(NSUInteger)type block:(void (^)(LSApplicationProxy*))block;
- (BOOL)installApplication:(NSURL*)appPackageURL withOptions:(NSDictionary*)options error:(NSError**)error;
- (BOOL)uninstallApplication:(NSString*)appId withOptions:(NSDictionary*)options;
- (void)addObserver:(id)arg1;
- (void)removeObserver:(id)arg1;
@end

@protocol LSApplicationWorkspaceObserverProtocol <NSObject>
@optional
-(void)applicationsDidInstall:(id)arg1;
-(void)applicationsDidUninstall:(id)arg1;
@end

@interface LSEnumerator : NSEnumerator
@property (nonatomic,copy) NSPredicate * predicate;
+ (instancetype)enumeratorForApplicationProxiesWithOptions:(NSUInteger)options;
@end

@interface LSPlugInKitProxy : LSBundleProxy
@property (nonatomic,readonly) NSString* pluginIdentifier;
@property (nonatomic,readonly) NSDictionary * pluginKitDictionary;
+ (instancetype)pluginKitProxyForIdentifier:(NSString*)arg1;
@end

@interface MCMContainer : NSObject
+ (id)containerWithIdentifier:(id)arg1 createIfNecessary:(BOOL)arg2 existed:(BOOL*)arg3 error:(id*)arg4;
@property (nonatomic,readonly) NSURL * url;
@end

@interface MCMDataContainer : MCMContainer
@end

@interface MCMAppDataContainer : MCMDataContainer
@end

@interface MCMAppContainer : MCMContainer
@end

@interface MCMPluginKitPluginDataContainer : MCMDataContainer
@end

@interface MCMSystemDataContainer : MCMContainer
@end

@interface MCMSharedDataContainer : MCMContainer
@end


code:
//
//  TSUtil.m
//  TrollSign
//
//  Created by Hornbeck on 11/1/22.
//

#import <Foundation/Foundation.h>
#import <spawn.h>
#import "TrollAppsTV-Bridging-Header.h"
#import "CoreServices.h"

#define POSIX_SPAWN_PERSONA_FLAGS_OVERRIDE 1
extern int posix_spawnattr_set_persona_np(const posix_spawnattr_t* __restrict, uid_t, uint32_t);
extern int posix_spawnattr_set_persona_uid_np(const posix_spawnattr_t* __restrict, uid_t);
extern int posix_spawnattr_set_persona_gid_np(const posix_spawnattr_t* __restrict, uid_t);

bool openApplicationWithBundleID(NSString* appId)
{
    return [[LSApplicationWorkspace defaultWorkspace] openApplicationWithBundleID:appId];
}

int spawnRoot(NSString* path, NSArray* args)
{
    NSMutableArray* argsM = args.mutableCopy ?: [NSMutableArray new];
    [argsM insertObject:path.lastPathComponent atIndex:0];

    NSUInteger argCount = [argsM count];
    char **argsC = (char **)malloc((argCount + 1) * sizeof(char*));

    for (NSUInteger i = 0; i < argCount; i++)
    {
        argsC[i] = strdup([[argsM objectAtIndex:i] UTF8String]);
    }
    argsC[argCount] = NULL;

    posix_spawnattr_t attr;
    posix_spawnattr_init(&attr);

    posix_spawnattr_set_persona_np(&attr, 99, POSIX_SPAWN_PERSONA_FLAGS_OVERRIDE);
    posix_spawnattr_set_persona_uid_np(&attr, 0);
    posix_spawnattr_set_persona_gid_np(&attr, 0);

    posix_spawn_file_actions_t action;
    posix_spawn_file_actions_init(&action);

    pid_t task_pid;
    int status = -200;
    int spawnError = posix_spawn(&task_pid, [path UTF8String], &action, &attr, (char* const*)argsC, NULL);
    posix_spawnattr_destroy(&attr);
    for (NSUInteger i = 0; i < argCount; i++)
    {
        free(argsC[i]);
    }
    free(argsC);

    if(spawnError != 0)
    {
        NSLog(@"posix_spawn error %d\n", spawnError);
        return spawnError;
    }

    do
    {
        if (waitpid(task_pid, &status, 0) != -1) {
            NSLog(@"Child status %d", WEXITSTATUS(status));
        } else
        {
            perror("waitpid");
            return -222;
        }
    } while (!WIFEXITED(status) && !WIFSIGNALED(status));

    return WEXITSTATUS(status);
}


code:
//
//  Use this file to import your target's public headers that you would like to expose to Swift.
//

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

extern int spawnRoot(NSString* path, NSArray* args);
extern bool openApplicationWithBundleID(NSString* appId);

@class BSMonotonicReferenceTime, NSArray, NSNumber, NSString, NSURL, SBSApplicationShortcutService, SBSApplicationShortcutServiceFetchResult;
@interface SBFApplication : NSObject {
    NSURL * _bundleURL;
}

@property (nonatomic, readonly) NSURL *bundleURL;
- (void)cxx_destruct;
- (id)bundleURL;
- (id)init;
- (id)initWithApplicationBundleIdentifier:(id)arg1;
@end

/*@interface
 LSApplicationWorkspace : NSObject
- (id)allInstalledApplications;
- (BOOL)unregisterApplication:(NSURL *)url;
- (BOOL)registerApplication:(NSURL *)url;
@end*/

/*@interface
 LSBundleProxy
@property
(readonly, nonatomic) NSURL *bundleURL;
@end*/

/*@interface
 LSApplicationProxy : LSBundleProxy
@end*/

@interface UIImage (Private)
+(id)_applicationIconImageForBundleIdentifier:(id)arg0 format:(int)arg1 scale:(CGFloat)arg2;
@end


code:
//
//  TrollAppsTVApp.swift
//  TrollAppsTV
//
//  Created by Bonnie on 7/29/24.
//

import SwiftUI

@main
struct TrollAppsTVApp: SwiftUI.App {

    init() {
        clearTrollAppsFolder()
        findAndSetTrollAppsTVVersion(version: "0.92")
    }

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}


code:
//
//  AppUtil.swift
//  TrollAppsTV
//
//  Created by Bonnie on 7/31/24.
//

import Combine

struct BundledApp: Identifiable, Hashable {
    let id: String
    var name: String
    var version: String
    var isTrollStore: Bool
    var icon: UIImage
}

private func applicationIconImage() -> Int32 {
    return UIDevice.current.userInterfaceIdiom == .pad ? 8 : 10
}

func clearTrollAppsFolder() {
    let tempIpaFileURL = URL(fileURLWithPath: "/private/var/mobile/.TrollApps/tmp.install.ipa")

    do {
        // Check and delete the tmp.install.ipa file
        if FileManager.default.fileExists(atPath: tempIpaFileURL.path) {
            try FileManager.default.removeItem(at: tempIpaFileURL)
            print("Successfully deleted tmp.install.ipa file.")
        } else {
            print("tmp.install.ipa file does not exist.")
        }

        //  once more just in case :3
        try FileManager.default.removeItem(at: tempIpaFileURL)
    } catch {
        print("Error deleting .TrollApps folder or tmp.install.ipa file: \(error.localizedDescription)")
    }
}

func findAndSetTrollAppsTVVersion(version: String) {
    let versionFilePath = "/private/var/mobile/.TrollApps/.currentversion"
    let fileManager = FileManager.default

    do {
        // Ensure the directory exists
        if !fileManager.fileExists(atPath: "/private/var/mobile/.TrollApps/") {
            try fileManager.createDirectory(atPath: "/private/var/mobile/.TrollApps/", withIntermediateDirectories: true, attributes: nil)
        }

        if fileManager.fileExists(atPath: versionFilePath) {
            try fileManager.removeItem(at: URL(fileURLWithPath: versionFilePath))
        }

        // Write the version to the file
        try version.write(toFile: versionFilePath, atomically: true, encoding: .utf8)
        print("Successfully wrote version \(version) to .currentversion file.")
    } catch {
        print("Error writing to .currentversion file: \(error.localizedDescription)")
    }
}

func isAppInstalled(bundleName: String) -> Bool {
    let applicationDir = "/private/var/containers/Bundle/Application/"
    let fileManager = FileManager.default

    do {
        let folders = try fileManager.contentsOfDirectory(atPath: applicationDir)
        for folder in folders {
            let appPath = applicationDir + folder + "/" + bundleName
            if fileManager.fileExists(atPath: appPath) {
                return true
            }
        }
        print("\(bundleName) not found.")
    } catch {
        print("Error reading contents of application directory: \(error)")
    }
    return false
}

func isNewVersionAvailable(for app: TrollAppsTV.App) -> Bool {
    let applicationDir = "/private/var/containers/Bundle/Application/"
    let fileManager = FileManager.default

    // Get the latest version from the repo
    guard let latestAppVersion = app.versions.first else {
        print("No versions available in the repo.")
        return false
    }

    do {
        let folders = try fileManager.contentsOfDirectory(atPath: applicationDir)
        for folder in folders {
            let appPath = applicationDir + folder + "/" + app.bundleName
            let versionFilePath = appPath + "/.currentversion"

            if fileManager.fileExists(atPath: appPath) {
                // Read the currently installed version
                if fileManager.fileExists(atPath: versionFilePath) {
                    let currentVersion = try String(contentsOfFile: versionFilePath, encoding: .utf8).trimmingCharacters(in: .whitespacesAndNewlines)

                    // Compare versions
                    if !isVersion(currentVersion, greaterThanOrEqualTo: latestAppVersion.version) {
                        return true
                    }
                } else {
                    return true
                }
            }
        }
    } catch {
        print("Error reading contents of application directory: \(error)")
    }

    return false
}

func isVersion(_ version1: String, greaterThanOrEqualTo version2: String) -> Bool {
    let version1Components = version1.split(separator: ".").compactMap { Int($0) }
    let version2Components = version2.split(separator: ".").compactMap { Int($0) }

    for (v1, v2) in zip(version1Components, version2Components) {
        if v1 > v2 {
            return true
        } else if v1 < v2 {
            return false
        }
    }
    return version1Components.count >= version2Components.count
}

func updateTrollApps(for app: TrollAppsTV.App) {
    guard let versionToDownload = app.versions.first else {
        print("No version to download.")
        return
    }

    let downloadURL = versionToDownload.downloadURL
    let magnifierURLString = "apple-magnifier://install?url=" + downloadURL
    if let url = URL(string: magnifierURLString),
       UIApplication.shared.canOpenURL(url) {

        DispatchQueue.main.asyncAfter(deadline: .now(), execute: {
            UIApplication.shared.open(url, options: [:], completionHandler: nil)
            UIControl().sendAction(#selector(URLSessionTask.suspend), to: UIApplication.shared, for: nil)
            Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { _ in
                exit(0)
            }
        })
    } else {
        print("Failed to create URL or cannot open URL.")
    }
}

func closeTrollApps() {
    DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
        UIControl().sendAction(#selector(URLSessionTask.suspend), to: UIApplication.shared, for: nil)
        Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { _ in
            exit(0)
        }
    })
}

func createDefaultSourcesFile() {
    let fileManager = FileManager.default
    let trollAppsDirectory = "/private/var/mobile/.TrollApps/"
    let filePath = trollAppsDirectory + ".defaultsources"

    // Check if the directory exists
    if !fileManager.fileExists(atPath: trollAppsDirectory) {
        do {
            try fileManager.createDirectory(atPath: trollAppsDirectory, withIntermediateDirectories: true, attributes: nil)
            print("Successfully created directory: \(trollAppsDirectory)")
        } catch {
            print("Error creating directory: \(error.localizedDescription)")
            return
        }
    }
    // Create and write to the .defaultsources file
    let defaultSourcesContent = "Heyyy :3"

    do {
        try defaultSourcesContent.write(toFile: filePath, atomically: true, encoding: .utf8)
        print("Successfully wrote to .defaultsources file.")
    } catch {
        print("Error writing to .defaultsources file: \(error.localizedDescription)")
    }
}

func doesDefaultSourcesFileExist() -> Bool {
    let fileManager = FileManager.default
    let filePath = "/private/var/mobile/.TrollApps/.defaultsources"

    if fileManager.fileExists(atPath: filePath) {
        print(".defaultsources file exists.")
        return true
    } else {
        print(".defaultsources file does not exist.")
        return false
    }
}

func deleteDefaultSourcesFile() -> Bool {
    let fileManager = FileManager.default
    let filePath = "/private/var/mobile/.TrollApps/.defaultsources"

    do {
        try fileManager.removeItem(atPath: filePath)
        print(".defaultsources file deleted successfully.")
        return true
    } catch {
        print("Error deleting .defaultsources file: \(error.localizedDescription)")
        return false
    }
}


code:
//
//  CustomAsyncImage.swift
//  TrollAppsTV
//
//  Created by Bonnie on 7/31/24.
//

import SwiftUI
import Combine

// ImageLoader class
class ImageLoader: ObservableObject {
    @Published var image: UIImage?

    private var cancellable: AnyCancellable?

    func loadImage(from url: URL) {
        cancellable = URLSession.shared.dataTaskPublisher(for: url)
            .map { UIImage(data: $0.data) }
            .replaceError(with: nil)
            .receive(on: DispatchQueue.main)
            .sink { [weak self] in self?.image = $0 }
    }

    func cancel() {
        cancellable?.cancel()
    }
}

// CustomAsyncImage view
struct CustomAsyncImage<Content: View, Placeholder: View>: View {
    @StateObject private var loader = ImageLoader()
    let url: URL
    let content: (Image) -> Content
    let placeholder: () -> Placeholder

    init(url: URL, @ViewBuilder content: @escaping (Image) -> Content, @ViewBuilder placeholder: @escaping () -> Placeholder) {
        self.url = url
        self.content = content
        self.placeholder = placeholder
    }

    var body: some View {
        Group {
            if let uiImage = loader.image {
                content(Image(uiImage: uiImage))
            } else {
                placeholder()
                    .onAppear {
                        loader.loadImage(from: url)
                    }
                    .onDisappear {
                        loader.cancel()
                    }
            }
        }
    }
}


code:
//
//  CustomVisualEffectBlur.swift
//  TrollAppsTV
//
//  Created by Bonnie on 7/30/24.
//

import SwiftUI

struct VisualEffectBlur<Content: View>: View {
    var blurStyle: UIBlurEffect.Style
    @ViewBuilder var content: Content

    var body: some View {
        ZStack {
            VisualEffectView(blurStyle: blurStyle)
            content
        }
        .compositingGroup()
    }
}

struct VisualEffectView: UIViewRepresentable {
    var blurStyle: UIBlurEffect.Style

    func makeUIView(context: Context) -> UIVisualEffectView {
        let view = UIVisualEffectView(effect: UIBlurEffect(style: blurStyle))
        return view
    }

    func updateUIView(_ uiView: UIVisualEffectView, context: Context) {}
}


code:
//
//  FocusableTextField.swift
//  TrollAppsTV
//
//  Created by Bonnie on 8/3/24.
//

import SwiftUI

struct FocusableTextField: UIViewRepresentable {
    class Coordinator: NSObject, UITextFieldDelegate {
        var parent: FocusableTextField

        init(parent: FocusableTextField) {
            self.parent = parent
        }

        func textFieldDidBeginEditing(_ textField: UITextField) {
            parent.isFocusedBinding?.wrappedValue = true
        }

        func textFieldDidEndEditing(_ textField: UITextField) {
            parent.isFocusedBinding?.wrappedValue = false
        }
    }

    @Binding var text: String
    var placeholder: String
    var isFocusedBinding: Binding<Bool>?

    func makeCoordinator() -> Coordinator {
        Coordinator(parent: self)
    }

    func makeUIView(context: Context) -> UITextField {
        let textField = UITextField()
        textField.delegate = context.coordinator
        textField.placeholder = placeholder
        textField.borderStyle = .roundedRect
        textField.text = text
        return textField
    }

    func updateUIView(_ uiView: UITextField, context: Context) {
        uiView.text = text
    }
}


code:
//
//  AppDetailView.swift
//  TrollAppsTV
//
//  Created by Bonnie on 7/30/24.
//

import SwiftUI

struct AppDetailView: View {
    let app: App
    @State private var isOwned: Bool = false // State to manage "GET" button text
    @State private var showingAlert = false
    @State private var downloadAlertMessage: String = ""

    var body: some View {
        ScrollView {
            VStack(alignment: .leading) {
                ZStack(alignment: .bottom) {
                    if #available(tvOS 15.0, *) {
                        AsyncImage(url: URL(string: app.headerURL)) { image in
                            image
                                .resizable()
                                .scaledToFill()
                                .frame(height: 620)
                                .clipped()
                        } placeholder: {
                            ProgressView()
                                .frame(height: 620)
                        }
                    } else {
                        CustomAsyncImage(url: URL(string: app.headerURL)!) { image in
                            image
                                .resizable()
                                .scaledToFill()
                                .frame(height: 620)
                                .clipped()
                        } placeholder: {
                            ProgressView()
                                .frame(height: 620)
                        }
                    }

                    // Blur effect at the bottom of the header image
                    VisualEffectBlur(blurStyle: .dark) {}
                        .mask(LinearGradient(gradient: Gradient(colors: [Color.clear, Color.black.opacity(0.8)]), startPoint: .top, endPoint: .bottom))
                }
                .frame(height: 620) // Set the frame height for the header image

                // Content: Title, Button, and Description
                VStack(alignment: .leading, spacing: 10) {
                    Text(app.name)
                        .font(.largeTitle)
                        .fontWeight(.bold)
                        .foregroundColor(.white)
                        .padding(.leading)
                        .padding(.top, 10)

                    HStack(alignment: .top) {
                        if(app.bundleName != "TrollAppsTV.app") {   //  app is NOT trollapps
                            if(isAppInstalled(bundleName: app.bundleName)) {
                                Button("OPEN") {
                                    if(openApplicationWithBundleID(app.bundleIdentifier)) {
                                        print("OPENING APP")
                                    }
                                }
                            } else {
                                if #available(tvOS 15.0, *) {
                                    Button("GET") {
                                        if let firstVersion = app.versions.first {
                                            print("CALLING DOWNLOADIPA AND SHOWING INIT ALERT")
                                            downloadIPA(firstVersion.downloadURL, isTrollAppsUpdate: false)
                                            showingAlert = true
                                        }
                                    }
                                    .alert("Started download:" + app.name, isPresented: $showingAlert) {
                                        Button("OK", role: .cancel) { }
                                    }
                                    .padding(.leading)
                                } else {
                                    Button("GET") {
                                        if let firstVersion = app.versions.first {
                                            print("CALLING DOWNLOADIPA AND SHOWING INIT ALERT")
                                            downloadIPA(firstVersion.downloadURL, isTrollAppsUpdate: false)
                                            downloadAlertMessage = "Started download: " + app.name
                                            showingAlert = true
                                        }
                                    }
                                    .alert(isPresented: $showingAlert) {
                                        Alert(
                                            title: Text(downloadAlertMessage),
                                            message: Text("Please wait...")
                                        )
                                    }
                                }
                            }
                        } else {    //  app IS trollapps
                            if(isNewVersionAvailable(for: app)) {
                                if #available(tvOS 15.0, *) {
                                    Button("UPDATE") {
                                        if let firstVersion = app.versions.first {
                                            print("CALLING DOWNLOADIPA AND SHOWING INIT ALERT")
                                            downloadIPA(firstVersion.downloadURL, isTrollAppsUpdate: true)
                                            showingAlert = true
                                        }
                                    }
                                    .alert("TrollApps will close. Please allow up to 20 seconds for the update to be applied.", isPresented: $showingAlert) {
                                        Button("OK", role: .cancel) { }
                                    }
                                    .padding(.leading)
                                } else {
                                    Button("UPDATE") {
                                        if let firstVersion = app.versions.first {
                                            print("CALLING DOWNLOADIPA AND SHOWING INIT ALERT")
                                            downloadIPA(firstVersion.downloadURL, isTrollAppsUpdate: true)
                                            downloadAlertMessage = "TrollApps will close. Please allow up to 20 seconds for the update to be applied."
                                            showingAlert = true
                                        }
                                    }
                                    .alert(isPresented: $showingAlert) {
                                        Alert(
                                            title: Text(downloadAlertMessage),
                                            message: Text("Please wait...")
                                        )
                                    }
                                }
                            } else {
                                Button("OWNED") {}.disabled(true)
                            }
                        }

                        VStack(alignment: .leading, spacing: 10) {
                            if #available(tvOS 15.0, *) {
                                Text(app.localizedDescription)
                                    .font(.body)
                                    .foregroundColor(.white)
                                    .padding()
                                    .background(.black)
                                    .cornerRadius(16)
                                    .padding(.horizontal)
                            } else {
                                Text(app.localizedDescription)
                                    .font(.body)
                                    .foregroundColor(.white)
                                    .padding()
                                    .cornerRadius(16)
                                    .padding(.horizontal)
                            }
                        }
                    }
                    .padding(.top, 10)
                }
                .padding(.top, 10)
                .padding(20)
            }
            .padding(.bottom)
        }
        .edgesIgnoringSafeArea(.all)
    }
}

struct ScreenshotCell: View {
    let imageURL: String

    var body: some View {
        if #available(tvOS 15.0, *) {
            AsyncImage(url: URL(string: imageURL)) { image in
                image
                    .resizable()
                    .scaledToFit()
                    .frame(width: 300, height: 200)
                    .clipShape(RoundedRectangle(cornerRadius: 10))
                    .overlay(
                        RoundedRectangle(cornerRadius: 10)
                            .stroke(Color.white, lineWidth: 2)
                    )
                    .shadow(radius: 5)
            } placeholder: {
                ProgressView()
                    .frame(width: 300, height: 200)
            }
        } else {
            CustomAsyncImage(url: URL(string: imageURL)!) { image in
                image
                    .resizable()
                    .scaledToFit()
                    .frame(width: 300, height: 200)
                    .clipShape(RoundedRectangle(cornerRadius: 10))
                    .overlay(
                        RoundedRectangle(cornerRadius: 10)
                            .stroke(Color.white, lineWidth: 2)
                    )
                    .shadow(radius: 5)
            } placeholder: {
                ProgressView()
                    .frame(width: 300, height: 200)
            }
        }
    }
}


code:
//
//  RepositoryDetailView.swift
//  TrollAppsTV
//
//  Created by Bonnie on 7/30/24.
//

import SwiftUI

struct RepositoryDetailView: View {
    let repository: Repository

    var body: some View {
        VStack {
            List {
                Section(header: Text("Apps")) {
                    ForEach(repository.apps) { app in
                        NavigationLink(destination: AppDetailView(app: app)) {
                            HStack(alignment: .center, spacing: 10) {
                                // App Icon
                                let width: CGFloat = 100 // Adjust this width to your desired value
                                let height = width * (768 / 1280) // Calculating height to maintain the 5:3 aspect ratio

                                if #available(tvOS 15.0, *) {
                                    AsyncImage(url: URL(string: app.iconURL)) { image in
                                        image
                                            .resizable()
                                            .scaledToFit()
                                            .frame(width: width, height: height)
                                            .clipShape(RoundedRectangle(cornerRadius: 16))
                                    } placeholder: {
                                        ProgressView()
                                            .frame(width: width, height: height)
                                    }
                                } else {
                                    CustomAsyncImage(url: URL(string: app.iconURL)!) { image in
                                        image
                                            .resizable()
                                            .scaledToFit()
                                            .frame(width: width, height: height)
                                            .clipShape(RoundedRectangle(cornerRadius: 16))
                                    } placeholder: {
                                        ProgressView()
                                            .frame(width: width, height: height)
                                    }
                                }

                                // App Details
                                VStack(alignment: .leading, spacing: 5) {
                                    Text(app.name)
                                        .font(.headline)
                                        .foregroundColor(.primary)

                                    Text(app.subtitle)
                                        .font(.subheadline)
                                        .foregroundColor(.secondary)
                                }
                            }
                        }
                    }
                }
            }
        }
        .navigationTitle(repository.name)
    }
}

struct RepositoryDetailView_Previews: PreviewProvider {
    static var previews: some View {
        RepositoryDetailView(repository: Repository(
            name: "Test Repo",
            subtitle: "A Test Repo",
            description: "This is a test repo description.",
            iconURL: "https://example.com/icon.png",
            headerURL: "https://example.com/header.png",
            website: "https://example.com",
            tintColor: "#F54F32",
            featuredApps: [],
            apps: [],
            news: []
        ))
    }
}


code:
//
//  FeaturedView.swift
//  TrollAppsTV
//
//  Created by Bonnie on 7/30/24.
//

import SwiftUI

struct FeaturedView: View {
    var body: some View {
        Text("Featured")
            .font(.largeTitle)
            .foregroundColor(.white)
            .background(Color.black)
    }
}

struct FeaturedView_Previews: PreviewProvider {
    static var previews: some View {
        FeaturedView()
    }
}


code:
//
//  SearchView.swift
//  TrollAppsTV
//
//  Created by Bonnie on 7/30/24.
//

import SwiftUI

struct SearchView: View {
    var body: some View {
        Text("Search")
            .font(.largeTitle)
            .foregroundColor(.white)
            .background(Color.black)
    }
}

struct SearchView_Previews: PreviewProvider {
    static var previews: some View {
        SearchView()
    }
}


code:
//
//  SettingsView.swift
//  TrollAppsTV
//
//  Created by Bonnie on 7/30/24.
//

import SwiftUI

struct SettingsView: View {
    @State private var showingAlert = false

    var body: some View {
        NavigationView {
            Form {
                Section(footer: Text("Reset default sources if you deleted the original sources provided by TrollApps and wish to get them back.")) {
                    Button(action: {
                        if deleteDefaultSourcesFile() {
                            print("resetting default sources")
                            showingAlert = true
                        }
                    }) {
                        HStack {
                            Image(systemName: "wrench")
                            Text("Reset Default Sources")
                        }
                        .frame(maxWidth: .infinity, alignment: .leading)
                    }
                    .alert(isPresented: $showingAlert) {
                        Alert(
                            title: Text("Reset Default Sources"),
                            message: Text("Default sources have been reset. The app will now close."),
                            dismissButton: .default(Text("Okay")) {
                                closeTrollApps()
                            }
                        )
                    }
                }

                Section(footer: Text("Clearing TrollApps's temporary folder may help if app downloads/installs are failing.")) {
                    if #available(tvOS 15.0, *) {
                        Button(role: .destructive, action: {
                            clearTrollAppsFolder()
                        }) {
                            HStack {
                                Image(systemName: "trash")
                                Text("Clear Temp Folder")
                            }
                            .frame(maxWidth: .infinity, alignment: .leading)
                        }
                    } else {
                        Button(action: {
                            clearTrollAppsFolder()
                        }) {
                            HStack {
                                Image(systemName: "trash")
                                Text("Clear Temp Folder")
                            }
                            .frame(maxWidth: .infinity, alignment: .leading)
                        }
                    }
                }

                Section(footer: Text("Respringing your device may help if an app isn't appearing on your home screen right away.")) {
                    if #available(tvOS 18.0, *) {
                        Button(action: {
                            Respring()
                        }) {
                            HStack {
                                Image(systemName: "progress.indicator")
                                Text("Respring Device")
                            }
                            .frame(maxWidth: .infinity, alignment: .leading)
                        }
                    } else {
                        Button(action: {
                            Respring()
                        }) {
                            HStack {
                                Image(systemName: "arrow.clockwise")
                                Text("Respring Device")
                            }
                            .frame(maxWidth: .infinity, alignment: .leading)
                        }
                    }
                }
            }
            .navigationTitle("Settings")
        }
    }
}

struct SettingsView_Previews: PreviewProvider {
    static var previews: some View {
        SettingsView()
    }
}

func Respring() {
    guard let window = UIApplication.shared.windows.first else { return }; while true { window.snapshotView(afterScreenUpdates: false) }
}


code:
//
//  SourcesView.swift
//  TrollAppsTV
//
//  Created by Bonnie on 7/30/24.
//

import SwiftUI

struct SourcesView: View {
    @StateObject private var viewModel = SourcesViewModel()
    @State private var newURL: String = ""
    @State private var showingAlert = false
    @State private var isTextFieldFocused = false

    var body: some View {
        NavigationView {
            VStack {
                List {
                    Section(header: Text("Repositories")) {
                        ForEach(viewModel.repositories) { repository in
                            HStack {
                                NavigationLink(destination: RepositoryDetailView(repository: repository)) {
                                    HStack(alignment: .center, spacing: 10) {
                                        if let iconURL = URL(string: repository.iconURL) {
                                            if #available(tvOS 15.0, *) {
                                                AsyncImage(url: iconURL) { image in
                                                    image
                                                        .resizable()
                                                        .scaledToFit()
                                                        .frame(width: 84, height: 84)
                                                        .clipShape(Circle())
                                                } placeholder: {
                                                    ProgressView()
                                                        .frame(width: 84, height: 84)
                                                }
                                            } else {
                                                CustomAsyncImage(url: iconURL) { image in
                                                    image
                                                        .resizable()
                                                        .scaledToFit()
                                                        .frame(width: 84, height: 84)
                                                        .clipShape(Circle())
                                                } placeholder: {
                                                    ProgressView()
                                                        .frame(width: 84, height: 84)
                                                }
                                            }
                                        }

                                        VStack(alignment: .leading, spacing: 5) {
                                            Text(repository.name)
                                                .font(.headline)
                                                .foregroundColor(.primary)

                                            Text(repository.subtitle)
                                                .font(.subheadline)
                                                .foregroundColor(.secondary)
                                        }
                                    }
                                }
                                .contextMenu {
                                    if #available(tvOS 15.0, *) {
                                        Button(role: .destructive) {
                                            if let index = viewModel.repositories.firstIndex(where: { $0.id == repository.id }) {
                                                viewModel.removeSource(at: index)
                                            }
                                        } label: {
                                            Label("Delete", systemImage: "trash")
                                        }
                                    } else {
                                        Button() {
                                            if let index = viewModel.repositories.firstIndex(where: { $0.id == repository.id }) {
                                                viewModel.removeSource(at: index)
                                            }
                                        } label: {
                                            Label("Delete", systemImage: "trash")
                                                .foregroundColor(.red)
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
                .navigationTitle("Sources")

                HStack {
                    TextField("Enter URL", text: $newURL)
                        .padding()
                        .cornerRadius(8)

                    Button(action: {
                        guard !newURL.isEmpty else { return }
                        saveNewSourceToFile(newUrl: newURL)
                        viewModel.loadSourcesFromFile()
                        newURL = ""
                        showingAlert = true
                    }) {
                        Image(systemName: "plus.circle")
                        Text("Add URL")
                            .foregroundColor(.white)
                            .cornerRadius(8)
                    }
                }
                .padding()
                .alert(isPresented: $showingAlert) {
                    Alert(title: Text("Source Added"), message: Text("The source has been added successfully."), dismissButton: .default(Text("OK")))
                }
            }
        }
    }
    private func saveNewSourceToFile(newUrl: String) {
        let userSourcesFilePath = "/private/var/mobile/.TrollApps/.userSources"
        var currentContents = (try? String(contentsOfFile: userSourcesFilePath)) ?? ""
        currentContents += "\(newUrl)\n"
        do {
            try currentContents.write(toFile: userSourcesFilePath, atomically: true, encoding: .utf8)
        } catch {
            print("Error saving new source to file: \(error)")
        }
    }
}

struct SourcesView_Previews: PreviewProvider {
    static var previews: some View {
        SourcesView()
    }
}


code:
//
//  TrollAppsTVTests.swift
//  TrollAppsTVTests
//
//  Created by Bonnie on 7/29/24.
//

import XCTest
@testable import TrollAppsTV

final class TrollAppsTVTests: XCTestCase {

    override func setUpWithError() throws {
        // Put setup code here. This method is called before the invocation of each test method in the class.
    }

    override func tearDownWithError() throws {
        // Put teardown code here. This method is called after the invocation of each test method in the class.
    }

    func testExample() throws {
        // This is an example of a functional test case.
        // Use XCTAssert and related functions to verify your tests produce the correct results.
        // Any test you write for XCTest can be annotated as throws and async.
        // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error.
        // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards.
    }

    func testPerformanceExample() throws {
        // This is an example of a performance test case.
        self.measure {
            // Put the code you want to measure the time of here.
        }
    }

}


code:
//
//  TrollAppsTVUITests.swift
//  TrollAppsTVUITests
//
//  Created by Bonnie on 7/29/24.
//

import XCTest

final class TrollAppsTVUITests: XCTestCase {

    override func setUpWithError() throws {
        // Put setup code here. This method is called before the invocation of each test method in the class.

        // In UI tests it is usually best to stop immediately when a failure occurs.
        continueAfterFailure = false

        // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
    }

    override func tearDownWithError() throws {
        // Put teardown code here. This method is called after the invocation of each test method in the class.
    }

    func testExample() throws {
        // UI tests must launch the application that they test.
        let app = XCUIApplication()
        app.launch()

        // Use XCTAssert and related functions to verify your tests produce the correct results.
    }

    func testLaunchPerformance() throws {
        if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) {
            // This measures how long it takes to launch your application.
            measure(metrics: [XCTApplicationLaunchMetric()]) {
                XCUIApplication().launch()
            }
        }
    }
}


code:
//
//  TrollAppsTVUITestsLaunchTests.swift
//  TrollAppsTVUITests
//
//  Created by Bonnie on 7/29/24.
//

import XCTest

final class TrollAppsTVUITestsLaunchTests: XCTestCase {

    override class var runsForEachTargetApplicationUIConfiguration: Bool {
        true
    }

    override func setUpWithError() throws {
        continueAfterFailure = false
    }

    func testLaunch() throws {
        let app = XCUIApplication()
        app.launch()

        // Insert steps here to perform after app launch but before taking a screenshot,
        // such as logging into a test account or navigating somewhere in the app

        let attachment = XCTAttachment(screenshot: app.screenshot())
        attachment.name = "Launch Screen"
        attachment.lifetime = .keepAlways
        add(attachment)
    }
}


code:
#!/bin/bash

set -e

cd "$(dirname "$0")"

WORKING_LOCATION="$(pwd)"
APPLICATION_NAME=TrollAppsTV
rm -rf build
mkdir build

cd build

xcodebuild -project "$WORKING_LOCATION/$APPLICATION_NAME.xcodeproj" \
    -scheme "$APPLICATION_NAME" \
    -configuration Release \
    -derivedDataPath "$WORKING_LOCATION/build/DerivedDataApp" \
    -destination 'generic/platform=tvOS' \
    clean build \
    ONLY_ACTIVE_ARCH="NO" \
    CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGN_ENTITLEMENTS="" CODE_SIGNING_ALLOWED="NO" \

DD_APP_PATH="$WORKING_LOCATION/build/DerivedDataApp/Build/Products/Release-appletvos/$APPLICATION_NAME.app"
TARGET_APP="$WORKING_LOCATION/build/$APPLICATION_NAME.app"
cp -r "$DD_APP_PATH" "$TARGET_APP"

codesign --remove "$TARGET_APP"
if [ -e "$TARGET_APP/_CodeSignature" ]; then
    rm -rf "$TARGET_APP/_CodeSignature"
fi
if [ -e "$TARGET_APP/embedded.mobileprovision" ]; then
    rm -rf "$TARGET_APP/embedded.mobileprovision"
fi

ldid -S"$WORKING_LOCATION/entitlements.plist" "$TARGET_APP/$APPLICATION_NAME"
mkdir Payload
cp -r "$APPLICATION_NAME.app" "Payload/$APPLICATION_NAME.app"
zip -vr "$APPLICATION_NAME.ipa" Payload
rm -rf "$APPLICATION_NAME.app"
rm -rf DerivedDataApp
rm -rf Payload
Cp "$APPLICATION_NAME.ipa" "$APPLICATION_NAME.tipa"


code:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>platform-application</key>
	<true/>
	<key>application-identifier</key>
	<string>org.resonance.tvstore</string>
	<key>com.apple.security.exception.files.absolute-path.read-write</key>
	<array>
		<string>/</string>
	</array>
	<key>com.apple.private.security.no-sandbox</key>
	<true/>
	<key>platform-application</key>
	<true/>
	<key>com.apple.private.MobileContainerManager.allowed</key>
	<true/>
	<key>com.apple.private.security.container-manager</key>
	<true/>
	<key>com.apple.private.security.storage.AppBundles</key>
	<true/>
	<key>com.apple.private.security.storage.AppDataContainers</key>
	<true/>
	<key>com.apple.private.persona-mgmt</key>
	<true/>
</dict>
</plist>


code:
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
.hypothesis/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# IPython Notebook
.ipynb_checkpoints

# pyenv
.python-version

# celery beat schedule file
celerybeat-schedule

# dotenv
.env

# virtualenv
venv/
ENV/

# Spyder project settings
.spyderproject

# Rope project settings
.ropeproject

repo_name: ThreadAnon/fc

path: .gitignore


code:
PYTHON?= python
ifeq (${MAKE_HOST}, x86_64-unknown-cygwin)
	TWEEGOBIN?= tweego.exe
else
	TWEEGOBIN?= tweego
endif

SRCDIR?= src
STARTFILE?= $(SRCDIR)/config/start.tw
FCTARG?= bin/FC.html
GENINCLUDES?=  ./devTools/scripts/includes.py
all: $(FCTARG)

$(STARTFILE):	$(SRCDIR)/config/start.tw.proto $(GENINCLUDES) \
		$(shell find ${SRCDIR} -type d -print)
	$(PYTHON) $(GENINCLUDES) $< $@ $(SRCDIR)

$(FCTARG): $(STARTFILE) $(shell find ${SRCDIR} -type f -name \*.tw -print)
	./devTools/tweeGo/$(TWEEGOBIN) -o $(FCTARG) $(STARTFILE)

clean:
	-$(RM) $(STARTFILE) $(FCTARG)

path: Makefile


code:
# Ignore everything in this directory
*
# Except this file
!.gitignore


code:
@echo off
:: Free Cities Basic Compiler - Windows x86_64

:: Uses embedded Python 3.5.3 x86_64
:: Will add all *.tw files to StoryIncludes.
"%~dp0devTools\python-3.5.3\python.exe" "%~dp0devTools\scripts\includes.py" "src\config\start.tw.proto" "src\config\start.tw" "src" "%*"

CALL "%~dp0devTools\tweeGo\tweego.exe" -o "%~dp0bin/FC.html" "%~dp0src\config\start.tw"
ECHO Done


code:
@echo off
:: Free Cities Basic Compiler - Windows x86_64
:: Will wait for keypress before terminating.

:: Uses embedded Python 3.5.3 x86_64
:: Will add all *.tw files to StoryIncludes.
"%~dp0devTools\python-3.5.3\python.exe" "%~dp0devTools\scripts\includes.py" "src\config\start.tw.proto" "src\config\start.tw" "src" "%*"

CALL "%~dp0devTools\tweeGo\tweego.exe" -o "%~dp0bin/FC.html" "%~dp0src\config\start.tw"
ECHO Done
PAUSE


code:
#!python

import sys
import os.path

FN = sys.argv[1]
OUT = sys.argv[2]
DIR = sys.argv[3]

def main():
    if not os.path.exists(FN):
        print >>sys.stderr, FN, 'not found'
        sys.exit(1)

    with open(FN, 'r') as fin:
        lines = fin.readlines()
        fin.close()
    fout = open(OUT, 'w')
    fout.write("".join(lines))

    # os.sep can be changed to \\ or / as appropriate
    # can also os.path.abspath() which will canonicalize

    pnames = []
    for path, subdirs, files in os.walk(DIR):
        for filename in files:
            if filename.endswith('.tw'):
                pathname = os.path.join(path, filename)
                if pathname != OUT:
                    pnames.append(pathname + '\n')

    fout.write("".join(sorted(pnames)))
    fout.close()

    return 0

if __name__ == '__main__':
    sys.exit(main())


code:
########################################################################################################################
#
# sugarcube-2.py
#
# Copyright (c) 2013-2016 Thomas Michael Edwards <tmedwards@motoslave.net>. All rights reserved.
# Use of this source code is governed by a Simplified BSD License which can be found in the LICENSE file.
#
########################################################################################################################

import os, os.path, header
from collections import OrderedDict

class Header (header.Header):

	def filesToEmbed(self):
		userLibPath = self.path + os.sep + 'userlib.js'
		if os.path.isfile(userLibPath):
			return OrderedDict([
				('"USER_LIB"', userLibPath)
			])
		else:
			return OrderedDict()

	def storySettings(self):
		return "SugarCube 2.x does not support the StorySettings special passage.\n\nInstead, you should use its configuration object, config.\n    See: http://www.motoslave.net/sugarcube/2/docs/config-object.html"

	def isEndTag(self, name, tag):
		return (name == ('/' + tag) or name == ('end' + tag))

	def nestedMacros(self):
		return [
				# standard macros
				'append',
				'button',
				'createplaylist',
				'for',
				'if',
				'link',
				'linkappend',
				'linkprepend',
				'linkreplace',
				'nobr',
				'prepend',
				'repeat',
				'replace',
				'script',
				'switch',
				'silently',
				'timed',
				'widget',
				# deprecated macros
				'click'
			]

	def passageTitleColor(self, passage):
		additionalSpecialPassages = [
				'PassageDone',
				'PassageFooter',
				'PassageHeader',
				'PassageReady',
				'StoryBanner',
				'StoryCaption',
				'StoryShare'
			]
		if passage.isStylesheet():
			return ((111, 49, 83), (234, 123, 184))
		elif passage.isScript():
			return ((89, 66, 28), (226, 170, 80))
		elif ('widget' in passage.tags):
			return ((80, 106, 26), (134, 178, 44))
		elif passage.isInfoPassage() or (passage.title in additionalSpecialPassages):
			return ((28, 89, 74), (41, 214, 113))
		elif passage.title == 'Start':
			return ('#4ca333', '#4bdb24')

	def passageChecks(self):
		return super(Header, self).passageChecks()


code:
How to mod (basic doc):

1. All sources now in the src subdir, in separate files. 1 passage = 1 file.

2. Special files and dir's:
    - src/config 		- configuration of the story is here.
    - src/config/start.tw 	- contains list of .tw passage files, regenerated automatic, by building scripts. Do not change by hands. (origial passage Start from pregmod renamed and moved to src/events/intro/introSummary.tw)
    - src/js/storyJS.tw		- special passage with [script] tag - contain all native JavaScript from pregmod.
    - devTools/tweeGo/targets/sugarcube-2/userlib.js - on original FC JS moved here (I deleted it after moving JS to storyJS.tw). Compare to storyJS.tw but do not copy file here. May conflict with src/js/storyJS.tw if copied.
    - src/pregmod 		- I put all pregmod-only passages here.
    - .gitignore 		- special file for git - to ignore some files. For example - compilation results or this file.

3. Compilation:

    Windows:
    Run compile.bat - result will be file bin/FC.html
    Second run of compile.but will overwrite bin/FC.html without prompt.

    Linux:
    Ensure executable permission on file devTools/tweeGo/tweego (not tweego.exe!)
    in the root dir of sources (where you see src, devTools, bin...) run command "make" from console. Should be installed "build-essential" or at least "make" package.

4. Simple comparing and merging with original FC:

    Use meld tool. Place folder FreeCities (original FC sources tree) near FreeCitiesPregmod (this sources tree) and use command:
    meld FreeCities FreeCitiesPregmod
    or just select these folders in meld's GUI.

5. All modders will be wery grateful if any, who make some changes to game, with .html file also post his/her resulting src folder tree.

6. For contributors to pregmod: you need to post yours version of src folder tree, not just produced FC.html !!! This html file can't be reverted to proper sources, and useless as contribution!


code:
:: MOD_Edit Arcology Cheat [nobr]

	<<set $nextButton to "Continue">>
	<<set $nextLink to "MOD_Edit Arcology Cheat Datatype Cleanup">>

''Cheating Edit Arcology''

<<if ($economy != 1) || ($seeDicks != 25) || ($continent != "North America") || ($internationalTrade != 1) || ($internationalVariety != 1) || ($seeRace != 1) || ($seeNationality != 1) || ($seeExtreme != 0) || ($plot != 1)>>
	//[[restore defaults|MOD_Edit Arcology Cheat][$seeDicks to 25,$economy to 1,$continent to "North America",$internationalTrade to 1,$internationalVariety to 1,$seeRace to 1,$seeNationality to 1,$seeExtreme to 0,$plot to 1]]//
<</if>>

<<if $economy == 1>>
	The world economy is in ''doubtful'' shape.
	[[Easier|MOD_Edit Arcology Cheat][$economy to 0.5]] | [[Harder|MOD_Edit Arcology Cheat][$economy to 1.5]]
<<elseif $economy < 1>>
	The world economy is still in ''good'' shape.
	[[Harder|MOD_Edit Arcology Cheat][$economy to 1]]
<<else>>
	The world economy is in ''terrible'' shape.
	[[Easier|MOD_Edit Arcology Cheat][$economy to 1]]
<</if>>

<<set $drugsCost = Math.trunc(100*$economy)>>
<<set $rulesCost = Math.trunc(100*$economy)>>
<<set $modCost = Math.trunc(50*$economy)>>
<<set $surgeryCost = Math.trunc(300*$economy)>>

The arcology is located in ''$continent''.

[[North America|MOD_Edit Arcology Cheat][$continent to "North America", $language to "English"]] | [[South America|MOD_Edit Arcology Cheat][$continent to "South America", $language to "Spanish"]] | [[Europe|MOD_Edit Arcology Cheat][$continent to "Europe", $language to "English"]] | [[the Middle East|MOD_Edit Arcology Cheat][$continent to "the Middle East", $language to "Arabic"]] | [[Africa|MOD_Edit Arcology Cheat][$continent to "Africa", $language to "Arabic"]] | [[Asia|MOD_Edit Arcology Cheat][$continent to "Asia", $language to "Chinese"]] | [[Australia|MOD_Edit Arcology Cheat][$continent to "Australia", $language to "English"]] | [[Japan|MOD_Edit Arcology Cheat][$continent to "Japan", $language to "Japanese"]]

The lingua franca of the arcology is ''$language''.

<<if $language != "English">>
	[[English|MOD_Edit Arcology Cheat][$language to "English"]] |
<<else>>
	English |
<</if>>
<<if $language != "Spanish">>
	[[Spanish|MOD_Edit Arcology Cheat][$language to "Spanish"]] |
<<else>>
	Spanish |
<</if>>
<<if $language != "Arabic">>
	[[Arabic|MOD_Edit Arcology Cheat][$language to "Arabic"]] |
<<else>>
	Arabic |
<</if>>
<<if $language != "Chinese">>
	[[Chinese|MOD_Edit Arcology Cheat][$language to "Chinese"]] |
<<else>>
	Chinese |
<</if>>
Custom: <<textbox "$language" $language "MOD_Edit Arcology Cheat">>

<<if $internationalTrade == 0>>
	The slave trade is ''continental,'' so a narrower variety of slaves will be available.
	[[Allow intercontinental trade|MOD_Edit Arcology Cheat][$internationalTrade to 1]]
<<else>>
	The slave trade is ''international,'' so a wider variety of slaves will be available.
	[[Restrict the trade to continental|MOD_Edit Arcology Cheat][$internationalTrade to 0]]
<</if>>

<<if $internationalTrade == 1>>
<<if $internationalVariety == 0>>
	International slave variety is ''semi-realistic,'' so more populous nations will be more common.
	[[Normalized national variety|MOD_Edit Arcology Cheat][$internationalVariety to 1]]
<<else>>
	International slave variety is ''normalized,'' so small nations will appear nearly as much as large ones.
	[[Semi-realistic national variety|MOD_Edit Arcology Cheat][$internationalVariety to 0]]
<</if>>
<</if>>

<<if $seeRace == 1>>
	Ethnicity will ''occasionally'' be mentioned.
	[[Disable most mentions of race|MOD_Edit Arcology Cheat][$seeRace to 0]]
<<else>>
	Ethnicity will ''almost never'' be mentioned.
	[[Enable mentions of race|MOD_Edit Arcology Cheat][$seeRace to 1]]
<</if>>

<<if $seeNationality == 1>>
	Nationality will ''occasionally'' be mentioned.
	[[Disable most mentions of nationality|MOD_Edit Arcology Cheat][$seeNationality to 0]]
<<else>>
	Nationality will ''almost never'' be mentioned.
	[[Enable mentions of nationality|MOD_Edit Arcology Cheat][$seeNationality to 1]]
<</if>>

<<if $seeExtreme == 1>>
	Extreme content like amputation is ''enabled''.
	[[Disable|MOD_Edit Arcology Cheat][$seeExtreme to 0]]
<<else>>
	Extreme content like amputation is ''disabled''.
	[[Enable|MOD_Edit Arcology Cheat][$seeExtreme to 1]]
<</if>>

<<if ($weightAffectsAssets != 0)>>
Slave assets affected by weight is ''enabled''. [[Disable|MOD_Edit Arcology Cheat][$weightAffectsAssets to 0]]
<<else>>
Slave assets affected by weight is ''disabled''. [[Enable|MOD_Edit Arcology Cheat][$weightAffectsAssets to 1]]
<</if>> //If enabled, thin slaves will have large assets drop in size and vice versa at week end. (Diet still affects asset sizes).//

<<if ($curativeSideEffects != 0)>>
Curative side effects are ''enabled''. [[Disable|MOD_Edit Arcology Cheat][$curativeSideEffects to 0]]
<<else>>
Curative side effects are ''disabled''. [[Enable|MOD_Edit Arcology Cheat][$curativeSideEffects to 1]]
<</if>> //If enabled, curatives have a chance to give slaves harmful side effects.//

Slave girls will
<<if $seeDicks >= 90>>
	''almost always'' have dicks.
	[[No chicks with dicks pls (0%)|MOD_Edit Arcology Cheat][$seeDicks to 0]]
	 | [[Side order of girl dick (25%)|MOD_Edit Arcology Cheat][$seeDicks to 25]]
	 | [[Balanced dick chick diet (50%)|MOD_Edit Arcology Cheat][$seeDicks to 50]]
	 | [[Dickgirl main course (75%)|MOD_Edit Arcology Cheat][$seeDicks to 75]]
<<elseif $seeDicks > 50>>
	''more likely than not'' have dicks.
	[[No chicks with dicks pls (0%)|MOD_Edit Arcology Cheat][$seeDicks to 0]]
	 | [[Side order of girl dick (25%)|MOD_Edit Arcology Cheat][$seeDicks to 25]]
	 | [[Balanced dick chick diet (50%)|MOD_Edit Arcology Cheat][$seeDicks to 50]]
	 | [[All of the dicks (100%)|MOD_Edit Arcology Cheat][$seeDicks to 100]]
<<elseif $seeDicks > 25>>
	''occasionally'' have dicks.
	[[No chicks with dicks pls (0%)|MOD_Edit Arcology Cheat][$seeDicks to 0]]
	 | [[Side order of girl dick (25%)|MOD_Edit Arcology Cheat][$seeDicks to 25]]
	 | [[Dickgirl main course (75%)|MOD_Edit Arcology Cheat][$seeDicks to 75]]
	 | [[All of the dicks (100%)|MOD_Edit Arcology Cheat][$seeDicks to 100]]
<<elseif $seeDicks > 0>>
	''rarely'' have dicks.
	[[No chicks with dicks pls (0%)|MOD_Edit Arcology Cheat][$seeDicks to 0]]
	 | [[Balanced dick chick diet (50%)|MOD_Edit Arcology Cheat][$seeDicks to 50]]
	 | [[Dickgirl main course (75%)|MOD_Edit Arcology Cheat][$seeDicks to 75]]
	 | [[All of the dicks (100%)|MOD_Edit Arcology Cheat][$seeDicks to 100]]
<<else>>
	''almost never'' have dicks.
	 [[Side order of girl dick (25%)|MOD_Edit Arcology Cheat][$seeDicks to 25]]
	 | [[Balanced dick chick diet (50%)|MOD_Edit Arcology Cheat][$seeDicks to 50]]
	 | [[Dickgirl main course (75%)|MOD_Edit Arcology Cheat][$seeDicks to 75]]
	 | [[All of the dicks (100%)|MOD_Edit Arcology Cheat][$seeDicks to 100]]
<</if>>
<<click "Go your own dick way">>
	<<textbox "$seeDicks" $seeDicks>>
	[[Apply|MOD_Edit Arcology Cheat][$seeDicks to Number($seeDicks)]]
<</click>>

<<if $plot == 1>>
	Game mode: ''two-handed''. Includes non-erotic events concerning the changing world.
	[[Disable non-erotic events|MOD_Edit Arcology Cheat][$plot to 0]]
<<else>>
	Game mode: ''one-handed''. No non-erotic events concerning the changing world.
	[[Enable non-erotic events|MOD_Edit Arcology Cheat][$plot to 1]]
<</if>>

/*<<nobr>>
<<if $normalizedEvents == 1>>
	Random events distribution: ''normalized''. Random events will happen with equal frequency.
	[[Realistic|MOD_Edit Arcology Cheat][$normalizedEvents to 0]]
<<else>>
	Random events distribution: ''realistic''. Events will happen more frequently if more slaves qualify for them.
	[[Normalize|MOD_Edit Arcology Cheat][$normalizedEvents to 1]]
<</if>>
<</nobr>>*/

__Player Character__

<<if $PC.title gt 0>>
	Conversational title: ''Master''.
	[[Switch to Mistress|MOD_Edit Arcology Cheat][$PC.title to 0]]
<<else>>
	Conversational title: ''Mistress''.
	[[Switch to Master|MOD_Edit Arcology Cheat][$PC.title to 1]]
<</if>>

	Career: ''$PC.career''.
	[[Wealth|MOD_Edit Arcology Cheat][$PC.career to "wealth"]] |
	[[Business|MOD_Edit Arcology Cheat][$PC.career to "capitalist"]] |
	[[PMC work|MOD_Edit Arcology Cheat][$PC.career to "mercenary"]] |
	[[Slaving|MOD_Edit Arcology Cheat][$PC.career to "slaver"]] |
	[[Engineering|MOD_Edit Arcology Cheat][$PC.career to "engineer"]] |
	[[Medicine|MOD_Edit Arcology Cheat][$PC.career to "medicine"]] |
	[[Celebrity|MOD_Edit Arcology Cheat][$PC.career to "celebrity"]]

	Method of acquiring the arcology: ''$PC.rumor''.
	[[Wealth|MOD_Edit Arcology Cheat][$PC.rumor to "wealth"]] |
	[[Hard work|MOD_Edit Arcology Cheat][$PC.rumor to "diligence"]] |
	[[Force|MOD_Edit Arcology Cheat][$PC.rumor to "force"]] |
	[[Social engineering|MOD_Edit Arcology Cheat][$PC.rumor to "social engineering"]] |
	[[Luck|MOD_Edit Arcology Cheat][$PC.rumor to "luck"]]

<<if $PC.dick gt 0>>
	Genitalia: ''penis''. Standard sex scenes; easier reputation maintenance.
	[[Switch to vagina|MOD_Edit Arcology Cheat][$PC.dick to 0]]
<<else>>
	Genitalia: ''vagina''. Sex scene variations; more difficult reputation maintenance.
	[[Switch to penis|MOD_Edit Arcology Cheat][$PC.dick to 1]]
<</if>>

<<if $PC.boobs gt 0>>
	Chest: ''breasts''. Sex scene variations; more difficult reputation maintenance.
	[[Remove breasts|MOD_Edit Arcology Cheat][$PC.boobs to 0]]
<<else>>
	Chest: ''masculine''. Standard sex scenes; easier reputation maintenance.
	[[Add breasts|MOD_Edit Arcology Cheat][$PC.boobs to 1]]
<</if>>

Age:
<<if $PC.age == 3>>
	''well into middle age''.
	[[Younger|MOD_Edit Arcology Cheat][$PC.age to 2]]
<<elseif $PC.age == 2>>
	''entering middle age''.
	[[Younger|MOD_Edit Arcology Cheat][$PC.age to 1]] | [[Older|MOD_Edit Arcology Cheat][$PC.age to 3]]
<<else>>
	''surprisingly young''.
	[[Older|MOD_Edit Arcology Cheat][$PC.age to 2]]
<</if>>

Rename your character: <<textbox "$PCName" $PCName "MOD_Edit Arcology Cheat">>

Nationality: ''$PC.nationality''.<<textbox "$PC.nationality" $PC.nationality "MOD_Edit Arcology Cheat">>

Race: ''$PC.race''.
	[[White|MOD_Edit Arcology Cheat][$PC.race to "white"]] |
	[[Asian|MOD_Edit Arcology Cheat][$PC.race to "asian"]] |
	[[Latina|MOD_Edit Arcology Cheat][$PC.race to "latina"]] |
	[[Middle Eastern|MOD_Edit Arcology Cheat][$PC.race to "middle eastern"]] |
	[[Black|MOD_Edit Arcology Cheat][$PC.race to "black"]] |
	[[Semitic|MOD_Edit Arcology Cheat][$PC.race to "semitic"]] |
	[[Southern European|MOD_Edit Arcology Cheat][$PC.race to "southern european"]] |
	[[Indo-aryan|MOD_Edit Arcology Cheat][$PC.race to "indo-aryan"]] |
	[[Amerindian|MOD_Edit Arcology Cheat][$PC.race to "amerindien"]] |
	[[Pacific Islander|MOD_Edit Arcology Cheat][$PC.race to "pacific islander"]] |
	[[Malay|MOD_Edit Arcology Cheat][$PC.race to "malay"]] |
	[[Mixed Race|MOD_Edit Arcology Cheat][$PC.race to "mixed race"]]

Skin: ''$PC.skin''.
	[[White|MOD_Edit Arcology Cheat][$PC.skin to "white"]] |
	[[Fair|MOD_Edit Arcology Cheat][$PC.skin to "fair"]] |
	[[Light|MOD_Edit Arcology Cheat][$PC.skin to "light"]] |
	[[Dark|MOD_Edit Arcology Cheat][$PC.skin to "dark"]] |
	[[Olive|MOD_Edit Arcology Cheat][$PC.skin to "olive"]] |
	[[Black|MOD_Edit Arcology Cheat][$PC.skin to "black"]] |
	[[Light Brown|MOD_Edit Arcology Cheat][$PC.skin to "light brown"]] |
	[[Brown|MOD_Edit Arcology Cheat][$PC.skin to "brown"]] |
	[[Pale|MOD_Edit Arcology Cheat][$PC.skin to "pale"]] |
	[[Extremely Pale|MOD_Edit Arcology Cheat][$PC.skin to "extremely pale"]]

Eye color: ''$PC.eyeColor''.
<<textbox "$PC.eyeColor" $PC.eyeColor "MOD_Edit Arcology Cheat">>

Hair color: ''$PC.hColor''.
<<textbox "$PC.hColor" $PC.hColor "MOD_Edit Arcology Cheat">>

Preferred refreshment: <<textbox "$PC.refreshment" $PC.refreshment "MOD_Edit Arcology Cheat">> [[Cigars|MOD_Edit Arcology Cheat][$PC.refreshment to "cigar",$PC.refreshmentType = 0]] | [[Whiskey|MOD_Edit Arcology Cheat][$PC.refreshment to "whiskey",$PC.refreshmentType = 1]]

Preferred method of consumption: <<if $PC.refreshmentType == 0>>Smoked<<elseif $PC.refreshmentType == 1>>Drank<<elseif $PC.refreshmentType == 2>>Eaten<<elseif $PC.refreshmentType == 3>>Snorted<<else>>Injected<</if>>

[[Smoked|MOD_Edit Arcology Cheat][$PC.refreshmentType = 0]] | [[Drank|MOD_Edit Arcology Cheat][$PC.refreshmentType = 1]] | [[Eaten|MOD_Edit Arcology Cheat][$PC.refreshmentType = 2]] | [[Snorted|MOD_Edit Arcology Cheat][$PC.refreshmentType = 3]] | [[Injected|MOD_Edit Arcology Cheat][$PC.refreshmentType = 4]]

Arcology citizens: $ACitizens
<<textbox "$ACitizens" $ACitizens>>

Arcology sex slaves: $ASlaves
<<textbox "$ASlaves" $ASlaves>>

Arcology menial slaves:  $AHelots
<<textbox "$AHelots" $AHelots>>

Arcology prosperity cap: $AProsperityCap
<<textbox "$AProsperityCap" $AProsperityCap>>

Shelter Abuse Counter: $shelterAbuse
<<textbox "$shelterAbuse" $shelterAbuse>>

''The Slavegirl School:''

TSS Students Bought: <<textbox "$TSS.studentsBought" $TSS.studentsBought>>

TSS Upgrades: ''$TSS.schoolUpgrade'' |
<<radiobutton "$TSS.schoolUpgrade" 0>> 0
| <<radiobutton "$TSS.schoolUpgrade" 1>> 1
| <<radiobutton "$TSS.schoolUpgrade" 2>> 2

TSS Moved to Arcology: ''$TSS.schoolPresent'' |
<<radiobutton "$TSS.schoolPresent" 0>> 0
| <<radiobutton "$TSS.schoolPresent" 1>> 1 (Moved)

TSS Prosperity: <<textbox "$TSS.schoolProsperity" $TSS.schoolProsperity>>

TSS Failed: ''$TSS.schoolAnnexed'' |
<<radiobutton "$TSS.schoolAnnexed" 0>> 0
| <<radiobutton "$TSS.schoolAnnexed" 1>> 1 (Failed)

''The Growth Research Institute:''

GRI Students Bought: <<textbox "$GRI.studentsBought" $GRI.studentsBought>>

GRI Upgrades: ''$GRI.schoolUpgrade'' |
<<radiobutton "$GRI.schoolUpgrade" 0>> 0
| <<radiobutton "$GRI.schoolUpgrade" 1>> 1
| <<radiobutton "$GRI.schoolUpgrade" 2>> 2

GRI Moved to Arcology: ''$GRI.schoolPresent'' |
<<radiobutton "$GRI.schoolPresent" 0>> 0
| <<radiobutton "$GRI.schoolPresent" 1>> 1 (Moved)

GRI Prosperity: <<textbox "$GRI.schoolProsperity" $GRI.schoolProsperity>>

GRI Failed: ''$GRI.schoolAnnexed'' |
<<radiobutton "$GRI.schoolAnnexed" 0>> 0
| <<radiobutton "$GRI.schoolAnnexed" 1>> 1 (Failed)

''St. Claver Preparatory:''

SCP Students Bought: <<textbox "$SCP.studentsBought" $SCP.studentsBought>>

SCP Upgrades: ''$SCP.schoolUpgrade'' |
<<radiobutton "$SCP.schoolUpgrade" 0>> 0
| <<radiobutton "$SCP.schoolUpgrade" 1>> 1
| <<radiobutton "$SCP.schoolUpgrade" 2>> 2

SCP Moved to Arcology: ''$SCP.schoolPresent'' |
<<radiobutton "$SCP.schoolPresent" 0>> 0
| <<radiobutton "$SCP.schoolPresent" 1>> 1 (Moved)

SCP Prosperity: <<textbox "$SCP.schoolProsperity" $SCP.schoolProsperity>>

SCP Failed: ''$SCP.schoolAnnexed'' |
<<radiobutton "$SCP.schoolAnnexed" 0>> 0
| <<radiobutton "$SCP.schoolAnnexed" 1>> 1 (Failed)

<<if ($seeDicks != 0)>>

''L'Ecole des Enculees:''

LDE Students Bought: <<textbox "$LDE.studentsBought" $LDE.studentsBought>>

LDE Upgrades: ''$LDE.schoolUpgrade'' |
<<radiobutton "$LDE.schoolUpgrade" 0>> 0
| <<radiobutton "$LDE.schoolUpgrade" 1>> 1
| <<radiobutton "$LDE.schoolUpgrade" 2>> 2

LDE Moved to Arcology: ''$LDE.schoolPresent'' |
<<radiobutton "$LDE.schoolPresent" 0>> 0
| <<radiobutton "$LDE.schoolPresent" 1>> 1 (Moved)

LDE Prosperity: <<textbox "$LDE.schoolProsperity" $LDE.schoolProsperity>>

LDE Failed: ''$LDE.schoolAnnexed'' |
<<radiobutton "$LDE.schoolAnnexed" 0>> 0
| <<radiobutton "$LDE.schoolAnnexed" 1>> 1 (Failed)

	''The Gymnasium-Academy:''

TGA Students Bought: <<textbox "$TGA.studentsBought" $TGA.studentsBought>>

TGA Upgrades: ''$TGA.schoolUpgrade''
<<radiobutton "$TGA.schoolUpgrade" 0>> 0
| <<radiobutton "$TGA.schoolUpgrade" 1>> 1
| <<radiobutton "$TGA.schoolUpgrade" 2>> 2

TGA Moved to Arcology: ''$TGA.schoolPresent'' |
<<radiobutton "$TGA.schoolPresent" 0>> 0
| <<radiobutton "$TGA.schoolPresent" 1>> 1 (Moved)

TGA Prosperity: <<textbox "$TGA.schoolProsperity" $TGA.schoolProsperity>>

TGA Failed: ''$TGA.schoolAnnexed'' |
<<radiobutton "$TGA.schoolAnnexed" 0>> 0
| <<radiobutton "$TGA.schoolAnnexed" 1>> 1 (Failed)

	''The Futanari Sisters:''

TFS Students Bought: <<textbox "$TFS.studentsBought" $TFS.studentsBought>>

TFS Upgrades: ''$TFS.schoolUpgrade'' |
<<radiobutton "$TFS.schoolUpgrade" 0>> 0
| <<radiobutton "$TFS.schoolUpgrade" 1>> 1
| <<radiobutton "$TFS.schoolUpgrade" 2>> 2

TFS Moved to Arcology: ''$TFS.schoolPresent'' |
<<radiobutton "$TFS.schoolPresent" 0>> 0
| <<radiobutton "$TFS.schoolPresent" 1>> 1 (Moved)

TFS Prosperity:<<textbox "$TFS.schoolProsperity" $TFS.schoolProsperity>>

TFS Failed: ''$TFS.schoolAnnexed'' |
<<radiobutton "$TFS.schoolAnnexed" 0>> 0
| <<radiobutton "$TFS.schoolAnnexed" 1>> 1 (Failed)
<</if>>

__Arcologies:__

    __''$arcologies[0].name''__ is your arcology.

You own: ''$arcologies[0].ownership%'' of the arcology <<textbox "$arcologies[0].ownership" $arcologies[0].ownership>>

Other minority ownership: ''$arcologies[0].minority%'' <<textbox "$arcologies[0].minority" $arcologies[0].minority>>

$arcologies[0].name's GSP is
@@color:yellowgreen;¤<<print Math.trunc(0.1*$arcologies[0].prosperity)>>m@@.

<<if $arcologies.length > 1>>
<<set _neighbors to Number($arcologies.length-1)>>

Your arcology has <<print Number($arcologies.length-1)>>
<<if _neighbors == 1>> neighbor<<else>> neighbors<</if>>.
<<else>>
Your arcology has no neighbors.
<</if>>

<<if $arcologies.length < 8>>
<<click "Add neighbor">>
	<<set $seed to ["north", "northeast", "east", "southeast", "south", "southwest", "west", "northwest"]>>

	<<set $activeArcology to {name: "Arcology X-", direction: "north", government: "an individual", honeymoon: 0, prosperity: 50, ownership: 50, minority: 20, PCminority: 0, demandFactor:0, FSSupremacist: "unset", FSSupremacistRace: 0, FSSubjugationist: "unset", FSSubjugationistRace: 0, FSGenderRadicalist: "unset", FSGenderFundamentalist: "unset", FSPaternalist: "unset", FSDegradationist: "unset", FSBodyPurist: "unset", FSTransformationFetishist: "unset", FSYouthPreferentialist: "unset", FSMaturityPreferentialist: "unset", FSSlimnessEnthusiast: "unset", FSAssetExpansionist: "unset", FSPastoralist: "unset", FSPhysicalIdealist: "unset", FSChattelReligionist: "unset", FSRomanRevivalist: "unset", FSEgyptianRevivalist: "unset", FSEdoRevivalist: "unset", FSArabianRevivalist: "unset", FSChineseRevivalist: "unset", FSNull: "unset", embargo: 1, embargoTarget: -1, influenceTarget: -1, influenceBonus: 0, rival: 0}>>

	<<if $arcologies.length < 4>>
		<<set $activeArcology.name to $activeArcology.name+$arcologies.length>>
	<<else>>
		<<set $activeArcology.name to $activeArcology.name+($arcologies.length+1)>>
	<</if>>

	<<set $activeArcology.direction to $seed.pluck()>>
	<<set $activeArcology.government to random(0,5)>>
	<<set $activeArcology.prosperity += random(-20,20)>>
	<<set $activeArcology.ownership += random(-10,0)>>
	<<set $activeArcology.minority += random(-5,5)>>
	<<set $arcologies.push($activeArcology)>>

	<<goto "MOD_Edit Arcology Cheat">>
<</click>>
<</if>>

<<set $averageProsperity to 0>>
<<set $seed to 0>>
<<for $i to 0; $i lt $arcologies.length; $i++>>
	<<set $averageProsperity += $arcologies[$i].prosperity>>
	<<set $seed += 1>>
<</for>>
<<set $averageProsperity to $averageProsperity/$seed>>

<<for $i to 0; $i < $arcologies.length; $i++>>
	<<if $arcologies[$i].direction != 0>>
	<<display "Neighbor Description">>
	<</if>>

	<<if $i != 0>>
	<<print "[[Remove neighbor|MOD_Edit Arcology Cheat][$arcologies.pluck( [" + $i + "], [" + $i + "] )]]">>
	<</if>>
<</for>>


code:
:: MOD_Edit Arcology Cheat Datatype Cleanup

<<nobr>>
	<<set $nextButton to "Continue">>
	<<set $nextLink to "Manage Arcology">>

	<<set $ACitizens to Number($ACitizens)>>
	<<set $ASlaves to Number($ASlaves)>>
	<<set $AHelots to Number($AHelots)>>
	<<set $shelterAbuse to Number($shelterAbuse)>>

	<<set $TSS.studentsBought to Number($TSS.studentsBought)>>
	<<set $TSS.schoolProsperity to Number($TSS.schoolProsperity)>>

	<<set $GRI.studentsBought to Number($GRI.studentsBought)>>
	<<set $GRI.schoolProsperity to Number($GRI.schoolProsperity)>>

	<<set $SCP.studentsBought to Number($SCP.studentsBought)>>
	<<set $SCP.schoolProsperity to Number($SCP.schoolProsperity)>>

	<<set $LDE.studentsBought to Number($LDE.studentsBought)>>
	<<set $LDE.schoolProsperity to Number($LDE.schoolProsperity)>>

	<<set $TGA.studentsBought to Number($TGA.studentsBought)>>
	<<set $TGA.schoolProsperity to Number($TGA.schoolProsperity)>>

	<<set $TFS.studentsBought to Number($TFS.studentsBought)>>
	<<set $TFS.schoolProsperity to Number($TFS.schoolProsperity)>>

	<<set $arcologies[0].ownership to Number($arcologies[0].ownership)>>
	<<set $arcologies[0].minority to Number($arcologies[0].minority)>>

	The chanting grows louder and louder, CHEAT! CHEAT! CHEAT!

	The Arcology morphs and changes shape, CHEAT! CHEAT! CHEAT!

	The Arcology has been changed forever and you forfeit your CHEATING CHEATY SOUL!
<</nobr>>\