为什么不是直接把 main.gd 扔回 pck

📅 2026/7/20 16:05:22 👁️ 阅读次数 📝 编程学习
为什么不是直接把 main.gd 扔回 pck

因为原游戏原本加载的是:

res://main.gdc
不是:

res://main.gd
所以你不能只改源码文件名就塞回去。更稳的做法是:

先把 .gd 编译回 .gdc
再用新的 .gdc 去替换包里的旧 .gdc
这就是你这两条命令连起来的意义。

  1. 两条命令连起来的完整语义

连起来就是这条链:

修改 main.gd

编译成 main.gdc

把新的 main.gdc 替换进 GD1.pck

得到可运行的 patched 版本
也就是:

第一条命令负责“把源码变回游戏能执行的格式”
第二条命令负责“把新逻辑塞回原游戏包”
6. 用一句最短的话总结

gd 是源码
gdc 是编译后的 Godot 脚本
pck 是 Godot 游戏资源包
两条命令分别是:

gd -> gdc
新 gdc -> patch 进 pck
aes,用的库封装,逸最简单的一集
int __fastcall main(int argc, const char **argv, const char **envp)
{
int i; // [rsp+Ch] [rbp-144h]
AES_KEY aes_key; // [rsp+20h] [rbp-130h] BYREF
char buf[16]; // [rsp+120h] [rbp-30h] BYREF
char ciphertext[24]; // [rsp+130h] [rbp-20h] BYREF
unsigned __int64 v8; // [rsp+148h] [rbp-8h]

v8 =readfsqword(0x28u);
read(0, buf, 0x10u);
if ( strlen("sdpc_key_123456
") == 16 )
{
AES_set_encrypt_key("sdpc_key_123456
", 128, &aes_key);
AES_encrypt(buf, ciphertext, &aes_key);
printf(“Ciphertext: “);
for ( i = 0; i <= 15; ++i )
printf(”%02x”, (unsigned __int8)ciphertext[i]);
putchar(10);
if ( !strcmp(ciphertext, “ae2f680621fb34d6a8ee7019069ded5e”) )
puts(“fail”);
else
puts(“success”);
return 0;
}
else
{
fwrite(“Key must be 16 bytes\n”, 1u, 0x15u, stderr);
return 1;
}
}
已知密文密钥,直接解密呗

解出来是

sdpc{rerepwnpwnp
???

from Crypto.Cipher import AES
key = b"sdpc_key_123456_"
ct = bytes.fromhex(“ae2f680621fb34d6a8ee7019069ded5e”)
pt = AES.new(key, AES.MODE_ECB).decrypt(ct)
print(pt)
ryo ? soyo? mio?
ida打开

int __fastcall main(int argc, const char **argv, const char **envp)
{
Stream *Stream; // rax
unsigned int v4; // eax
char Buffer[1024]; // [rsp+20h] [rbp-60h] BYREF
char *Str1; // [rsp+420h] [rbp+3A0h]
char *Str2; // [rsp+428h] [rbp+3A8h]

_main();
Str2 = “U0RQQ1NFQ3t3ZWxjb21lX05ld1N0YXJzfQ==”;
puts(“please input your flag:”);
Stream = __iob_func();
fgets(Buffer, 1024, Stream);
Buffer[strcspn(Buffer, “\n”)] = 0;
v4 = strlen(Buffer);
Str1 = (char *)base64_encode(Buffer, v4);
if ( !Str1 )
return 1;
if ( !strcmp(Str1, Str2) )
puts(“Correct”);
else
puts(“False”);
free(Str1);
return 0;
}
直接base64解码

SDPCSEC{welcome_NewStars}
逸别卷了,喝一杯茶休息一下
int _fastcall main(int argc, const char **argv, const char **envp)
{
size_t n; // rax
size_t n_1; // rax
uint32_t dest
[2]; // [rsp+18h] [rbp-28h] BYREF
uint32_t key[4]; // [rsp+20h] [rbp-20h] BYREF
char buf[8]; // [rsp+30h] [rbp-10h] BYREF
unsigned __int64 v9; // [rsp+38h] [rbp-8h]

v9 =readfsqword(0x28u);
read(0, buf, 8u);
memset(key, 0, sizeof(key));
n = strlen(“sdpc”);
memcpy(key, “sdpc”, n);
*(QWORD *)dest= 0;
n_1 = strlen(buf);
memcpy(dest
, buf, n_1);
tea_encrypt(dest
, key);
printf(“Encrypted: %08x%08x\n”, dest_[0], dest_[1]);
if ( !strcmp((const char *)dest_, " 9dcfccd3088d1064") )
puts(“fail”);
else
puts(“success”);
return 0;
}
read(0, buf, 8u);
从标准输入(键盘)读最多 8 个字节,存到 buf 数组里。

read 是一个系统提供的基础函数。
第一个参数 0 = 标准输入(键盘)。
第二个参数 buf = 读到的内容存到哪里。
第三个参数 8u = 最多读 8 个字节。
所以你在键盘上打东西,程序会取前 8 个字符存到 buf。
memset(key, 0, sizeof(key));
把 key 数组的每一个字节都设为 0。这叫清零。

memset = memory set(设置内存)。
为什么这么做?因为 key 声明时没有指定初始值,里面的内容是垃圾值(内存里之前残留的数据),必须先清零才能用。
sizeof(int) // → 4 (int 占 4 字节)
sizeof(char) // → 1 (char 占 1 字节)
sizeof(“abc”) // → 4 (字符串 “abc” 实际是 ‘a’,‘b’,‘c’,‘\0’,共 4 字节)
int arr[10];
sizeof(arr) // → 40 (10 × 4 = 40 字节)
用来返回目标在内存中占多少字节
memcpy(key, “sdpc”, n);
memcpy = memory copy(复制内存)。把字符串 “sdpc” 的 4 个字节复制到 key 数组的开头。
做完这一步,key 数组的内容是:
key[0] = “sdpc” 的前 4 个字节(‘s’,‘d’,‘p’,‘c’)
key[1] = 0
key[2] = 0
key[3] = 0

(QWORD *)dest = 0;
把 dest_ 数组的前 8 个字节全部设为 0。QWORD 是 8 字节整数, 是解引用(把 dest当成那 8 字节看),效果相当于 dest_[0] = 0 且 dest_[1] = 0。就是清零。

memcpy(dest_, buf, n_1);
把你输入的 n_1 个字符复制到 dest_ 数组里。dest_ 的前 4 字节被当作一个叫 dest_[0] 的整数,后 4 字节被当作 dest_[1]。
为什么要这样搞?因为 dest_ 声明的是 uint32_t 数组(每个元素 4 字节),但你输入的是字符串(一个一个字符)。它用 memcpy 把这 8 个字符直接覆盖到 dest_ 的 8 字节上。这样,你输入的 8 个字符就变成了两个 32 位整数——这是把字符串当数字去加密的前提。