unrpyc完全指南:Ren'Py游戏逆向工程实战

4094 字
20 分钟
unrpyc完全指南:Ren'Py游戏逆向工程实战

一、unrpyc是什么?#

unrpyc的作用和原理#

unrpyc 是一款专门用于反编译Ren’Py引擎游戏脚本文件(.rpyc格式)的开源工具。它的主要作用是将编译后的二进制文件还原为可读的Python源代码(.rpy格式),使开发者能够:

  • 分析游戏源代码结构

  • 提取游戏文本内容

  • 学习游戏开发技巧

  • 修改游戏逻辑和文本

  • 进行本地化和翻译工作

工作原理

Ren’Py引擎使用Python编写,游戏脚本会被编译成字节码格式的.rpyc文件。unrpyc通过解析.rpyc文件中的字节码指令,重建对应的Python语句结构,最终输出为标准的.rpy源文件。这个过程类似于Java反编译或Python字节码反汇编。

支持的Ren’Py版本(特别强调Ren’Py 8)#

unrpyc对Ren’Py版本的支持相当广泛,但存在重要差异:

Ren’Py版本支持状态注意事项
Ren’Py 6.x✅ 完全支持早期版本,兼容性良好
Ren’Py 7.x✅ 完全支持主流版本,功能完善
Ren’Py 8.0.x⚠️ 部分支持需要特定版本unrpyc
Ren’Py 8.1.x+✅ 支持推荐使用最新版unrpyc

Ren’Py 8的特殊性

Ren’Py 8引入了许多新特性和字节码变更,包括:

  • Python 3.9+语法支持

  • 新的AST节点类型

  • 优化的字节码指令集

  • 改进的类型注解系统

这意味着使用unrpyc处理Ren’Py 8游戏时,必须使用最新版本的unrpyc(通常需要2023年以后的版本)。

与其他工具的对比#

工具名称主要功能优势劣势适用场景
unrpycrpyc文件反编译功能强大,持续更新,支持最新版本需要Python环境,使用相对复杂深度代码分析,脚本修改
rpatoolrpa资源包解包操作简单,图形化界面功能单一,仅限资源提取提取图片、音频等资源
rpydecompilerrpyc文件反编译老牌工具,兼容性好更新缓慢,不支持新版Ren’Py旧版本游戏快速解包

选择建议

  • 代码层面操作:优先选择unrpyc

  • 纯资源提取:可考虑rpatool

  • 旧游戏兼容:可尝试rpydecompiler作为备选

二、安装和配置#

Python环境准备#

系统要求

  • Python 3.7 或更高版本(推荐Python 3.9+)

  • pip包管理器

  • 基本命令行操作能力

安装Python

Windows系统

# 1. 从官网下载Python安装包
# 访问 https://www.python.org/downloads/
# 下载对应版本的安装程序
# 2. 安装时务必勾选"Add Python to PATH"
# 3. 验证安装
python --version
pip --version

macOS系统

# 使用Homebrew安装
brew install python3
# 验证安装
python3 --version
pip3 --version

Linux系统

# Ubuntu/Debian
sudo apt-get update
sudo apt-get install python3 python3-pip
# CentOS/RHEL
sudo yum install python3 python3-pip
# 验证安装
python3 --version
pip3 --version

unrpyc安装方法#

方法一:通过pip安装(推荐)

# 安装最新版本
pip install unrpyc
# 或从GitHub安装最新开发版
pip install git+https://github.com/CensoredUsername/unrpyc.git

方法二:从源码安装

# 1. 克隆仓库
git clone https://github.com/CensoredUsername/unrpyc.git
cd unrpyc
# 2. 安装依赖
pip install -r requirements.txt
# 3. 安装工具
python setup.py install

方法三:直接下载使用

# 1. 下载最新发布版本
# 访问 https://github.com/CensoredUsername/unrpyc/releases
# 2. 解压到工作目录
unzip unrpyc-x.x.x.zip
cd unrpyc-x.x.x

配置和测试#

基本配置

# 创建工作目录
mkdir unrpyc_workspace
cd unrpyc_workspace
# 创建配置文件(可选)
cat > config.json << EOF
{
"output_dir": "decompiled",
"backup": true,
"verbose": true,
"encoding": "utf-8"
}
EOF

功能测试

# 1. 查看帮助信息
unrpyc --help
# 2. 创建测试文件
echo 'label start: "Hello, World!"' > test.rpy
# 3. 编译测试文件(需要Ren'Py SDK)
# renpy_sdk/renpy.sh compile .
# 4. 反编译测试
unrpyc game/*.rpyc
# 5. 检查输出
ls decompiled/

预期输出

unrpyc version x.x.x
Decompiling test.rpyc -> decompiled/test.rpy
Successfully decompiled 1 file.

三、基础解包教程#

解包单个rpyc文件#

基本语法

unrpyc <文件路径>

实际操作

game/
# 1. 定位目标文件
# 假设游戏结构如下:
# ├── script.rpyc
# ├── options.rpyc
# └── images/
# 2. 解包单个文件
unrpyc game/script.rpyc
# 3. 指定输出目录
unrpyc game/script.rpyc --output decompiled/
# 4. 查看反编译结果
cat game/script.rpy

输出示例

# Decompiled by unrpyc
label start:
"你好,欢迎来到这个世界!"
menu:
"开始游戏":
jump game_start
"退出":
return
label game_start:
"游戏开始了..."
return

批量解包整个游戏#

方法一:批量解包所有rpyc文件

# 解包game目录下所有rpyc文件
unrpyc game/*.rpyc
# 递归解包所有子目录
find game/ -name "*.rpyc" -exec unrpyc {} \;
# 使用通配符
unrpyc game/ **/*.rpyc

方法二:使用批处理脚本

batch_decompile.sh
#!/bin/bash
GAME_DIR="game"
OUTPUT_DIR="decompiled"
mkdir -p "$OUTPUT_DIR"
for file in "$GAME_DIR"/*.rpyc; do
if [ -f "$file" ]; then
echo "Processing: $file"
unrpyc "$file" --output "$OUTPUT_DIR/"
fi
done
echo "Batch decompilation completed!"

方法三:Python批量处理

import os
import subprocess
def batch_decompile(game_dir, output_dir):
"""批量解包rpyc文件"""
# 确保输出目录存在
os.makedirs(output_dir, exist_ok=True)
# 遍历游戏目录
for root, dirs, files in os.walk(game_dir):
for file in files:
if file.endswith('.rpyc'):
rpyc_path = os.path.join(root, file)
print(f"Processing: {rpyc_path}")
# 执行解包命令
subprocess.run([
'unrpyc',
rpyc_path,
'--output', output_dir
])
if __name__ == "__main__":
batch_decompile('game', 'decompiled')

常见错误和解决#

错误1:找不到文件

Error: File not found: game/script.rpyc

**解决方案 **:

# 检查文件路径
ls -la game/
# 使用绝对路径
unrpyc /path/to/game/script.rpyc

错误2:权限不足

PermissionError: [Errno 13] Permission denied

**解决方案 **:

# Linux/macOS
sudo unrpyc game/*.rpyc
# Windows(以管理员身份运行CMD)
# 右键CMD -> "以管理员身份运行"

错误3:Python版本不兼容

SyntaxError: invalid syntax

**解决方案 **:

# 检查Python版本
python --version
# 如版本过低,升级Python
# 或使用虚拟环境
python -m venv venv
source venv/bin/activate # Linux/macOS
# 或 venv\Scripts\activate # Windows
pip install --upgrade unrpyc

错误4:内存不足

MemoryError: Unable to allocate memory

**解决方案 **:

# 分批处理
unrpyc game/script.rpyc
unrpyc game/options.rpyc
# ...
# 或使用限制内存的参数
unrpyc game/*.rpyc --max-memory 512

四、进阶操作#

处理加密的rpyc文件#

**识别加密文件 **:

# 尝试解包,加密文件会报错
unrpyc encrypted.rpyc
# 输出:Error: File is encrypted or corrupted

**处理方法 **:

方法一:查找解密密钥

import struct
def find_key(file_path):
"""在游戏文件中查找可能的解密密钥"""
with open(file_path, 'rb') as f:
data = f.read()
# 搜索常见密钥模式
patterns = [
b'renpy',
b'archive',
b'key',
b'password'
]
for pattern in patterns:
if pattern in data:
print(f"Found potential pattern: {pattern}")

方法二:使用解密插件

# 安装解密扩展
pip install unrpyc[decrypt]
# 使用密钥解包
unrpyc encrypted.rpyc --key your_decryption_key

方法三:内存提取法

# 1. 运行游戏
# 2. 使用内存分析工具提取密钥
# 3. 使用提取的密钥解包
# Windows使用Process Hacker
# Linux使用gdb

Ren’Py 8版本的特殊处理#

**版本检测 **:

import struct
def detect_renpy_version(rpyc_file):
"""检测rpyc文件的Ren'Py版本"""
with open(rpyc_file, 'rb') as f:
header = f.read(4)
if header.startswith(b'REN'):
version = struct.unpack('I', f.read(4))[0]
return f"Ren'Py {version >> 16}.{version & 0xFFFF}"
return "Unknown version"

**Ren’Py 8特殊处理 **:

# 1. 确保使用最新版unrpyc
pip install --upgrade unrpyc
# 2. 指定Ren'Py 8模式
unrpyc game/*.rpyc --renpy8
# 3. 处理新语法特性
unrpyc game/*.rpyc --python-version 3.9

**处理Python 3.9+语法 **:

# Ren'Py 8生成的代码可能包含新语法
# 反编译后需要手动调整以下内容:
# 1. 类型注解
def old_style(x, y):
pass
def new_style(x: int, y: str) -> bool:
pass
# 2. 海象运算符
if (n := len(items)) > 10:
print(f"Too many items: {n}")
# 3. 字典合并
dict1 = {'a': 1}
dict2 = {'b': 2}
merged = {** dict1, **dict2}

乱码问题解决#

**识别编码问题 **:

# 检查文件编码
file -i script.rpy
# 或
chardetect script.rpy

**解决方案 **:

方法一:指定正确编码

# UTF-8编码
unrpyc game/script.rpyc --encoding utf-8
# GBK编码(中文游戏常见)
unrpyc game/script.rpyc --encoding gbk
# Shift-JIS编码(日文游戏)
unrpyc game/script.rpyc --encoding shift-jis

方法二:编码转换

import codecs
def convert_encoding(input_file, output_file, from_encoding, to_encoding):
"""转换文件编码"""
with codecs.open(input_file, 'r', from_encoding) as f:
content = f.read()
with codecs.open(output_file, 'w', to_encoding) as f:
f.write(content)
# 使用示例
convert_encoding('script.rpy', 'script_utf8.rpy', 'gbk', 'utf-8')

方法三:批量修复编码

fix_encoding.sh
#!/bin/bash
for file in decompiled/*.rpy; do
# 检测当前编码
encoding=$(file -i "$file" | grep -oP 'charset=\K\S+')
# 转换为UTF-8
if [ "$encoding" != "utf-8" ]; then
iconv -f "$encoding" -t utf-8 "$file" > "${file}.tmp"
mv "${file}.tmp" "$file"
echo "Converted: $file from $encoding to utf-8"
fi
done

五、实战案例#

案例1:解包一个简单的游戏#

**目标 **:完整解包一个小型Ren’Py游戏

**步骤 **:

  1. 准备工作
# 创建工作目录
mkdir -p tutorial_game/original
mkdir -p tutorial_game/decompiled
# 复制游戏文件
cp -r path/to/game/* tutorial_game/original/
  1. 分析游戏结构
# 查看游戏文件结构
cd tutorial_game/original
tree -L 2
# 典型结构:
# .
# ├── game/
# │ ├── script.rpyc
# │ ├── options.rpyc
# │ ├── images/
# │ ├── audio/
# │ └── gui/
# ├── renpy/
# └── lib/
  1. 执行解包
# 解包所有rpyc文件
unrpyc game/*.rpyc --output ../decompiled/
# 检查解包结果
ls -lh ../decompiled/
  1. 验证解包结果
# 查看主要脚本
cat ../decompiled/script.rpy
# 检查解包完整性
wc -l ../decompiled/*.rpy

**预期结果 **:

Decompiling game/script.rpyc -> ../decompiled/script.rpy
Decompiling game/options.rpyc -> ../decompiled/options.rpy
Successfully decompiled 2 files.
# script.rpy 内容示例:
# Decompiled by unrpyc
define s = Character("主角")
label start:
scene bg room
s "这是一个简单的Ren'Py游戏示例"
menu:
"继续":
s "好的,让我们继续吧!"
"结束":
s "再见!"
return

案例2:提取游戏中的图片和音频#

**目标 **:从游戏中提取所有资源文件

**步骤 **:

  1. 识别资源文件位置
# 查找资源文件
find game/ -type f \( -name "*.png" -o -name "*.jpg" -o -name "*.ogg" -o -name "*.mp3" \)
# 检查是否有rpa压缩包
find game/ -name "*.rpa"
  1. 解压rpa资源包
# 使用rpatool解压
rpatool -x game/archive.rpa
# 或使用unrpyc的内置解压功能
unrpyc --extract-archive game/archive.rpa --output resources/
  1. 批量提取资源
import os
import shutil
def extract_resources(game_dir, output_dir):
"""提取游戏资源文件"""
# 支持的文件类型
image_extensions = ['.png', '.jpg', '.jpeg', '.webp', '.gif']
audio_extensions = ['.ogg', '.mp3', '.wav', '.flac']
# 创建输出目录
os.makedirs(f"{output_dir}/images", exist_ok=True)
os.makedirs(f"{output_dir}/audio", exist_ok=True)
# 遍历游戏目录
for root, dirs, files in os.walk(game_dir):
for file in files:
file_path = os.path.join(root, file)
ext = os.path.splitext(file)[1].lower()
# 提取图片
if ext in image_extensions:
rel_path = os.path.relpath(file_path, game_dir)
dest_path = os.path.join(output_dir, "images", rel_path)
os.makedirs(os.path.dirname(dest_path), exist_ok=True)
shutil.copy2(file_path, dest_path)
print(f"Copied image: {file}")
# 提取音频
elif ext in audio_extensions:
rel_path = os.path.relpath(file_path, game_dir)
dest_path = os.path.join(output_dir, "audio", rel_path)
os.makedirs(os.path.dirname(dest_path), exist_ok=True)
shutil.copy2(file_path, dest_path)
print(f"Copied audio: {file}")
if __name__ == "__main__":
extract_resources('game', 'extracted_resources')
  1. 整理和分类资源
organize_resources.sh
#!/bin/bash
IMAGES_DIR="extracted_resources/images"
AUDIO_DIR="extracted_resources/audio"
# 按用途分类图片
mkdir -p "$IMAGES_DIR/backgrounds"
mkdir -p "$IMAGES_DIR/characters"
mkdir -p "$IMAGES_DIR/gui"
mkdir -p "$IMAGES_DIR/effects"
# 按文件名模式移动
mv "$IMAGES_DIR"/bg* "$IMAGES_DIR/backgrounds/" 2>/dev/null
mv "$IMAGES_DIR"/char* "$IMAGES_DIR/characters/" 2>/dev/null
mv "$IMAGES_DIR"/ui* "$IMAGES_DIR/gui/" 2>/dev/null
# 按类型分类音频
mkdir -p "$AUDIO_DIR/bgm"
mkdir -p "$AUDIO_DIR/sfx"
mkdir -p "$AUDIO_DIR/voice"
mv "$AUDIO_DIR"/bgm* "$AUDIO_DIR/bgm/" 2>/dev/null
mv "$AUDIO_DIR"/se* "$AUDIO_DIR/sfx/" 2>/dev/null
mv "$AUDIO_DIR"/vo* "$AUDIO_DIR/voice/" 2>/dev/null
echo "Resource organization completed!"

案例3:修改游戏文本并重新打包#

**目标 **:修改游戏文本内容并重新编译

**步骤 **:

  1. 解包和备份
# 原始游戏备份
cp -r game game_backup
# 解包文件
unrpyc game/*.rpyc --output decompiled/
  1. 修改文本内容
# 修改文本的脚本
import re
def modify_text(input_file, output_file, replacements):
"""修改游戏文本"""
with open(input_file, 'r', encoding='utf-8') as f:
content = f.read()
# 执行文本替换
for old_text, new_text in replacements.items():
content = content.replace(old_text, new_text)
# 写入修改后的文件
with open(output_file, 'w', encoding='utf-8') as f:
f.write(content)
# 使用示例
replacements = {
"你好,世界": "Hello, World!",
"欢迎来到游戏": "Welcome to the game",
"开始游戏": "Start Game"
}
modify_text(
'decompiled/script.rpy',
'decompiled/script_modified.rpy',
replacements
)
  1. 高级文本修改(正则表达式)
import re
def advanced_text_modification(input_file, output_file):
"""使用正则表达式进行高级文本修改"""
with open(input_file, 'r', encoding='utf-8') as f:
lines = f.readlines()
modified_lines = []
for line in lines:
# 修改角色对话
if '"' in line and not line.strip().startswith('#'):
# 提取对话内容
match = re.search(r'"([^"]*)"', line)
if match:
original_text = match.group(1)
# 可以添加翻译逻辑或修改逻辑
# 这里简单示例:添加前缀
modified_text = f"[MODIFIED] {original_text}"
line = line.replace(original_text, modified_text)
modified_lines.append(line)
# 写入修改后的文件
with open(output_file, 'w', encoding='utf-8') as f:
f.writelines(modified_lines)
advanced_text_modification(
'decompiled/script.rpy',
'decompiled/script_modified.rpy'
)
  1. 重新编译游戏
# 方法一:使用Ren'Py SDK重新编译
# 1. 将修改后的.rpy文件复制回game目录
cp decompiled/script_modified.rpy game/script.rpy
# 2. 使用Ren'Py SDK编译
cd /path/to/renpy_sdk
./renpy.sh compile /path/to/game
# 方法二:直接替换文件(简单修改)
# 1. 删除原始.rpyc文件
rm game/script.rpyc
# 2. 复制修改后的.rpy文件
cp decompiled/script_modified.rpy game/script.rpy
# 3. 游戏会自动重新编译
  1. 测试修改效果
# 运行游戏测试
cd game
python -m renpy
# 检查是否有语法错误
# Ren'Py会在启动时检查语法错误并显示在控制台

六、常见问题FAQ#

Q1: unrpyc支持Ren’Py 8解包吗?#

**A **: 支持但需要注意版本兼容性。

**详细说明 **:

表格

Ren’Py 8版本unrpyc要求支持程度
8.0.0-8.0.3unrpyc 1.2.0+部分支持
8.1.0+unrpyc 2.0.0+完全支持
最新版本最新版unrpyc完全支持

**解决方案 **:

# 确保使用最新版unrpyc
pip install --upgrade unrpyc
# 检查版本兼容性
unrpyc --version
# 如遇到问题,尝试指定Ren'Py 8模式
unrpyc game/*.rpyc --renpy8

**注意事项 **:

  • Ren’Py 8使用Python 3.9+,确保Python版本匹配

  • 新版Ren’Py可能有未公开的字节码变更

  • 遇到问题时可向unrpyc项目提交issue

Q2: 解包后文件乱码怎么办?#

**A **: 乱码通常是编码问题,可以通过指定正确编码解决。

**解决步骤 **:

  1. 检测文件编码
# 使用file命令
file -i script.rpy
# 使用chardetect工具(需要安装)
pip install chardet
chardetect script.rpy
# 输出示例:script.rpy: utf-8 with confidence 0.99
  1. 常见编码类型
encodings = {
'utf-8': '国际通用编码,大多数现代游戏使用',
'gbk': '简体中文编码,国产游戏常见',
'gb2312': '简体中文编码,较老的国产游戏',
'big5': '繁体中文编码,港台游戏常见',
'shift-jis': '日文编码,日本游戏常见',
'euc-kr': '韩文编码,韩国游戏常见'
}
  1. 批量修复编码
import os
import chardet
def detect_file_encoding(file_path):
"""检测文件编码"""
with open(file_path, 'rb') as f:
raw_data = f.read()
result = chardet.detect(raw_data)
return result['encoding']
def fix_encoding(file_path, target_encoding='utf-8'):
"""修复文件编码"""
# 检测当前编码
current_encoding = detect_file_encoding(file_path)
print(f"File: {file_path}, Current encoding: {current_encoding}")
# 读取并转换编码
try:
with open(file_path, 'r', encoding=current_encoding) as f:
content = f.read()
with open(file_path, 'w', encoding=target_encoding) as f:
f.write(content)
print(f"Converted to: {target_encoding}")
return True
except Exception as e:
print(f"Error: {e}")
return False
# 批量修复
for file in os.listdir('decompiled'):
if file.endswith('.rpy'):
fix_encoding(f'decompiled/{file}')
  1. 手动指定编码解包
# 如果知道原始编码,可以在解包时指定
unrpyc game/script.rpyc --encoding gbk --output decompiled/
# 或使用iconv转换
iconv -f gbk -t utf-8 script.rpy > script_utf8.rpy

Q3: 如何判断游戏是否加密?#

**A **: 通过多种方法可以判断游戏是否使用了加密保护。

**检测方法 **:

  1. 尝试直接解包
# 尝试解包,加密文件会报错
unrpyc game/script.rpyc
# 正常文件输出:
# Decompiling game/script.rpyc -> game/script.rpy
# Successfully decompiled 1 file.
# 加密文件输出:
# Error: Unable to decompile: file is encrypted or corrupted
  1. 检查文件头
import struct
def check_rpyc_header(file_path):
"""检查rpyc文件头"""
with open(file_path, 'rb') as f:
header = f.read(16) # 读取前16字节
print(f"File: {file_path}")
print(f"Header (hex): {header.hex()}")
# 正常rpyc文件通常以特定标识开头
if header.startswith(b'REN'):
print("Status: Normal RPYC file")
return False
elif header.startswith(b'RPA'): # 资源包
print("Status: RPA archive file")
return False
else:
print("Status: Possibly encrypted or custom format")
return True
# 检查所有rpyc文件
for file in os.listdir('game'):
if file.endswith('.rpyc'):
check_rpyc_header(f'game/{file}')
  1. 分析文件内容模式
# 正常文件包含可读文本
strings game/script.rpyc | head -20
# 加密文件内容看起来像随机数据
xxd game/script.rpyc | head -10
  1. 检查游戏配置
def check_game_config(game_dir):
"""检查游戏配置文件"""
options_file = f"{game_dir}/options.rpy"
try:
with open(options_file, 'r', encoding='utf-8') as f:
content = f.read()
# 查找加密相关配置
encryption_keywords = [
'encryption',
'obfuscate',
'compile',
'archive'
]
for keyword in encryption_keywords:
if keyword in content.lower():
print(f"Found encryption-related keyword: {keyword}")
return True
except FileNotFoundError:
print("options.rpy not found")
return False
check_game_config('game')
  1. 使用专业工具检测
# 使用binwalk分析文件结构
binwalk game/script.rpyc
# 使用hexdump查看文件内容
hexdump -C game/script.rpyc | head -20

**判断标准 **:

表格

现象可能原因处理建议
解包报错”encrypted”文件加密需要解密密钥
文件头不是”REN”自定义格式可能需要专用工具
文件大小异常压缩或加密进一步分析
无法读取文本内容加密保护寻找解密方法

**应对策略 **:

  • 轻度加密:尝试查找密钥文件

  • 中度加密:可能需要内存分析

  • 重度加密:可能需要逆向工程

  • 完全加密:建议联系原作者获取授权

附录#

常用命令速查#

# 基本解包
unrpyc game/*.rpyc
# 指定输出目录
unrpyc game/*.rpyc --output decompiled/
# 指定编码
unrpyc game/*.rpyc --encoding utf-8
# Ren'Py 8模式
unrpyc game/*.rpyc --renpy8
# 详细输出
unrpyc game/*.rpyc --verbose
# 批量处理
find game/ -name "*.rpyc" -exec unrpyc {} \;

参考资源#

法律声明#

本文档仅供学习研究使用。在使用unrpyc工具时,请遵守相关法律法规和软件许可协议:

  • 尊重知识产权和版权

  • 不得用于非法破解和盗版

  • 仅限用于个人学习和合法研究

  • 商业使用需要获得相应授权

  • 遵守当地相关法律法规

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

unrpyc完全指南:Ren'Py游戏逆向工程实战
https://www.kshare.top/posts/unrpyc完全指南renpy游戏逆向工程实战/
作者
Kshare
发布于
2026-02-24
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
Kshare
Hello, I'm Kshare.
公告
欢迎来到Kshare站点!近期站点进行升级,欢迎访问和收藏站点!
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
137
分类
12
标签
59
总字数
333,011
运行时长
0
最后活动
0 天前

文章目录