// Assassins Creed III & Liberation Remastered autosplitter & load remover made by TpRedNinja
// People who helped me: Akilogames, DeathHound, Mysterion and his tutorial, a lot of people from the speedrun tool development discord server

//[133480] 59396096 
state("ACIII", "AC3 Steam")
{
    bool IsLoading: 0x03269D90, 0x468, 0xB0, 0xC8, 0x370; // 0 for not loading and 1 for loading note the cutscene with connor cutting his hair counts as loading
    int percentage: 0x03269D88, 0xC60, 0xB18, 0x690, 0x4; // displays current percentage complete note does not work with tyranny of king washington as it displays 24 for some reason
    int IGT: 0x03203510, 0x850, 0x8, 0x3D8, 0x80; // Uses the built in timer for each save file note its in seconds for display but can be used for the timer.
    int MissionComplete: 0x32C2270;
}
//[133480] 53477376 
state("ACLiberation", "ACL Steam")
{
    int percentage: 0x02C0EC88, 0x280, 0x10, 0x2CC; // detects overall completion % value
    int menu: 0x02BCB4C0, 0x80, 0xC0, 0x4DC; // detects if you're in a menu or in-game/cutscene. 32756 when in pause menu or loading & main, any other number below when not, note when selecting save it's below 32756.
    int pausemenu: 0x2C0EDD0; // detects if you're in the pause menu. 1 for if you are 0 if you aren't
    int IGT: 0x2C0D0B0; // somehow game time is 4 bytes idk why.
    // Note IGT // it will pause if the pause menu is open or if you go back to the main menu
    bool IsLoading: 0x02BDAC70, 0xBB0, 0xA28, 0x10, 0xF58; // detects if you're in the loading screen or not, 1 for is 0 for not, IGT goes on during unskippable cutscene and cutscene isn't considered loading
    int currency: 0x02C26C80, 0x478, 0xB0, 0x98; // detects currency, becomes 4294967295 when loading into the game but changes back, but when leaving the game it becomes ??
}
//[133480] 59396096 
state("ACIII", "AC3 UbisoftConnect")
{
    bool IsLoading: 0x03206FC8, 0x48, 0x2F8, 0x38, 0x30;
    int percentage: 0x03269CC8, 0xC20, 0xB40, 0x6B8, 0x4;
    int IGT: 0x03203450, 0x738, 0x2B0, 0x398, 0x80; //stops when in pausemenu during loading it doesnt for some reason.
    int MissionComplete: 0x32C21B0; //detects the mission end screen but does not detect sequence complete. 0 for false 1 for true
    int Cutscene: 0x03269CD0, 0x1A8, 0x88, 0x0, 0x928; //0 when first in the main menu but 4 for when no cutscene adn 65540 or something like that during a cutscene
}

state("ACLiberation", "ACL UbisoftConnect")
{
    int percentage: 0x02BC5F00, 0x2D0, 0xEC;
    int menu: 0x02BC7F50, 0x74C; // same condition as steam 
    int pausemenu: 0x02C0ED10; // just an address does changing persona's screen as paused
    int IGT: 0x02C0CF00, 0xE8; // steam was just an address Ubisoft is a full-on pointer
    bool IsLoading: 0x02C26F98, 0x0, 0x200;
    int currency: 0x02C26BC0, 0xB0, 0x98;
}

startup
{
    // credit to deathhound246 on discord for allowing me to copy some of the code from the RE2R autosplitter. was very helpful in making this.
    // ubisoft connect hash id(ACL): SHA256: 7a4b62b1ebe7ce7b5a54d7265ba8a0e1a3151c6c3ab87342506d359429c075c9
    vars.aclubisoftconnect = new byte[32]{ 0x7a, 0x4b, 0x62, 0xb1, 0xeb, 0xe7, 0xce, 0x7b, 0x5a, 0x54, 0xd7, 0x26, 0x5b, 0xa8, 0xa0, 0xe1, 0xa3, 0x15, 0x1c, 0x6c, 0x3a, 0xb8, 0x73, 0x42, 0x50, 0x6d, 0x35, 0x94, 0x29, 0xc0, 0x75, 0xc9 };
    // ubisoft connect hash id(ACIII): SHA256: 8e414119ee22d300b4acdaa5a15fd2d02482b7a51ba803370618920a94a1dc0d
    vars.ac3ubisoftconnect = new byte[32]{ 0x8e, 0x41, 0x41, 0x19, 0xee, 0x22, 0xd3, 0x00, 0xb4, 0xac, 0xda, 0xa5, 0xa1, 0x5f, 0xd2, 0xd0, 0x24, 0x82, 0xb7, 0xa5, 0x1b, 0xa8, 0x03, 0x37, 0x06, 0x18, 0x92, 0x0a, 0x94, 0xa1, 0xdc, 0x0d };
    // steam hash id: SHA256(ACL): 87836d1759dd4dae57eff66aa170c07c4c5e6b817632379e493d2929adebe947
    vars.aclsteam = new byte[32]{ 0x87, 0x83, 0x6d, 0x17, 0x59, 0xdd, 0x4d, 0xae, 0x57, 0xef, 0xf6, 0x6a, 0xa1, 0x70, 0xc0, 0x7c, 0x4c, 0x5e, 0x6b, 0x81, 0x76, 0x32, 0x37, 0x9e, 0x49, 0x3d, 0x29, 0x29, 0xad, 0xeb, 0xe9, 0x47 };
    // steam hash id(ACIII): SHA256: 450b76dea323089077a8bb8f0a9bafd3b7c02f48a46de48811f2f00b63aa13d1
    vars.ac3steam = new byte[32]{ 0x45, 0x0b, 0x76, 0xde, 0xa3, 0x23, 0x08, 0x90, 0x77, 0xa8, 0xbb, 0x8f, 0x0a, 0x9b, 0xaf, 0xd3, 0xb7, 0xc0, 0x2f, 0x48, 0xa4, 0x6d, 0xe4, 0x88, 0x11, 0xf2, 0xf0, 0x0b, 0x63, 0xaa, 0x13, 0xd1 };

    // calculates the hash id for the current module credit to the re2r autosplitter & deathHound246 on discord for this code 
    Func<ProcessModuleWow64Safe, byte[]> CalcModuleHash = (module) => 
    {
        print("Calculating hash of " + module.FileName);
        byte[] checksum = new byte[32];
        using (var hashFunc = System.Security.Cryptography.SHA256.Create())
            using (var fs = new FileStream(module.FileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete))
                checksum = hashFunc.ComputeHash(fs);
        return checksum;
    };
    vars.CalcModuleHash = CalcModuleHash;


    //Literally a setting to split on every mission
    settings.Add("Splitting", false, "Splitting");
        settings.Add("Mission", true, "Mission", "Splitting");
        settings.SetToolTip("Mission", "Choose this if you want to split after a mission is completed. \n" + "will not split for modern day sections");
        settings.Add("Some Missions", false, "Some Missions", "Splitting");
        settings.SetToolTip("Some Missions", "Choose this if you want to split after some missions are completed. \n" + "will not split for liberation\n" +
        "Missions it will split on: ");

    // Asks the user if they want to change to game time if the comparison is set to real time on startup.
    if(timer.CurrentTimingMethod == TimingMethod.RealTime)
    {        
        var timingMessage = MessageBox.Show(
            "This Autosplitter has a load removal Time without loads. "+
            "LiveSplit is currently set to display and compare against Real Time (including loads).\n\n"+
            "Would you like the timing method to be set to Game Time?",
            "Assassin's Creed III Remastered/Liberation Remastered | LiveSplit",
            MessageBoxButtons.YesNo, MessageBoxIcon.Question
        );
        if (timingMessage == DialogResult.Yes)
            timer.CurrentTimingMethod = TimingMethod.GameTime;
    };

    // set text taken from Poppy Platime C2
    // to display the text associated with this script aka current percentage along with IGT
    Action<string, string> SetTextComponent = (id, text) => {
        var textSettings = timer.Layout.Components.Where(x => x.GetType().Name == "TextComponent").Select(x => x.GetType().GetProperty("Settings").GetValue(x, null));
        var textSetting = textSettings.FirstOrDefault(x => (x.GetType().GetProperty("Text1").GetValue(x, null) as string) == id);
        if (textSetting == null)
        {
            var textComponentAssembly = Assembly.LoadFrom("Components\\LiveSplit.Text.dll");
            var textComponent = Activator.CreateInstance(textComponentAssembly.GetType("LiveSplit.UI.Components.TextComponent"), timer);
            timer.Layout.LayoutComponents.Add(new LiveSplit.UI.Components.LayoutComponent("LiveSplit.Text.dll", textComponent as LiveSplit.UI.Components.IComponent));

            textSetting = textComponent.GetType().GetProperty("Settings", BindingFlags.Instance | BindingFlags.Public).GetValue(textComponent, null);
            textSetting.GetType().GetProperty("Text1").SetValue(textSetting, id);
        }

        if (textSetting != null)
            textSetting.GetType().GetProperty("Text2").SetValue(textSetting, text);
    };
    vars.SetTextComponent = SetTextComponent;
    vars.game = "None";
}

init
{
    // Detects the game version based on SHA-256 hash for Assassin's Creed III & Liberation Remastered
    byte[] checksum = vars.CalcModuleHash(modules.First());
    if (Enumerable.SequenceEqual(checksum, vars.aclsteam))
    {
        version = "ACL Steam";
        vars.game = "ACL";
    } else if(Enumerable.SequenceEqual(checksum, vars.aclubisoftconnect))
    { 
        version = "ACL UbisoftConnect";
        vars.game = "ACL";
    } else if (Enumerable.SequenceEqual(checksum, vars.ac3steam))
    {
        version = "AC3 Steam";
        vars.game = "AC3";
    } else if(Enumerable.SequenceEqual(checksum, vars.ac3ubisoftconnect))
    { 
        version = "AC3 UbisoftConnect";
        vars.game = "AC3";
    } else
    {
        version = "Unknown";
        vars.game = "None";
    }
}

update
{
    //print(modules.First().ModuleMemorySize.ToString());
    if (current.percentage != null)
    {
        vars.SetTextComponent("Percentage Completion", current.percentage + "%");
    }
}

start
{
    // starts when first skippable cutscene appears should be no delay
    if (vars.game == "ACL" && !current.IsLoading && current.menu == 0 && current.IGT > 0 && old.IGT == 0 && current.percentage == 0)
    {
        return true;
    }

    //starts when you have control of desmond hopefully
    if(vars.game == "AC3" && !current.IsLoading && current.percentage == 0)
    {
        return true;
    }
}

split
{
    // splits after every mission that gives you percentage note some missions dont have a end mission screen so make sure you have enough splits
    if(current.percentage > old.percentage && settings["Mission"])
    {
        return true;
    }

    if (vars.game == "AC3" && current.MissionComplete == 1 && old.MissionComplete == 0 && settings["Some Missions"])
    {
        return true;
    }

}

isLoading
{
   return current.IsLoading;
}