C#手写UserPref存档

本文介绍了一种基于C#的自定义存档系统,名为UserPref,用于替代Unity中的PlayerPrefs。该系统使用DES加密算法保护数据,通过文件而非注册表进行存储,提供了一种更安全的数据持久化解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

C#手写UserPref存档

这是我的第一篇博客,代码可能不够高效简洁,希望大家多多指点!

用过unity的肯定都知道PlayerPrefs类:

PlayerPrefs.GetInt("key_i");
PlayerPrefs.GetFloat("key_f");
PlayerPrefs.GetString("key_s");
PlayerPrefs.SetInt("key_i",0);
PlayerPrefs.SetFloat("key_f",0f);
PlayerPrefs.SetString("key_s","hello world!");

这是unity里自带的存档系统,通过修改注册表数值存档,相当简洁而好用。
诶?这么好用,我们为什么不把它从unity里搬出来,给其他C#程序用呢?(别告诉我从Dnspy里扒拉)

但是PlayerPrefs有个问题。

那就是它使用的是注册表。
而众所周知,有个东西叫Regedit。(win+r,然后输入“Regedit”回车)它可以编辑注册表,也就是说如果你用PlayerPrefs不加密保存数据,只要用户懂点技术,就可以修改存档。

所以,

1.我们为什么不用文件存档?
2.为什么不自带加密系统呢?

于是,

我做了一个类似PlayerPref的类-UserPref!(又不一定是做游戏用)

原理:

1. 存档地址:C:\Users\Administrator\AppData\Roaming\程序名\prefs.dat
2. 加密算法:DES
3. 存储格式:

(解密后的prefs.dat)
/数值名/
/数值类型,默认是string/
/以string保存的数据/
/数值名/
/数值类型,默认是string/
/以string保存的数据/
/数值名/
/数值类型,默认是string/
/以string保存的数据/
……

瞎BB 说 了,直接上代码。

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;

namespace UserPreference
{
	public class UserPrefs
	{
		/// <summary>
		/// 应用程序的名称
		/// </summary>
		public static string AppName;

		/// <summary>
		/// 存储加密密码,必须是八位。
		/// </summary>
		public static string DataKey;

		/// <summary>
		/// "C:/"
		/// </summary>
		private static string SystemDiskCode
		{
			get
			{
				return Environment.GetEnvironmentVariable("systemdrive") + '\\';
			}
		}

		/// <summary>
		/// @"C:\Users\Administrator\AppData\Roaming\"+程序名
		/// </summary>
		private static string DataFolderPath
		{
			get
			{
				return Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\" + AppName;
			}
		}

		/// <summary>
		/// 清除文件里的数据
		/// </summary>
		private static void ClearDataFileText()
		{
			File.WriteAllText(DataPath, "");
		}

		public static string DataPath
		{
			get
			{
				return DataFolderPath + @"\prefs.dat";
			}
		}

		/// <summary>
		/// 逐行读取文件,并解密数据
		/// </summary>
		/// <returns></returns>
		private static string[] ReadFileDataInLines()
		{
			try
			{
				if (File.Exists(DataPath))
				{
					string[] num = File.ReadAllLines(DataPath);
					for (int i = 0; i < num.Length; i++)
					{
						num[i] = SystemSafety.StringDecryptByDES(num[i], DataKey);

					}
					return num;
				}
				else
				{
					Directory.CreateDirectory(DataFolderPath);
					File.WriteAllText(DataPath, "");
					return new string[0];
				}
			}
			catch (Exception)
			{
				Directory.CreateDirectory(DataFolderPath);
				File.WriteAllText(DataPath, "");
				return new string[0];
			}
		}

		/// <summary>
		/// 读取数据。
		/// </summary>
		/// <returns>读取到的数据。</returns>
		private static Data[] ReadData()
		{
			string[] list = ReadFileDataInLines();
			if (list.Length % 3 != 0)
			{
				ClearDataFileText();
				return new Data[0];
			}
			else
			{
				Data[] data = new Data[list.Length / 3];
				for (int i = 0; i < list.Length; i += 3)
				{
					data[i / 3].DataName = list[i];
					switch (list[i + 1])
					{
						case "int":
							data[i / 3].dataType = Data.DataType.Integer;
							break;
						case "str":
							data[i / 3].dataType = Data.DataType.String;
							break;
						case "flt":
							data[i / 3].dataType = Data.DataType.Float;
							break;
						default:
							data[i / 3].dataType = Data.DataType.String;
							break;
					}
					data[i / 3].DataValue = list[i + 2];
				}
				return data;
			}
		}

		/// <summary>
		/// 将数据加密并保存。
		/// </summary>
		/// <param name="datas">数据</param>
		private static void SaveData(Data[] datas)
		{
			string[] texts = new string[datas.Length * 3];
			for (int i = 0; i < datas.Length; i++)
			{
				texts[i * 3] = datas[i].DataName;
				switch (datas[i].dataType)
				{
					case Data.DataType.Integer:
						texts[i * 3 + 1] = "int";
						break;
					case Data.DataType.String:
						texts[i * 3 + 1] = "str";
						break;
					case Data.DataType.Float:
						texts[i * 3 + 1] = "flt";
						break;
				}
				texts[i * 3 + 2] = datas[i].DataValue;
			}
			for (int i = 0; i < texts.Length; i++)
			{
				texts[i] = SystemSafety.StringEncryptByDES(texts[i], DataKey);
			}
			File.WriteAllLines(DataPath, texts);
		}

		/// <summary>
		/// 读取指定名字的整数数值
		/// </summary>
		/// <param name="key">整数的名字</param>
		/// <param name="defaultValue">默认返回值,当该数值不存在或读取出现问题时返回。</param>
		/// <returns>获取到的整数</returns>
		public static int GetInt(string key, int defaultValue = 0)
		{
			foreach (var r in ReadData())
			{
				if (r.DataName == key && r.dataType == Data.DataType.Integer)
				{
					int o;
					try
					{
						o = int.Parse(r.DataValue);
					}
					catch (FormatException)
					{
						SetInt(key, defaultValue);
						return defaultValue;
					}
					return o;
				}
			}
			return defaultValue;
		}

		/// <summary>
		/// 读取指定名字的浮点数数值
		/// </summary>
		/// <param name="key">浮点数的名字</param>
		/// <param name="defaultValue">默认返回值,当该数值不存在或读取出现问题时返回。</param>
		/// <returns>获取到的浮点数</returns>
		public static float GetFloat(string key, float defaultValue = 0)
		{
			foreach (var r in ReadData())
			{
				if (r.DataName == key && r.dataType == Data.DataType.Float)
				{
					float o;
					try
					{
						o = float.Parse(r.DataValue);
					}
					catch (FormatException)
					{
						SetFloat(key, defaultValue);
						return defaultValue;
					}
					return o;
				}
			}
			return defaultValue;
		}

		/// <summary>
		/// 读取指定名字的字符串数值
		/// </summary>
		/// <param name="key">字符串的名字</param>
		/// <param name="defaultValue">默认返回值,当该数值不存在或读取出现问题时返回。</param>
		/// <returns>获取到的字符串</returns>
		public static string GetString(string key, string defaultValue = "")
		{
			foreach (var r in ReadData())
			{
				if (r.DataName == key && r.dataType == Data.DataType.Integer)
				{
					return r.DataValue;
				}
			}
			return defaultValue;
		}

		/// <summary>
		/// 写入一个整数,如果该数值已存在,则修改该数值。
		/// </summary>
		/// <param name="key">数值的名字</param>
		/// <param name="value">需要存入的整数</param>
		public static void SetInt(string key, int value)
		{
			Data[] datas = ReadData();
			int x = GetKeyIndex(key, Data.DataType.Integer, ReadData());
			if (x == -1)
			{
				Array.Resize<Data>(ref datas, datas.Length + 1);
				datas[datas.Length - 1].DataName = key;
				datas[datas.Length - 1].DataValue = value.ToString();
				datas[datas.Length - 1].dataType = Data.DataType.Integer;
			}
			else
			{
				datas[x].DataName = key;
				datas[x].DataValue = value.ToString();
				datas[x].dataType = Data.DataType.Integer;
			}
			SaveData(datas);
		}

		/// <summary>
		/// 将数据中的所有数值都删除。
		/// </summary>
		public static void DeleteAllKeys()
		{
			ClearDataFileText();
		}

		/// <summary>
		/// 指定一个数值,如果该数值存在,就将其删除。
		/// </summary>
		/// <param name="key"></param>
		/// <param name="type"></param>
		public static void DeleteKey(string key, Data.DataType type)
		{
			Data[] d = ReadData();
			int x = GetKeyIndex(key, type, d);
			if (x != -1)
			{
				List<Data> l = d.ToList();
				l.RemoveAt(x);
				SaveData(l.ToArray());
			}
		}

		/// <summary>
		/// 写入一个浮点数,如果该数值已存在,则修改该数值。
		/// </summary>
		/// <param name="key">数值的名字</param>
		/// <param name="value">需要存入的浮点数</param>
		public static void SetFloat(string key, float value)
		{
			Data[] datas = ReadData();
			int x = GetKeyIndex(key, Data.DataType.Float, ReadData());
			if (x == -1)
			{
				Array.Resize<Data>(ref datas, datas.Length + 1);
				datas[datas.Length - 1].DataName = key;
				datas[datas.Length - 1].DataValue = value.ToString();
				datas[datas.Length - 1].dataType = Data.DataType.Float;
			}
			else
			{
				datas[x].DataName = key;
				datas[x].DataValue = value.ToString();
				datas[x].dataType = Data.DataType.Float;
			}
			SaveData(datas);
		}

		/// <summary>
		/// 写入一个字符串值,如果该数值已存在,则修改该数值。
		/// </summary>
		/// <param name="key">数值的名字</param>
		/// <param name="value">需要存入的字符串</param>
		public static void SetString(string key, string value)
		{
			Data[] datas = ReadData();
			int x = GetKeyIndex(key, Data.DataType.String, ReadData());
			if (x == -1)
			{
				Array.Resize<Data>(ref datas, datas.Length + 1);
				datas[datas.Length - 1].DataName = key;
				datas[datas.Length - 1].DataValue = value;
				datas[datas.Length - 1].dataType = Data.DataType.String;
			}
			else
			{
				datas[x].DataName = key;
				datas[x].DataValue = value;
				datas[x].dataType = Data.DataType.String;
			}
			SaveData(datas);
		}

		/// <summary>
		/// 检查指定数值是否存在。
		/// </summary>
		/// <param name="key">指定数值名字</param>
		/// <param name="type">指定数值类型</param>
		/// <returns>是否存在。</returns>
		public static bool HasKey(string key, Data.DataType type)
		{
			foreach (var r in ReadData())
			{
				if (r.DataName == key && r.dataType == type)
				{
					return true;
				}
			}
			return false;
		}

		/// <summary>
		/// 获取指定数据在数组里的索引,如果不存在则输出-1。
		/// </summary>
		/// <param name="key">指定数值名字</param>
		/// <param name="type">指定数值类型</param>
		/// <param name="datas">数据的数组</param>
		/// <returns>指定数据在数组里的索引,如果不存在则为-1</returns>
		private static int GetKeyIndex(string key, Data.DataType type, Data[] datas)
		{
			for (int i = 0; i < datas.Length; i++)
			{
				if (datas[i].DataName == key && datas[i].dataType == type)
				{
					return i;
				}
			}
			return -1;
		}
	}

	/// <summary>
	/// 一个数据值的结构体。
	/// </summary>
	public struct Data
	{
		public enum DataType
		{
			Integer,
			Float,
			String
		}
		public DataType dataType;//类型
		public string DataName;//数据的名字
		public string DataValue;//数据的值(string)
	}

	/// <summary>
	/// 这个用来加解密的类是我在破解滚动的天空时从文件里翻出来的,改了改。(无耻)(滑稽)
	/// </summary>
	public class SystemSafety
	{
		public static string StringEncryptByDES(string encryptInfo, string key, string iv = "12345678")
		{
			string result;
			try
			{
				byte[] bytes = Encoding.UTF8.GetBytes(key);
				byte[] bytes2 = Encoding.UTF8.GetBytes(iv);
				DESCryptoServiceProvider descryptoServiceProvider = new DESCryptoServiceProvider();
				using (MemoryStream memoryStream = new MemoryStream())
				{
					byte[] bytes3 = Encoding.UTF8.GetBytes(encryptInfo);
					try
					{
						using (CryptoStream cryptoStream = new CryptoStream(memoryStream, descryptoServiceProvider.CreateEncryptor(bytes, bytes2), CryptoStreamMode.Write))
						{
							cryptoStream.Write(bytes3, 0, bytes3.Length);
							cryptoStream.FlushFinalBlock();
						}
						result = Convert.ToBase64String(memoryStream.ToArray());
					}
					catch
					{
						result = encryptInfo;
					}
				}
			}
			catch
			{
				result = "DES加密出错";
			}
			return result;
		}

		public static string StringDecryptByDES(string encryptedString, string key, string iv = "12345678")
		{
			byte[] bytes = Encoding.UTF8.GetBytes(key);
			byte[] bytes2 = Encoding.UTF8.GetBytes(iv);
			DESCryptoServiceProvider descryptoServiceProvider = new DESCryptoServiceProvider();
			string result;
			using (MemoryStream memoryStream = new MemoryStream())
			{
				byte[] array = Convert.FromBase64String(encryptedString);
				try
				{
					using (CryptoStream cryptoStream = new CryptoStream(memoryStream, descryptoServiceProvider.CreateDecryptor(bytes, bytes2), CryptoStreamMode.Write))
					{
						cryptoStream.Write(array, 0, array.Length);
						cryptoStream.FlushFinalBlock();
					}
					result = Encoding.UTF8.GetString(memoryStream.ToArray());
				}
				catch
				{
					result = encryptedString;
				}
			}
			return result;
		}
	}
}

使用方法(非常建议你认真看一看)

1. UserPrefs.AppName 必须设置!写上你的程序名就行,不写会报错。
2. UserPrefs.DataKey 必须设置!这是数据加解密的密码,且必须有8个字符。
3. 可能还有Bug,不过经测试目前没有问题,我也会以后再更新。

欢迎指出不足,谢谢各位大佬们!

Build target 'Target 1' User command #1: GetSvnRevision.bat Updating '.': At revision 581. Revision: 581 compiling main.c... ..\..\kernal\main.c(1): warning C500: LICENSE ERROR (R207(2): REGISTRY READ ERROR) compiling UserPref.c... ..\..\msFunc\UserPref.c(1): warning C500: LICENSE ERROR (R207(2): REGISTRY READ ERROR) compiling MenuStr.c... ..\..\msFunc\MenuStr.c(1): warning C500: LICENSE ERROR (R207(2): REGISTRY READ ERROR) linking... BL51 BANKED LINKER/LOCATER V6.22 - SN: K1LMC-42VBMC COPYRIGHT KEIL ELEKTRONIK GmbH 1987 - 2009 COMMON {"..\..\1out\Debug.obj"}, COMMON {"..\..\1out\mcu.obj"}, COMMON {"..\..\1out\isr.obj"}, COMMON {"..\..\1out\main.obj"}, COMMON {"..\..\1out\ms_rwreg.obj"}, COMMON {"..\..\1out\misc.obj"}, COMMON {"..\..\1out\Global.obj"}, COMMON {"..\..\1out\L51_BANK.obj"}, COMMON {"..\..\msLib\Maria2_dsp_lib.LIB"}, COMMON {"..\..\msLib\M9_VD_AGC_Lib.LIB"}, BANK0 {"..\..\1out\TV_2IN1.obj"}, BANK0 {"..\..\1out\MenuFunc.obj"}, BANK0 {"..\..\1out\i2c.obj"}, BANK0 {"..\..\1out\msDDC2BI.obj"}, BANK0 {"..\..\1out\NVRam.obj"}, BANK0 {"..\..\1out\TDA7052.obj"}, BANK0 {"..\..\1out\mStar.obj"}, BANK0 {"..\..\1out\CalendarPatch.obj"}, BANK0 {"..\..\1out\Game.obj"}, BANK0 {"..\..\1out\IR.obj"}, BANK0 {"..\..\1out\Ir_nec.obj"}, BANK0 {"..\..\1out\Ir_rc5.obj"}, BANK0 {"..\..\1out\IrFunc.obj"}, BANK0 {"..\..\1out\Keypad.obj"}, BANK0 {"..\..\1out\power.obj"}, BANK0 {"..\..\1out\UserPref.obj"}, BANK0 {"..\..\1out\Adjust.obj"}, BANK0 {"..\..\1out\AutoFunc.obj"}, BANK0 {"..\..\1out\detect.obj"}, BANK0 {"..\..\1out\mode.obj"}, BANK0 {"..\..\1out\DevTuner.obj"}, BANK0 {"..\..\1out\msVD.obj"}, BANK0 {"..\..\1out\DevVD.obj"}, BANK0 {"..\..\msLib\MstAce.LIB"}, BANK0 {"..\..\msLib\Maria2_acc_lib.LIB"}, BANK0 {"..\..\msLib\M2_VD_Std_Lib.LIB"}, BANK0 {"..\..\msLib\Maria2_fsc_lib.LIB"}, BANK0 {"..\..\1out\M2_VSync2.obj"}, BANK1 {"..\..\1out\menu.obj"}, BANK1 {"..\..\1out\MenuStr.obj"}, BANK1 {"..\..\1out\msOSD.obj"} TO "..\..\1out\CarTV_128" BankArea(0x4800,0xFFFF) PRINT("..\..\
最新发布
04-29
### 编译过程中的 LICENSE ERROR 和链接器问题解决方案 在 Keil 开发环境中遇到 `LICENSE ERROR (R207(2): REGISTRY READ ERROR)` 警告通常表明许可证文件存在问题或未正确安装。以下是针对该问题的分析和解决方法: #### 1. 许可证错误 R207 的原因 `LICENSE ERROR (R207(2): REGISTRY READ ERROR)` 表明开发环境无法读取注册表中的许可证信息[^1]。这可能是由于以下原因之一: - 许可证文件损坏或丢失。 - 注册表项被意外删除或修改。 - 使用的是试用版软件,功能受限。 #### 2. 解决方案 为了修复此问题,可以尝试以下措施: ##### 更新或重新安装许可证 如果怀疑许可证文件有问题,则需要联系供应商获取新的许可证文件并按照说明进行更新。确保将 `.lic` 文件放置到指定目录下(通常是 Keil 安装路径下的 `\uv4\` 子目录)。完成后重启 IDE 验证是否解决问题[^1]。 ##### 检查系统权限 某些情况下,操作系统可能阻止程序访问必要的注册表键值。确认当前用户具有管理员权限,并以管理员身份运行 Keil 应用程序[^1]。 ##### 清理缓存数据 有时旧版本残留的数据会干扰新版本正常工作。建议卸载现有工具链后再彻底清除所有关联配置文件夹(如 AppData 中的相关记录),最后重装最新稳定发行包[^1]。 #### 3. 关于 C500 错误 C500 是由 BL51 链接器产生的错误码之一,表示内存分配冲突或者超出可用资源范围的情况发生。具体表现为试图定义超过允许大小的对象或将多个段映射至同一物理地址空间内造成覆盖现象。 要处理这类情况可以从以下几个方面入手调整项目设置: - **优化存储结构**: 修改目标硬件平台参数使之匹配实际需求;减少全局变量数量及尺寸来降低 RAM 占用量; - **分隔代码区域**: 利用 scatter file 技术自定义各模块加载位置从而避免相互间干扰; - **启用压缩选项**: 如果支持的话开启 LTO(Link Time Optimization) 功能有助于进一步缩小最终产物体积[^1]. ```c #pragma arm section code="text" void myFunction() { // Function implementation here. } #pragma arm resetsection ``` 以上代码片段展示了如何通过 pragma 指令手动控制特定函数所属节名称以便更灵活管理布局安排[^1]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值