当前位置: 首页 > news >正文

godot+c#操作godot-sqlite并加解密

前言

在前面一篇文章,我已经写好了如何godot+c#使用godot-sqlite连接数据库
但是这会有一个问题,当对这个数据库有了解的人,很容易就可以实现篡改游戏数据.
因此,本次将对其进行加解密访问

步骤

加解密

依赖安装

  • 和前面文章一样,我们需要引入加解密的依赖。
  • 同样,找到你建立的godot游戏项目,找到有csproj格式文件的目录

在这里插入图片描述

  • 使用cmd命令行执行如下指令
dotnet add package Microsoft.Data.Sqlite.Core
dotnet add package SQLitePCLRaw.bundle_e_sqlcipher
  • 假如有看我前一个文章的进行安装的,需要卸载掉旧的引用防止程序抓的包异常,卸载命令行如下
dotnet remove package System.Data.SQLite.Core
  • 执行安装之后,你会看到csproj文件内容应该是这样的
<Project Sdk="Godot.NET.Sdk/4.4.1"><PropertyGroup><TargetFramework>net8.0</TargetFramework><EnableDynamicLoading>true</EnableDynamicLoading></PropertyGroup><ItemGroup><PackageReference Include="Microsoft.Data.Sqlite.Core" Version="9.0.9" /><PackageReference Include="SQLitePCLRaw.bundle_e_sqlcipher" Version="2.1.11" /></ItemGroup>
</Project>

代码调整

注意:关于怎么引入godot-sqlite本文章不再额外缀诉,如需要请访问我前一个文章

  • 同样,我们有四个文件,采取和java的jdbc一样的模式,来访问数据库
DBConnection.cs

该代码是关于sqlite的数据库连接配置代码,里面设置了加密密钥_encryptionKey,该值根据自己来设定,相当于访问密码

  • 代码如下
using System;
using Microsoft.Data.Sqlite;
using Godot;
using System.IO;public static class DBConnection
{private static SqliteConnection _connection;private static string _dbPath = "user://test.db";public static string _encryptionKey = "ABC123";// 获取数据库连接:cite[7]public static SqliteConnection GetConnection(){if (_connection == null){string userDir = OS.GetUserDataDir();string fullPath = Path.Combine(userDir, "test.db");// 创建连接字符串var connectionString = new SqliteConnectionStringBuilder{DataSource = fullPath,Mode = SqliteOpenMode.ReadWriteCreate,Password = _encryptionKey // 添加加密密钥}.ToString();_connection = new SqliteConnection(connectionString);try{_connection.Open();InitializeTables();GD.Print("成功打开加密数据库连接");}catch (Exception e){GD.PrintErr($"打开数据库失败: {e.Message}");_connection = null;throw;}}return _connection;}// 初始化数据库表:cite[1]private static void InitializeTables(){string createTableSQL = @"CREATE TABLE IF NOT EXISTS students (id INTEGER PRIMARY KEY AUTOINCREMENT,name TEXT NOT NULL,age INTEGER NOT NULL,grade TEXT)";using (var command = new SqliteCommand(createTableSQL, _connection)){command.ExecuteNonQuery();}GD.Print("数据库表初始化成功");}// 关闭数据库连接public static void CloseConnection(){if (_connection != null){_connection.Close();_connection = null;GD.Print("数据库连接已经关闭");}}// 更改加密密钥的方法public static bool ChangeEncryptionKey(string newKey){try{using (var command = _connection.CreateCommand()){command.CommandText = $"PRAGMA rekey = '{newKey}'";command.ExecuteNonQuery();_encryptionKey = newKey;GD.Print("加密密钥已更改");return true;}}catch (Exception e){GD.PrintErr($"更改密钥失败: {e.Message}");return false;}}public static void EncryptExistingDatabase(string unencryptedPath, string encryptedPath, string key){// 连接到未加密数据库var unencryptedConnectionString = new SqliteConnectionStringBuilder{DataSource = unencryptedPath}.ToString();// 连接到新加密数据库var encryptedConnectionString = new SqliteConnectionStringBuilder{DataSource = encryptedPath,Password = key}.ToString();using (var unencryptedConn = new SqliteConnection(unencryptedConnectionString))using (var encryptedConn = new SqliteConnection(encryptedConnectionString)){unencryptedConn.Open();encryptedConn.Open();// 将未加密数据库的内容复制到加密数据库using (var command = unencryptedConn.CreateCommand()){command.CommandText = $"ATTACH DATABASE '{encryptedPath}' AS encrypted KEY '{key}'";command.ExecuteNonQuery();command.CommandText = "SELECT sqlcipher_export('encrypted')";command.ExecuteNonQuery();command.CommandText = "DETACH DATABASE encrypted";command.ExecuteNonQuery();}}// 备份原始未加密数据库File.Move(unencryptedPath, unencryptedPath + ".backup");File.Move(encryptedPath, unencryptedPath);GD.Print("数据库加密完成");}
}
Student.cs

和前一个文章一样,我因为采用了jdbc的模式访问数据库,所以有一个实体作为映射,一个实体对应一个表结构,这个是学生信息表,那么实际游戏开发可以替换成自己的玩家数据表,物品信息表等

  • 代码如下
using Godot;
using System;public class Student
{public int Id { get; set; }public string Name { get; set; }public int Age { get; set; }public string Grade { get; set; }public Student() { }public Student(string name, int age, string grade){Name = name;Age = age;Grade = grade;}// 重写ToString方法便于输出public override string ToString(){return $"Student [ID: {Id}, Name: {Name}, Age: {Age}, Grade: {Grade}]";}
}
StudentDAO.cs

该文件是学生信息表的DAO层,用于操作学生信息表进行增删改查

  • 代码如下
using System;
using Microsoft.Data.Sqlite;
using System.Collections.Generic;
using Godot;
using System.IO;public class StudentDAO
{private static string GetDbPath(){string userDir = OS.GetUserDataDir();return Path.Combine(userDir, "test.db");}private static string GetConnectionString(){return new SqliteConnectionStringBuilder{DataSource = GetDbPath(),Password = DBConnection._encryptionKey // 与DBConnection中相同的密钥}.ToString();}// 添加学生:cite[1]public static bool AddStudent(Student student){// string dbPath = GetDbPath();//string connectionString = $"Data Source={dbPath};Version=3;";// 使用 using 语句确保连接在使用后会被正确关闭和释放:cite[10]var connection = DBConnection.GetConnection();try{connection.Open();string sql = @"INSERT INTO students (name, age, grade) VALUES (@name, @age, @grade)";using (var command = new SqliteCommand(sql, connection)){command.Parameters.AddWithValue("@name", student.Name);command.Parameters.AddWithValue("@age", student.Age);command.Parameters.AddWithValue("@grade", student.Grade);int rowsAffected = command.ExecuteNonQuery();GD.Print($"新增学生: {student.Name}, 操作结果: {rowsAffected}");return rowsAffected > 0;}}catch (Exception e){GD.PrintErr($"添加学生失败: {e.Message}");return false;}}// 根据ID删除学生:cite[5]public static bool DeleteStudent(int id){//string dbPath = GetDbPath();//string connectionString = $"Data Source={dbPath};Version=3;";try{var connection = DBConnection.GetConnection();connection.Open();string sql = "DELETE FROM students WHERE id = @id";using (var command = new SqliteCommand(sql, connection)){command.Parameters.AddWithValue("@id", id);int rowsAffected = command.ExecuteNonQuery();GD.Print($"使用ID删除学生: {id}, 操作结果: {rowsAffected}");return rowsAffected > 0;}}catch (Exception e){GD.PrintErr($"删除学生失败: {e.Message}");return false;}}// 更新学生信息:cite[5]public static bool UpdateStudent(Student student){//string dbPath = GetDbPath();//string connectionString = $"Data Source={dbPath};Version=3;";try{var connection = DBConnection.GetConnection();connection.Open();string sql = @"UPDATE students SET name = @name, age = @age, grade = @grade WHERE id = @id";using (var command = new SqliteCommand(sql, connection)){command.Parameters.AddWithValue("@name", student.Name);command.Parameters.AddWithValue("@age", student.Age);command.Parameters.AddWithValue("@grade", student.Grade);command.Parameters.AddWithValue("@id", student.Id);int rowsAffected = command.ExecuteNonQuery();GD.Print($"更新学生信息: {student.Name}, 操作结果: {rowsAffected}");return rowsAffected > 0;}}catch (Exception e){GD.PrintErr($"更新学生信息失败: {e.Message}");return false;}}// 获取所有学生:cite[1]public static List<Student> GetAllStudents(){
/*        string dbPath = GetDbPath();string connectionString = $"Data Source={dbPath};Version=3;";*/var students = new List<Student>();try{var connection = DBConnection.GetConnection();connection.Open();string sql = "SELECT * FROM students";using (var command = new SqliteCommand(sql, connection))using (var reader = command.ExecuteReader()){while (reader.Read()){students.Add(new Student{Id = Convert.ToInt32(reader["id"]),Name = reader["name"].ToString(),Age = Convert.ToInt32(reader["age"]),Grade = reader["grade"].ToString()});}}GD.Print($"已获取 {students.Count} 数量学生信息");}catch (Exception e){GD.PrintErr($"获取学生信息失败: {e.Message}");}return students;}// 根据ID查询学生:cite[7]public static Student GetStudentById(int id){
/*        string dbPath = GetDbPath();string connectionString = $"Data Source={dbPath};Version=3;";*/try{var connection = DBConnection.GetConnection();connection.Open();string sql = "SELECT * FROM students WHERE id = @id";using (var command = new SqliteCommand(sql, connection)){command.Parameters.AddWithValue("@id", id);using (var reader = command.ExecuteReader()){if (reader.Read()){GD.Print($"使用ID获取学生信息: {id}");return new Student{Id = Convert.ToInt32(reader["id"]),Name = reader["name"].ToString(),Age = Convert.ToInt32(reader["age"]),Grade = reader["grade"].ToString()};}}}}catch (Exception e){GD.PrintErr($"获取学生信息失败: {e.Message}");}GD.Print($"未找到学生信息: {id}");return null;}// 根据姓名查询学生(模糊查询):cite[7]public static List<Student> GetStudentsByName(string name){
/*        string dbPath = GetDbPath();string connectionString = $"Data Source={dbPath};Version=3;";*/var students = new List<Student>();try{var connection = DBConnection.GetConnection();connection.Open();string sql = "SELECT * FROM students WHERE name LIKE @name";using (var command = new SqliteCommand(sql, connection)){command.Parameters.AddWithValue("@name", $"%{name}%");using (var reader = command.ExecuteReader()){while (reader.Read()){students.Add(new Student{Id = Convert.ToInt32(reader["id"]),Name = reader["name"].ToString(),Age = Convert.ToInt32(reader["age"]),Grade = reader["grade"].ToString()});}}}GD.Print($"已获取 {students.Count} 行数据包含该名称的学生: {name}");}catch (Exception e){GD.PrintErr($"根据名称获取学生失败: {e.Message}");}return students;}
}
SQLiteBridge.cs

该文件是用于演示的执行表的增删改查的逻辑代码,使用该代码,我们需要建立一个node2d或者node节点,将代码挂载上去,并编译完成之后启动游戏

using Godot;
using System;
using System.Collections.Generic;
using SQLitePCL;public partial class SQLiteBridge : Node2D
{public override void _Ready(){Batteries_V2.Init(); // 此方法用于初始化 SQLitePCLRaw 的包提供程序GD.Print("Starting SQLite database operations...");// 测试数据库操作TestDatabaseOperations();GD.Print("All database operations completed");}private void TestDatabaseOperations(){// 1. 添加学生GD.Print("\n=== 添加学生 ===");var student1 = new Student("张三", 20, "大三");var student2 = new Student("李四", 19, "大二");var student3 = new Student("王五", 21, "大三");StudentDAO.AddStudent(student1);StudentDAO.AddStudent(student2);StudentDAO.AddStudent(student3);// 2. 查询所有学生GD.Print("\n=== 查询所有学生 ===");List<Student> allStudents = StudentDAO.GetAllStudents();foreach (var student in allStudents){GD.Print(student.ToString());}// 3. 根据ID查询学生GD.Print("\n=== 根据ID查询学生 ===");Student retrievedStudent = StudentDAO.GetStudentById(1);if (retrievedStudent != null){GD.Print($"找到学生: {retrievedStudent}");}// 4. 根据姓名查询学生GD.Print("\n=== 根据姓名查找学生 ===");List<Student> studentsByName = StudentDAO.GetStudentsByName("张");foreach (var student in studentsByName){GD.Print($"找到学生: {student}");}// 5. 更新学生信息GD.Print("\n=== 更新学生信息 ===");if (retrievedStudent != null){retrievedStudent.Age = 22;retrievedStudent.Grade = "大四";StudentDAO.UpdateStudent(retrievedStudent);}// 6. 删除学生GD.Print("\n=== 删除学生 ===");StudentDAO.DeleteStudent(2);// 7. 验证删除结果GD.Print("\n=== 验证删除结果 ===");List<Student> remainingStudents = StudentDAO.GetAllStudents();foreach (var student in remainingStudents){GD.Print($"学生: {student}");}}public override void _ExitTree(){}
}
执行结果

执行启动游戏之后,就会看到如下信息

在这里插入图片描述

Starting SQLite database operations...=== 添加学生 ===
数据库表初始化成功
成功打开加密数据库连接
新增学生: 张三, 操作结果: 1
新增学生: 李四, 操作结果: 1
新增学生: 王五, 操作结果: 1=== 查询所有学生 ===
已获取 3 数量学生信息
Student [ID: 1, Name: 张三, Age: 20, Grade: 大三]
Student [ID: 2, Name: 李四, Age: 19, Grade: 大二]
Student [ID: 3, Name: 王五, Age: 21, Grade: 大三]=== 根据ID查询学生 ===
使用ID获取学生信息: 1
找到学生: Student [ID: 1, Name: 张三, Age: 20, Grade: 大三]=== 根据姓名查找学生 ===
已获取 1 行数据包含该名称的学生: 张
找到学生: Student [ID: 1, Name: 张三, Age: 20, Grade: 大三]=== 更新学生信息 ===
更新学生信息: 张三, 操作结果: 1=== 删除学生 ===
使用ID删除学生: 2, 操作结果: 1=== 验证删除结果 ===
已获取 2 数量学生信息
学生: Student [ID: 1, Name: 张三, Age: 22, Grade: 大四]
学生: Student [ID: 3, Name: 王五, Age: 21, Grade: 大三]
All database operations completed

和前一个文章所说一样,我们的数据库是保存在我们的user://路径,因为这个路径是可读写的,所以我们可以在指定文件夹找到该数据库db格式文件

注意:如果需要知道数据库文件存在哪里,请查看我前一篇文章

图形化工具访问数据库

根据如上,我们执行完毕之后,就有了db数据库文件
在这里插入图片描述

  • 但是因为经过加密了,我们原本是可以直接打开数据库看到表资料的,现在是无法直接打开的。

  • 然后我们是在代码里面进行执行解密以达到可以进行表的增删改查操作,外部人员如果想简单修改破解是不容易的

  • 如我使用dbeaver尝试打开加密后的db数据库
    在这里插入图片描述

  • 可以看到它是报错了

[SQLITE_NOTADB] File opened that is not a database file (file is not a database)
  • 这个时候我们需要用上工具了,我有找到一个工具

DB Browser for SQLCipher3.11.2绿色便携版.7z

该工具是免安装的

解压之后找到DB Browser for SQLCipher.exe,然后双击启动
在这里插入图片描述

  • 然后我们点击[打开数据库],找到我们需要解密的db数据库文件

在这里插入图片描述

  • 接着会让我们输入密码,密码就是我们前面设置的密钥,如我设置的ABC123

在这里插入图片描述

  • 如果连接成功会看到表

在这里插入图片描述

  • 并且我们可以右键【浏览表】可以看到我们游戏运行代码时候保存的游戏数据
    在这里插入图片描述

结语

根据如上,我们就可以实现【godot+c#使用godot-sqlite连接数据库并加解密】,以此实现可以用在游戏存档和读档,并且防止篡改游戏数据


文章转载自:

http://7ks8LsEq.yznsx.cn
http://Kbwg2DJP.yznsx.cn
http://3dqzFOZC.yznsx.cn
http://NNN7KHc3.yznsx.cn
http://mjONgOR6.yznsx.cn
http://CKytsZuv.yznsx.cn
http://GzXD6Mu8.yznsx.cn
http://1hsGN93N.yznsx.cn
http://8F7wuhq0.yznsx.cn
http://gFvg7pdH.yznsx.cn
http://YjcPbdhT.yznsx.cn
http://BkMu2WYS.yznsx.cn
http://sfC41LKD.yznsx.cn
http://bscZyWYC.yznsx.cn
http://BSoqZyo0.yznsx.cn
http://4WIktbkW.yznsx.cn
http://cAEqbPEm.yznsx.cn
http://zGqQyZ5k.yznsx.cn
http://tm8qPnFp.yznsx.cn
http://o11a1JvC.yznsx.cn
http://SH5h9pnM.yznsx.cn
http://GZvOG29k.yznsx.cn
http://9ayU9oJ8.yznsx.cn
http://VKCQ3S9a.yznsx.cn
http://BWTb8xxb.yznsx.cn
http://vSZf3stq.yznsx.cn
http://JTzxIsTh.yznsx.cn
http://4RTvcALS.yznsx.cn
http://2oSJTmPC.yznsx.cn
http://aMtqwI3I.yznsx.cn
http://www.dtcms.com/a/382846.html

相关文章:

  • Scikit-learn 机器学习:构建、训练与评估预测模型
  • React学习教程,从入门到精通,React 组件核心语法知识点详解(类组件体系)(19)
  • Java分布式编程:RMI机制
  • 5-12 WPS JS宏 Range数组规范性测试
  • MySQL 的安装、启动、连接(Windows、macOS 和 Linux)
  • (附源码)基于Spring Boot的宿舍管理系统设计
  • Mac下Python3安装
  • C++数组与字符串:从基础到实战技巧
  • 第13课:分布式Agent系统
  • Docker 容器化部署核心实战——Nginx 服务配置与正反向代理原理解析
  • 【分享】中小学教材课本 PDF 资源获取指南
  • 如何用 Git Hook 和 CI 流水线为 FastAPI 项目保驾护航?
  • 安卓旋转屏幕后如何防止数据丢失-ViewModel入门
  • STM32_05_时钟树
  • 元宇宙与体育产业:沉浸式体验重构体育全链条生态
  • LeetCode 每日一题 966. 元音拼写检查器
  • C++密码锁 2023年CSP-S认证真题 CCF信息学奥赛C++ 中小学提高组 第二轮真题解析
  • Vue3 视频播放器完整指南 – @videojs-player/vue 从入门到精通
  • 零售企业数字化转型的道、法、术:基于开源AI大模型AI智能名片S2B2C商城小程序的战略重构
  • 【编号500】(道路分类)广东路网数据广东路网分类数据(2025年)
  • 【PHP7内核剖析】-1.3 FPM
  • 网络编程之UDP广播与粘包问题
  • h3笔记:polygon
  • Unity 性能优化 之 编辑器创建资源优化( 工作流 | 场景 | 预制体)
  • 《Python Web部署应知应会》No3:Flask网站的性能优化和实时监测深度实战
  • 《嵌入式硬件(十):基于IMX6ULL的按键操作》
  • JVM默认栈大小
  • 深度学习实战指南:从神经网络基础到模型优化的完整攻略
  • 浏览器性能测试深度解析:指标、工具与优化实践
  • 【嵌入式DIY实例-ESP32篇】-3D姿态测量(Pitch, Roll, Yaw)