Skip to content

Commit

Permalink
Merge pull request #21 from MopsieX/release/release-1.5
Browse files Browse the repository at this point in the history
Release/release 1.5
  • Loading branch information
TimeMaster18 authored Jan 2, 2020
2 parents d456ea6 + 76d7d12 commit cab1f64
Show file tree
Hide file tree
Showing 9 changed files with 691 additions and 160 deletions.
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,21 @@ This project is still in its early stages, much more will hopefully come soon :)
- Play with Max
- Choose traps and gear
- Choose your skin and your dye
- Choose Guardians
- New extra difficulties for every map
- New Mods:
- - Remove Trap Cap
- - Edit the starting coin
- - God Mode
- Automatic file backups
- Reset configuration button
- More coming soon
- Reset configuration button and config saves

### Coming Next
- Trap Tiers
- Play with other starter heroes like Gabriella and Smolder
- Save multiple loadouts

### Known Problems
- Hero HP doesn't scale as expected
- UI of the launcher is lacking

178 changes: 128 additions & 50 deletions SingleplayerLauncher/Hero.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.Linq;
using System.Windows.Forms;

namespace SingleplayerLauncher
{
Expand All @@ -20,35 +22,60 @@ public static Hero Instance
}
}

public string name { get; set; }
private int objectOffset { get; set; }
public UPKFile UPKFile { get; set; }
private byte[] skinPattern;
public string skin { get; set; }

public string[] loadout { get; set; } // Could be it's own class if we get Guardians and Traits to work.
private byte[] loadoutHeader;
public string Skin { get; set; }
public string[] Loadout { get; set; } // Could be it's own class if we get Guardians and Traits to work.
public string[] Guardians { get; set; }

private const int LoadoutSlotByteSize = 4;
private const int LoadoutSlotsNumber = 9;
private const int LoadoutSlotsNumber = 9;
// Where the actual array of the loadout starts is + 12 bytes from the loadout header.
// Array Size in bytes + Array (start?) index + Array number of elements (4 + 4 + 4 )
private const int LoadoutOffsetFromHeader = 12;

// TODO Add resource file with the rest of heroes and remove this (and use above ones)
private const int GuardianSlotsNumber = 2;
// Where the actual array of the guardians starts is + 12 bytes from the loadout header.
// Array Size in bytes + Array (start?) index + Array number of elements (4 + 4 + 4 )
private const int GuardiansOffsetFromHeader = 12;

// TODO Add resource file with the rest of heroes and remove this (and use above ones only, the rest to resource files)
private const string NameMaximilian = "Maximilian";
private int HeroObjectOffsetMaximilian = 0x28AB495; // Within the file offset where it starts
private const string SpitfireGameUPKMaximilian = "..//SpitfireGame//CookedPCConsole//SpitfireGame.upk";
private const int HeroObjectOffsetMaximillian = 0x28AB495; // Within the file offset where it starts (Maximillian currently default)
private const int HeroObjectSizeMaximillian = 1542;
// private const string SpitfireGameUPKMaximilian = "..//SpitfireGame//CookedPCConsole//SpitfireGame.upk";
private static readonly byte[] LoadoutHeaderMaximilian = new byte[] { 0xC6, 0x2C, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0xC5, 0x07, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00
};
private static readonly byte[] StartHeaderAfterLoadoutMaximilian = new byte[] { 0xF3, 0x2C, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0xC5, 0x07, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00
};
private static readonly byte[] WavesHeaderMaximillian = new byte[] { 0xF3, 0x2c, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00
private static readonly byte[] GuardiansHeaderMaximillian = new byte[] { 0xBA, 0x2C, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0xC5, 0x07, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00
};
private static readonly int DefaultWaveClassesSectionLength = 64;
private static readonly byte[] DefaultWaveClassesHeaderMaximillian = new byte[] {
0xF3, 0x2c, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00
};
private static readonly int HeroDamageTypeSectionLength = 40;
private static readonly byte[] HeroDamageTypeHeaderMaximillian = new byte[] {
0x0E, 0x46, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00
};
private static readonly int StrategicRoleSectionLength = 40;
private static readonly byte[] StrategicRoleHeaderMaximillian = new byte[] {
0x74, 0xA3, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00
};
private static readonly int DefaultRoleClassSectionLength = 28;
private static readonly byte[] DefaultRoleClassHeaderMaximillian = new byte[] {
0xE4, 0x2C, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00
};


private static readonly byte[] SkinPatternMaximilian = new byte[] { 0x52, 0x1E, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x6B, 0x66, 0x00, 0x00,
Expand All @@ -64,56 +91,106 @@ public static Hero Instance
0x05, 0x00, 0x00, 0x00,
0x41, 0xCC, 0x02, 0x00
};
private static readonly byte[] StartHeaderAfterGuardiansMaximillian = new byte[] { };

public void ApplySkin()
{
int skinIndex = UPKFile.FindBytesKMP(SkinPatternMaximilian, HeroObjectOffsetMaximilian) + SkinPatternMaximilian.Length;
UPKFile.OverrideBytes(Resources.skins[NameMaximilian][skin], skinIndex);
}


private static readonly byte[] WeaverTreeDefaultHeaderMaximillian = new byte[] { 0x00, 0xB4, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00
};
private static readonly byte[] StartHeaderAfterGuardiansMaximillian = WeaverTreeDefaultHeaderMaximillian;

public void ApplyLoadoutChanges()
{
RemoveByteSection(DefaultWaveClassesHeaderMaximillian, DefaultWaveClassesSectionLength);
RemoveByteSection(HeroDamageTypeHeaderMaximillian, HeroDamageTypeSectionLength);
RemoveByteSection(StrategicRoleHeaderMaximillian, StrategicRoleSectionLength);
RemoveByteSection(DefaultRoleClassHeaderMaximillian, DefaultRoleClassSectionLength);

ApplyTrapsGear();
//ApplyGuardians();
//ApplyTraits();
//ApplySkin();
ApplySkin();

// FillRemovedBytes should be run after all the removing
if (UPKFile.nBytesRemoved > 0)
{
int positionToFillRemovedBytesWithZeros = UPKFile.FindBytesKMP(StartHeaderAfterGuardiansMaximillian, HeroObjectOffsetMaximillian, HeroObjectSizeMaximillian);
FillRemovedBytes(positionToFillRemovedBytesWithZeros);
}

ApplyGuardians(); // Should go after everything else since it's where we are inserting the extra bytes and needs to know the size
}

private void ApplyTrapsGear()
{
if (loadout == null || loadout.Length != 9)
if (Loadout == null || Loadout.Length != 9)
throw new Exception("9 traps/gear must be used");

int startIndex = UPKFile.FindBytesKMP(LoadoutHeaderMaximilian, HeroObjectOffsetMaximilian) + LoadoutHeaderMaximilian.Length;
int endIndex = UPKFile.FindBytesKMP(StartHeaderAfterLoadoutMaximilian, HeroObjectOffsetMaximilian);

// Where the actual array of the loadout starts is + 12 bytes from the loadout header.
// Array Size in bytes // Array (start?) index // Array number of elements
// 4 + 4 + 4
int arrayOffset = 12;
int startIndex = UPKFile.FindBytesKMP(LoadoutHeaderMaximilian, HeroObjectOffsetMaximillian, HeroObjectSizeMaximillian) + LoadoutHeaderMaximilian.Length;
int arrayElementCountIndex = startIndex + 8;
int arraySizeIndex = startIndex;

// Less than 9 slots are setup so we set them up to 9 and insert+remove bytes
if (endIndex - startIndex < 12 * 4)
// There aren't 9 slots set up so we create them and insert necessary bytes
if (UPKFile.getByte(arrayElementCountIndex) != 9)
{
UPKFile.OverrideSingleByte((byte)(LoadoutSlotsNumber + 1) * LoadoutSlotByteSize, startIndex); // Array Size
UPKFile.OverrideSingleByte((byte)LoadoutSlotsNumber, startIndex + 8); // Array Element Count ( the 4 bytes inbetween are "index 0")
UPKFile.OverrideSingleByte((byte)(LoadoutSlotsNumber + 1) * LoadoutSlotByteSize, arraySizeIndex); // Array Size
UPKFile.OverrideSingleByte((byte)LoadoutSlotsNumber, arrayElementCountIndex); // Array Element Count ( the 4 bytes inbetween are "index 0")

// Add new slots (2 slots)
UPKFile.InsertZeroedBytes(startIndex + arrayOffset + 8, 2 * LoadoutSlotByteSize);
UPKFile.InsertZeroedBytes(startIndex + LoadoutOffsetFromHeader + 8, 2 * LoadoutSlotByteSize);
}

// Convert and apply Loadout
byte[] loadoutBytes = ConvertLoadoutToBytes(Loadout);
UPKFile.OverrideBytes(loadoutBytes, startIndex + LoadoutOffsetFromHeader);
}

private void ApplyGuardians()
{
if (Guardians == null || Guardians.Length != 2)
throw new Exception("2 guardians must be used");

int startIndex = UPKFile.FindBytesKMP(GuardiansHeaderMaximillian, HeroObjectOffsetMaximillian, HeroObjectSizeMaximillian) + GuardiansHeaderMaximillian.Length;
int endIndex = UPKFile.FindBytesKMP(StartHeaderAfterGuardiansMaximillian, startIndex + GuardiansOffsetFromHeader, HeroObjectSizeMaximillian);
int totalSize = endIndex - startIndex; // Everything after header

byte[] firstGuardian = Resources.guardians[Guardians[0]].First; // Extra space after each guardian is already included
byte[] secondGuardian = Resources.guardians[Guardians[1]].First;

byte[] sizeFirstGuardian = new byte[] {Resources.guardians[Guardians[0]].Second, 0x00, 0x00, 0x00 }; // Add the 0x00 to complete the 4 bytes field

int secondGuardianOffset = firstGuardian.Length + sizeFirstGuardian.Length + GuardiansOffsetFromHeader + 4; // 4 from second guardian size itself
byte[] sizeSecondGuardian = new byte[] {(byte) (totalSize - secondGuardianOffset), 0x00, 0x00, 0x00 }; // Counting extra space

int emptySpaceOffset = secondGuardianOffset + Resources.guardians[Guardians[1]].Second;
byte[] emptySpace = Enumerable.Repeat((byte)0x00, totalSize - emptySpaceOffset).ToArray();

// Remove 8 bytes to make space for 2 slots
int removeIndex = UPKFile.FindBytesKMP(IconToRemoveFromFileBytes, HeroObjectOffsetMaximilian);
int nBytesRemove = 2 * LoadoutSlotByteSize;
UPKFile.RemoveBytes(removeIndex, nBytesRemove);
// Combining arrays to SizeGuardian1 + Guardian1 + SizeGuardian2 + Guardian2 + emptySpace
byte[] guardiansBytes = sizeFirstGuardian.Concat(firstGuardian).Concat(sizeSecondGuardian).Concat(secondGuardian).Concat(emptySpace).ToArray();

// set to 0 the icon field (20 bytes remaining, we removed 8)
UPKFile.OverrideBytes(UPKFile.CreateZeroedByteArray(IconToRemoveFromFileBytes.Length - nBytesRemove), removeIndex);
UPKFile.OverrideBytes(guardiansBytes, startIndex + GuardiansOffsetFromHeader);

UPKFile.OverrideSingleByte((byte) (totalSize - 8), startIndex); // Size (counts array element count and both guardians and their sizes but not itself or index so -8)
//TODO check single byte and avoid override?
UPKFile.OverrideSingleByte(GuardianSlotsNumber, startIndex + 8); // Array Element Count
}

private void ApplySkin()
{
int skinIndex = UPKFile.FindBytesKMP(SkinPatternMaximilian, HeroObjectOffsetMaximillian, HeroObjectSizeMaximillian) + SkinPatternMaximilian.Length;
UPKFile.OverrideBytes(Resources.skins[NameMaximilian][Skin], skinIndex);
}

private void RemoveByteSection(byte[] sectionHeaderByteArray, int length)
{
int removeIndex = UPKFile.FindBytesKMP(sectionHeaderByteArray, HeroObjectOffsetMaximillian, HeroObjectSizeMaximillian);

if (removeIndex != -1)
{
UPKFile.RemoveBytes(removeIndex, length);
}
}

// Convert and apply Loadout
byte[] loadoutBytes = ConvertLoadoutToBytes(loadout);
UPKFile.OverrideBytes(loadoutBytes, startIndex + arrayOffset);
private void FillRemovedBytes(int insertIndex)
{
UPKFile.InsertZeroedBytes(insertIndex, 0);
}

private byte[] ConvertLoadoutToBytes(string[] loadout)
Expand All @@ -122,7 +199,7 @@ private byte[] ConvertLoadoutToBytes(string[] loadout)

for (int i = 0; i < loadout.Length; i++)
{
byte[] slotBytes = new byte[LoadoutSlotByteSize];
byte[] slotBytes;

if (Resources.traps.ContainsKey(loadout[i]))
{
Expand All @@ -141,5 +218,6 @@ private byte[] ConvertLoadoutToBytes(string[] loadout)

return loadoutBytes;
}

}
}
1 change: 1 addition & 0 deletions SingleplayerLauncher/LauncherMainForm.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

56 changes: 48 additions & 8 deletions SingleplayerLauncher/LauncherMainForm.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using SingleplayerLauncher.Mods;
using Newtonsoft.Json.Linq;
using SingleplayerLauncher.Mods;
using System;
using System.Collections.Generic;
using System.Diagnostics;
Expand Down Expand Up @@ -181,7 +182,30 @@ private void Form1_Load(object sender, EventArgs e)

chkCustomIni.Checked = defaultCustomIniSetting;

hero.loadout = Resources.defaultLoadout;
hero.Loadout = Resources.defaultLoadout;
if (Settings.Instance.ContainsKey("hero"))
comBoxHero.SelectedItem = Settings.Instance["hero"];
if (Settings.Instance.ContainsKey("skin"))
comBoxSkin.SelectedItem = Settings.Instance["skin"];
if (Settings.Instance.ContainsKey("dye"))
comBoxDye.SelectedItem = Settings.Instance["dye"];
if (Settings.Instance.ContainsKey("map"))
comBoxMap.SelectedItem = Settings.Instance["map"];
if (Settings.Instance.ContainsKey("gameMode"))
comBoxGameMode.SelectedItem = Settings.Instance["gameMode"];
if (Settings.Instance.ContainsKey("difficulty"))
comBoxDifficulty.SelectedItem = Settings.Instance["difficulty"];
if (Settings.Instance.ContainsKey("extraDifficulty"))
comBoxExtraDifficulty.SelectedItem = Settings.Instance["extraDifficulty"];
if (Settings.Instance.ContainsKey("customIni"))
chkCustomIni.Checked = (bool)Settings.Instance["customIni"];
if (Settings.Instance.ContainsKey("log"))
chkLog.Checked = (bool)Settings.Instance["log"];
if (Settings.Instance.ContainsKey("loadout"))
{
var savedLoadOut = ((JArray)Settings.Instance["loadout"]).ToObject<string[]>();
hero.Loadout = savedLoadOut;
}
}

private void btnLaunch_Click(object sender, EventArgs e)
Expand All @@ -194,23 +218,35 @@ private void btnLaunch_Click(object sender, EventArgs e)
UpdateCharacterDataIni();
UpdateDefaultGameIni();
}
if (comBoxSkin.SelectedItem != null || LoadoutEditorForm.bytes.Count > 0)
if (comBoxSkin.SelectedItem != null)
{
hero.skin = comBoxSkin.SelectedItem.ToString();
hero.ApplySkin();
hero.Skin = comBoxSkin.SelectedItem.ToString();
}


MessageBox.Show("Saving your changes. Please wait.");
hero.ApplyLoadoutChanges();

ApplyMods(spitfireGameUPKFile);

MessageBox.Show("Saving your changes. Please wait.");
spitfireGameUPKFile.Save();
MessageBox.Show("Finished");

SaveSettings();
StartGame();
}
public void SaveSettings()
{

Settings.Instance["hero"] = comBoxHero.SelectedItem;
Settings.Instance["skin"] = comBoxSkin.SelectedItem;
Settings.Instance["dye"] = comBoxDye.SelectedItem;
Settings.Instance["map"] = comBoxMap.SelectedItem;
Settings.Instance["gameMode"] = comBoxGameMode.SelectedItem;
Settings.Instance["difficulty"] = comBoxDifficulty.SelectedItem;
Settings.Instance["extraDifficulty"] = comBoxExtraDifficulty.SelectedItem;
Settings.Instance["customIni"] = chkCustomIni.Checked;
Settings.Instance["log"] = chkLog.Checked;
Settings.Save();
}
private static void ApplyMods(UPKFile spitfireGameUPKFile)
{
NoTrapCap ntp = new NoTrapCap(spitfireGameUPKFile);
Expand Down Expand Up @@ -484,6 +520,10 @@ private void btnMods_Click(object sender, EventArgs e)
ModLoaderForm mlf = new ModLoaderForm();
mlf.Show();
}

private void ChkLog_CheckedChanged(object sender, EventArgs e)
{
}
}
}

Expand Down
Loading

0 comments on commit cab1f64

Please sign in to comment.