public void SaveToFile() { //========================== WRITING TO A NEW FILE FOLLOWS ========================== if (filepath == null) { MessageBox.Show("You need to open a valid save file first!", "Error", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } Byte[] checksumArea; uint checksum; if (game == Game.EPF) //============ ELITE PENGUIN FORCE ============ { //WRITE INVENTORY for (int i = 0; i < saveFileEditor.inventoryBox.Items.Count; i++) { if (saveFileEditor.inventoryBox.GetItemChecked(i)) { filebytes[0xF650 + i] = 01; } else { filebytes[0xF650 + i] = 00; } } //WRITE LIFETIME COINS WriteU32ToArray(filebytes, 0xF704, (uint)saveFileEditor.lifetimeCoins.Value); //lifetime coins //WRITE MINIGAME HIGH SCORES WriteU32ToArray(filebytes, 0xF708, (uint)saveFileEditor.highScore1.Value); //snowboarding WriteU32ToArray(filebytes, 0xF710, (uint)saveFileEditor.highScore2.Value); //cart surfer WriteU32ToArray(filebytes, 0xF70C, (uint)saveFileEditor.highScore3.Value); //ice fishing WriteU32ToArray(filebytes, 0xF714, (uint)saveFileEditor.highScore5.Value); //dance challenge WriteU32ToArray(filebytes, 0xF718, (uint)saveFileEditor.highScore4.Value); //jet pack adventure WriteU32ToArray(filebytes, 0xF71C, (uint)saveFileEditor.highScore6.Value); //snow trekker //ERASE OLD PENGUIN NAME (otherwise, if the new name is shorter, it won't overwrite the entire length old name) for (int i = 0; i < oldPenguinName.Length * 2; i++) { filebytes[0xF720 + i] = 0x00; } //WRITE NEW PENGUIN NAME WriteU16StringToArray(filebytes, 0xF720, saveFileEditor.penguinNameTextBox.Text); oldPenguinName = saveFileEditor.penguinNameTextBox.Text; //ERASE OLD ONLINE NAMES (otherwise, if the new names are shorter, it won't overwrite the entire lengths of the old names) for (int i = 0; i < oldOnlineName1.Length * 2; i++) { filebytes[0xF73A + i] = 0x00; } for (int i = 0; i < oldOnlineName2.Length * 2; i++) { filebytes[0xF754 + i] = 0x00; } for (int i = 0; i < oldOnlineName3.Length * 2; i++) { filebytes[0xF76E + i] = 0x00; } //WRITE NEW ONLINE NAMES WriteU16StringToArray(filebytes, 0xF73A, saveFileEditor.onlineName1.Text); oldOnlineName1 = saveFileEditor.onlineName1.Text; WriteU16StringToArray(filebytes, 0xF754, saveFileEditor.onlineName2.Text); oldOnlineName2 = saveFileEditor.onlineName1.Text; WriteU16StringToArray(filebytes, 0xF76E, saveFileEditor.onlineName3.Text); oldOnlineName3 = saveFileEditor.onlineName1.Text; //WRITE NEW COINS WriteU32ToArray(filebytes, 0xF7E4, (uint)saveFileEditor.coinsChooser.Value); //I get the feeling that 0xF7E8 could be a bitfield for which side missions you've completed (although it doesn't seem to control which ones are AVAILABLE) //WRITE CURRENT MISSION filebytes[0xF7EC] = (byte)saveFileEditor.currentMissionChooser.SelectedIndex; //WRITE NEW COLOUR filebytes[0xF7F0] = (byte)saveFileEditor.colourChooser.SelectedIndex; //WRITE CURRENT SIDEMISSION COMPLETEDNESS at 0xF7F4, and something else relevant might be at 0xF7F6? //When the relevant mask is set, the mission has been completed (and is no longer available). //However, the initial availability of the mission is somewhere else. //supposedly a bitfield, here are the bitmasks for now: //0x0001: A Wrench in the Works (ski village) //0x0002: Cart surfing mission (mine) //0x0004: Tour guide lost in forest mission (forest) //0x0008: Pizza ordering mission (pizza parlor) //0x0010: Wrong pizzas mission (pizza parlor) //0x0020: Finding sailboats mission (dock) //0x0040: Alien messages mission (forest) //0x0080: Lost scarf mission (ski hill) //0x0100: Rory fixing clock mission (snow forts) //0x0200: Hide and Seek (pet shop) //WRITE PUFFLES int puffles = 0x00; if (saveFileEditor.puffleBouncer.Checked) { puffles = puffles | 0x01; } if (saveFileEditor.puffleBlast.Checked) { puffles = puffles | 0x02; } if (saveFileEditor.puffleFlare.Checked) { puffles = puffles | 0x04; } if (saveFileEditor.pufflePop.Checked) { puffles = puffles | 0x08; } if (saveFileEditor.puffleLoop.Checked) { puffles = puffles | 0x10; } if (saveFileEditor.puffleFlit.Checked) { puffles = puffles | 0x20; } if (saveFileEditor.puffleChirp.Checked) { puffles = puffles | 0x40; } if (saveFileEditor.puffleChill.Checked) { puffles = puffles | 0x80; } filebytes[0xF7FB] = (byte)(puffles & 0xFF); //WRITE UNLOCKABLES (map, puffle whistle, etc) int unlocks = 0x00; if (saveFileEditor.mapUnlockable.Checked) { unlocks = unlocks | 0x01; } if (saveFileEditor.HQteleportUnlockable.Checked) { unlocks = unlocks | 0x02; } if (saveFileEditor.inventoryUnlockable.Checked) { unlocks = unlocks | 0x04; } if (saveFileEditor.whistleUnlockable.Checked) { unlocks = unlocks | 0x08; } filebytes[0xF7FC] = (byte)(unlocks & 0xFF); if (embeddedArc == null || (embeddedArc != null && embeddedArc.filebytes.Length < 0xC860)) { //if it's safe to write the newsletter, do so //write the text for (int i = 0; i < 0x96; i++) { if (i < saveFileEditor.topStoryBox.Text.Length) { filebytes[0xC960 + (i * 2)] = (byte)saveFileEditor.topStoryBox.Text[i]; } else { filebytes[0xC960 + (i * 2)] = 0x00; } filebytes[0xC961 + (i * 2)] = 0x00; } for (int i = 0; i < 0x96; i++) { if (i < saveFileEditor.tipsAndSecretsTextBox.Text.Length) { filebytes[0xCA8C + (i * 2)] = (byte)saveFileEditor.tipsAndSecretsTextBox.Text[i]; } else { filebytes[0xCA8C + (i * 2)] = 0x00; } filebytes[0xCA8D + (i * 2)] = 0x00; } for (int i = 0; i < 0x96; i++) { if (i < saveFileEditor.jokeTextBox.Text.Length) { filebytes[0xCBB8 + (i * 2)] = (byte)saveFileEditor.jokeTextBox.Text[i]; } else { filebytes[0xCBB8 + (i * 2)] = 0x00; } filebytes[0xCBB9 + (i * 2)] = 0x00; } Array.Copy(newsletterImage, 0, filebytes, 0xCD10, 0x2940); //copy newsletter image into save file int colorindex = 0; foreach (Color c in newsletterPalette) //copy newsletter palette into save file { ushort ABGR1555Color = saveFileEditor.form1.ColorToABGR1555(c); filebytes[0xCCF0 + (colorindex * 2)] = (byte)(ABGR1555Color & 0x00FF); filebytes[0xCCF0 + (colorindex * 2) + 1] = (byte)((ABGR1555Color & 0xFF00) >> 8); colorindex++; } } int endOfDownloadArc = 0; if (embeddedArc != null) { //WRITE DOWNLOADABLE MISSION (maybe you should also clear a space for it beforehand? in case the old mission was bigger) Array.Copy(embeddedArc.filebytes, 0, filebytes, 0x100, embeddedArc.filebytes.Length); //DOWNLOAD.ARC CHECKSUM CALCULATION //redoing the checksum here just in case endOfDownloadArc = (0x100 + embeddedArc.filebytes.Length) - 4; //minus 4, because the length includes the checksum, but that's what we want to write to while (endOfDownloadArc % 4 != 0) //pad to multiple of 4 { filebytes[endOfDownloadArc] = 0; endOfDownloadArc++; } checksumArea = new Byte[endOfDownloadArc - 0x100]; Array.Copy(filebytes, 0x100, checksumArea, 0x0, endOfDownloadArc - 0x100); checksum = Crc32.Compute(checksumArea); WriteU32ToArray(filebytes, endOfDownloadArc, checksum); } //CHECKSUM CALCULATION EPF int checksumAreaSize = 0xF700; if (extendedSaveMode) { checksumAreaSize = 0xFF00; } checksumArea = new Byte[checksumAreaSize]; Array.Copy(filebytes, 0x100, checksumArea, 0x0, checksumAreaSize); checksum = Crc32.Compute(checksumArea); WriteU32ToArray(filebytes, 0x0C, checksum); } else if (game == Game.HR) //============ HERBERT'S REVENGE ============ { //WRITE NEW COINS WriteU32ToArray(filebytes, 0x28, (uint)saveFileEditor.coinsChooser.Value); //ERASE OLD PENGUIN NAME (otherwise, if the new name is shorter, it won't overwrite the entire length old name) for (int i = 0; i < oldPenguinName.Length * 2; i++) { filebytes[0x58 + i] = 0x00; } //WRITE NEW PENGUIN NAME WriteU16StringToArray(filebytes, 0x58, saveFileEditor.penguinNameTextBox.Text); oldPenguinName = saveFileEditor.penguinNameTextBox.Text; //WRITE NEW COLOUR filebytes[0x24] = (byte)saveFileEditor.colourChooser.SelectedIndex; //WRITE NEW MISSION WriteU16ToArray(filebytes, 0xA6, GetNewHRMissionValue(saveFileEditor.currentMissionChooser.SelectedIndex)); //WRITE INVENTORY for (int i = 0; i < saveFileEditor.inventoryBox.Items.Count; i++) { if (saveFileEditor.inventoryBox.GetItemChecked(i)) { filebytes[0x0124 + i] = 01; } else { filebytes[0x0124 + i] = 00; } } //CHECKSUM CALCULATION HR checksumArea = new Byte[0x130]; Array.Copy(filebytes, 0x20, checksumArea, 0x0, 0x130); checksum = Crc32.Compute(checksumArea); WriteU32ToArray(filebytes, 0x08, checksum); } File.WriteAllBytes(filepath, filebytes); }