/*
 * This file is part of LSPosed.
 *
 * LSPosed is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * LSPosed is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with LSPosed.  If not, see <https://www.gnu.org/licenses/>.
 *
 * Copyright (C) 2020 EdXposed Contributors
 * Copyright (C) 2021 - 2022 LSPosed Contributors
 */

package org.lsposed.lspd.core;

import android.app.ActivityThread;
import android.app.LoadedApk;
import android.content.pm.ApplicationInfo;
import android.content.res.CompatibilityInfo;

import com.android.internal.os.ZygoteInit;

import org.lsposed.lspd.deopt.PrebuiltMethodsDeopter;
import org.lsposed.lspd.hooker.AttachHooker;
import org.lsposed.lspd.hooker.CrashDumpHooker;
import org.lsposed.lspd.hooker.HandleSystemServerProcessHooker;
import org.lsposed.lspd.hooker.LoadedApkCtorHooker;
import org.lsposed.lspd.hooker.LoadedApkCreateCLHooker;
import org.lsposed.lspd.hooker.OpenDexFileHooker;
import org.lsposed.lspd.impl.LSPosedContext;
import org.lsposed.lspd.impl.LSPosedHelper;
import org.lsposed.lspd.service.ILSPApplicationService;
import org.lsposed.lspd.util.Utils;

import java.util.List;

import dalvik.system.DexFile;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedInit;

public class Startup {
    private static void startBootstrapHook(boolean isSystem) {
        Utils.logD("startBootstrapHook starts: isSystem = " + isSystem);
        LSPosedHelper.hookMethod(CrashDumpHooker.class, Thread.class, "dispatchUncaughtException", Throwable.class);
        if (isSystem) {
            LSPosedHelper.hookAllMethods(HandleSystemServerProcessHooker.class, ZygoteInit.class, "handleSystemServerProcess");
        } else {
            LSPosedHelper.hookAllMethods(OpenDexFileHooker.class, DexFile.class, "openDexFile");
            LSPosedHelper.hookAllMethods(OpenDexFileHooker.class, DexFile.class, "openInMemoryDexFile");
            LSPosedHelper.hookAllMethods(OpenDexFileHooker.class, DexFile.class, "openInMemoryDexFiles");
        }
        LSPosedHelper.hookConstructor(LoadedApkCtorHooker.class, LoadedApk.class,
                ActivityThread.class, ApplicationInfo.class, CompatibilityInfo.class,
                ClassLoader.class, boolean.class, boolean.class, boolean.class);
        LSPosedHelper.hookMethod(LoadedApkCreateCLHooker.class, LoadedApk.class, "createOrUpdateClassLoaderLocked", List.class);
        LSPosedHelper.hookAllMethods(AttachHooker.class, ActivityThread.class, "attach");
    }

    public static void bootstrapXposed() {
        // Initialize the Xposed framework
        try {
            Utils.logD("Startup.bootstrapXposed starts: startsSystemServer = " + XposedInit.startsSystemServer
                    + ", processName = " + LSPosedContext.processName
                    + ", appDir = " + LSPosedContext.appDir);
            startBootstrapHook(XposedInit.startsSystemServer);
            Utils.logD("Startup.bootstrapXposed: bootstrap hooks installed");
            XposedInit.loadLegacyModules();
            Utils.logD("Startup.bootstrapXposed: legacy modules loaded");
        } catch (Throwable t) {
            Utils.logE("error during Xposed initialization", t);
        }
    }

    public static void initXposed(boolean isSystem, String processName, String appDir, ILSPApplicationService service) {
        // init logger
        Utils.logD("Startup.initXposed starts: isSystem = " + isSystem
                + ", processName = " + processName
                + ", appDir = " + appDir
                + ", service = " + service);
        Utils.logD("Startup.initXposed: ApplicationServiceClient.Init start");
        ApplicationServiceClient.Init(service, processName);
        Utils.logD("Startup.initXposed: ApplicationServiceClient.Init done");
        Utils.logD("Startup.initXposed: XposedBridge.initXResources start");
        XposedBridge.initXResources();
        Utils.logD("Startup.initXposed: XposedBridge.initXResources done");
        XposedInit.startsSystemServer = isSystem;
        LSPosedContext.isSystemServer = isSystem;
        LSPosedContext.appDir = appDir;
        LSPosedContext.processName = processName;
        Utils.logD("Startup.initXposed: context fields set isSystemServer = " + LSPosedContext.isSystemServer
                + ", processName = " + LSPosedContext.processName
                + ", appDir = " + LSPosedContext.appDir);
        Utils.logD("Startup.initXposed: PrebuiltMethodsDeopter.deoptBootMethods start");
        PrebuiltMethodsDeopter.deoptBootMethods(); // do it once for secondary zygote
        Utils.logD("Startup.initXposed: PrebuiltMethodsDeopter.deoptBootMethods done");
    }
}
