<commit_before>﻿using System;
using System.Runtime.InteropServices;

namespace LibUxie.GBA {
	[StructLayout(LayoutKind.Sequential, Pack=1)]
	struct Footer {
		public ushort sectionId;
		public ushort checksum;
		public uint mark;
		public uint saveIndex;
	}

	[StructLayout(LayoutKind.Sequential, Pack = 1)]
	struct Item {
		public ushort index;
		public ushort amount;
	}

	public enum Version {
		Unknown,
		RubySapphire,
		Emerald,
		FireRedLeafGreen
	};

	public enum SaveSlot {
		Main,
		Backup
	}

	public class Save {
		private const int BLOCK_COUNT = 14;
		private const int BLOCK_LENGTH = 0x1000;
		private const int UNPACKED_BLOCK_LENGTH = 0xF80;
		private const uint FOOTER_MARK = 0x08012025;
		private const int UNPACKED_SIZE = 0xD900;
		private const int PACKED_SIZE = 0x20000;
		private const int SAVE_SECTION = 0xE000;

		private const int RSE_SECKEY_OFFSET = 0xAC;
		private const int FRLG_SECKEY_OFFSET = 0xAF8;

		private const int RSE_STORAGE = UNPACKED_BLOCK_LENGTH + 0x490;
		private const int FRLG_STORAGE = UNPACKED_BLOCK_LENGTH + 0x290;
		private const int RS_ITEM_COUNT = 216;
		private const int FRLG_ITEM_COUNT = 216;
		private const int E_ITEM_COUNT = 236;

		private const int RSE_PC_ITEM_COUNT = 50;
		private const int FRLG_PC_ITEM_COUNT = 30;

		private Version type;
		private byte[] order;
		private byte[] unpacked;
		private uint saveIndex;

		/* Unsafe a.k.a. "I got this." */
		private static unsafe Footer *GetFooter(byte[] data, int offset, int block) {
			const int FOOTER_LENGTH = 0xC;
			offset = offset + ((block + 1) * BLOCK_LENGTH - FOOTER_LENGTH);
			fixed(byte* ptr = &data[offset]) {
				return (Footer*)ptr;
			}
		}

		private static unsafe int GetTypeOffset(byte[] data, SaveSlot slot) {
			uint a = GetFooter(data, 0, 0)->saveIndex;
			uint b = GetFooter(data, SAVE_SECTION, 0)->saveIndex;
			if(slot == SaveSlot.Main) {
				if(b > a) {
					return SAVE_SECTION;
				}
				return 0;
			}
			if(a > b) {
				return SAVE_SECTION;
			}
			return 0;
		}
		private unsafe static Version DetectVersion(byte[] data) {
			if(data.Length != PACKED_SIZE) {
				return Version.Unknown;
			}
			uint mark = GetFooter(data, 0, 0)->mark;
			if(mark != FOOTER_MARK) {
				return Version.Unknown;
			}

			/* These are all in the first block. So no need to unpack first. */
			const int RSE_SECKEY2_OFFSET = 0x1F4;
			const int FRLG_SECKEY2_OFFSET = 0xF20;

			/* FireRed/LeafGreen */
			if(BitConverter.ToInt32(data, FRLG_SECKEY_OFFSET)
			== BitConverter.ToInt32(data, FRLG_SECKEY2_OFFSET)) {
				return Version.FireRedLeafGreen;
			} else
			/* Emerald */
				if(BitConverter.ToInt32(data, RSE_SECKEY_OFFSET)
			== BitConverter.ToInt32(data, RSE_SECKEY2_OFFSET)) {
				return Version.Emerald;
			}
			/* Ruby/Sapphire */
			if(BitConverter.ToInt32(data, RSE_SECKEY_OFFSET) == 0
			&& BitConverter.ToInt32(data, RSE_SECKEY2_OFFSET) == 0) {
				return Version.RubySapphire;
			}

			return Version.Unknown;
		}

		private unsafe void Crypt() {
			//No (working) encryption in Ruby and Sapphire
			if(type == Version.RubySapphire) {
				return;
			}
			uint key = 0;
			int offset = 0;
			if(type == Version.Emerald) {
				key = BitConverter.ToUInt32(unpacked, RSE_SECKEY_OFFSET);<commit_msg>Fixed bug in new gba version detection.<commit_after>﻿using System;
using System.Runtime.InteropServices;

namespace LibUxie.GBA {
	[StructLayout(LayoutKind.Sequential, Pack=1)]
	struct Footer {
		public ushort sectionId;
		public ushort checksum;
		public uint mark;
		public uint saveIndex;
	}

	[StructLayout(LayoutKind.Sequential, Pack = 1)]
	struct Item {
		public ushort index;
		public ushort amount;
	}

	public enum Version {
		Unknown,
		RubySapphire,
		Emerald,
		FireRedLeafGreen
	};

	public enum SaveSlot {
		Main,
		Backup
	}

	public class Save {
		private const int BLOCK_COUNT = 14;
		private const int BLOCK_LENGTH = 0x1000;
		private const int UNPACKED_BLOCK_LENGTH = 0xF80;
		private const uint FOOTER_MARK = 0x08012025;
		private const int UNPACKED_SIZE = 0xD900;
		private const int PACKED_SIZE = 0x20000;
		private const int SAVE_SECTION = 0xE000;

		private const int RSE_SECKEY_OFFSET = 0xAC;
		private const int FRLG_SECKEY_OFFSET = 0xAF8;

		private const int RSE_STORAGE = UNPACKED_BLOCK_LENGTH + 0x490;
		private const int FRLG_STORAGE = UNPACKED_BLOCK_LENGTH + 0x290;
		private const int RS_ITEM_COUNT = 216;
		private const int FRLG_ITEM_COUNT = 216;
		private const int E_ITEM_COUNT = 236;

		private const int RSE_PC_ITEM_COUNT = 50;
		private const int FRLG_PC_ITEM_COUNT = 30;

		private Version type;
		private byte[] order;
		private byte[] unpacked;
		private uint saveIndex;

		/* Unsafe a.k.a. "I got this." */
		private static unsafe Footer *GetFooter(byte[] data, int offset, int block) {
			const int FOOTER_LENGTH = 0xC;
			offset = offset + ((block + 1) * BLOCK_LENGTH - FOOTER_LENGTH);
			fixed(byte* ptr = &data[offset]) {
				return (Footer*)ptr;
			}
		}

		private static unsafe int GetTypeOffset(byte[] data, SaveSlot slot) {
			uint a = GetFooter(data, 0, 0)->saveIndex;
			uint b = GetFooter(data, SAVE_SECTION, 0)->saveIndex;
			if(slot == SaveSlot.Main) {
				if(b > a) {
					return SAVE_SECTION;
				}
				return 0;
			}
			if(a > b) {
				return SAVE_SECTION;
			}
			return 0;
		}
		private unsafe static Version DetectVersion(byte[] data) {
			if(data.Length != PACKED_SIZE) {
				return Version.Unknown;
			}
			uint mark = GetFooter(data, 0, 0)->mark;
			if(mark != FOOTER_MARK) {
				return Version.Unknown;
			}

			/* None of these cross a block boundry so no need to unpack. */
			const int RSE_SECKEY2_OFFSET = 0x1F4;
			const int FRLG_SECKEY2_OFFSET = 0xF20;

			/* Find the first block */
			int offset = 0;
			for(int i = 0; i < BLOCK_COUNT; ++i) {
				if(GetFooter(data, 0, i)->sectionId == 0) {
					offset = BLOCK_LENGTH * i;
					break;
				}
			}

			/* Ruby/Sapphire */
			if(BitConverter.ToInt32(data, offset + RSE_SECKEY_OFFSET) == 0
			&& BitConverter.ToInt32(data, offset + RSE_SECKEY2_OFFSET) == 0) {
				return Version.RubySapphire;
			}
			/* Emerald */
			if(BitConverter.ToInt32(data, offset + RSE_SECKEY_OFFSET)
			== BitConverter.ToInt32(data, offset + RSE_SECKEY2_OFFSET)) {
				return Version.Emerald;
			}
			/* FireRed/LeafGreen */
			if(BitConverter.ToInt32(data, offset + FRLG_SECKEY_OFFSET)
			== BitConverter.ToInt32(data, offset + FRLG_SECKEY2_OFFSET)) {
				return Version.FireRedLeafGreen;
			}
			

			return Version.Unknown;
		}

		private unsafe void Crypt() {
			//No (working) encryption in Ruby and Sapphire
			if(type == Version.RubySapphire) {
				return;
			}
			uint key = 0;
			int offset = 0;
			if(type == Version.Emerald) {
				key = BitConverter.ToUInt32(unpacked, RSE_SECKEY_OFFSET);
{'fraction_non_alphanumeric': 0.09900401367623012, 'fraction_numerical': 0.029433625687527873, 'mean_word_length': 3.4763805721889556, 'pattern_counts': {'":': 0, '<': 4, '<?xml version=': 0, '>': 14, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3588198', 'n_tokens_mistral': 2954, 'n_tokens_neox': 2384, 'n_words': 620}