================================================================================
1. 下载存档。
2. 将ZIP文件解压到电脑上的任意位置。
3. 打开你首选语言的文件夹,以阅读或使用原始 .txt 资源。
短信之所以棘手,是因为它使用嵌套ID而非纯文本。虽然“碎片”通常以连续块的形式存储,但.json文件中的短信往往包含大量交叉引用(本地化键值或十六进制ID)。在游戏中,一条文本通常由发件人ID、收件人ID、时间戳以及实际文本片段动态组合而成;如果你的脚本只是简单地读取语言文件,就会缺少这些必要的关联信息。
剧透:
显示
导入 os
导入 json
PROJECT_DIR = r"C:\Users\Admin\Desktop\赛博朋克工作\所有赛博朋克文本\01_游戏原始数据"
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
主工作区 = os.path.dirname(当前目录)
OUTPUT_SHARDS = os.path.join(MAIN_WORKSPACE, "03_已完成日志文本_拆分器")
OUTPUT_SMS = os.path.join(MAIN_WORKSPACE, "04_FERTIGE_TEXTE_FUER_SMS_NPC")
OUTPUT_CODEX = os.path.join(MAIN_WORKSPACE, "05_完成文本_用于法典_词典")
OUTPUT_MAILS = os.path.join(MAIN_WORKSPACE, "06_用于电脑邮件的完成文本")
加载JSON文件(文件路径)
如果os.path.exists(filepath):
尝试:
以只读模式打开文件路径并以UTF-8编码读取,返回解析后的JSON数据。
except Exception as e: print(f"[警告] 加载时出错: {e}")
无
在根文件夹中查找包含指定内容且扩展名匹配的精确文件
遍历根目录下的所有目录、子目录和文件:
如果 "archiv" 在 root 的小写形式中:继续
对于文件中的文件:
如果必须包含的小写文本在文件小写内容中且文件以小写扩展名结尾:
如果“journal”在must_contain.lower()中且“onscreen”在file.lower()中:继续
返回 os.path.join(root, file)
无
定义解析文本条目函数(数据):
条目 = {}
def scan_dict(node):
如果节点是字典类型:
如果“femaleVariant”在节点中:
pk_val = node.get("primaryKey")
如果 pk_val 是字典且包含 "value" 键:pk_val = pk_val["value"]
txt_val = 节点["女性变体"]
如果 isinstance(txt_val, dict) 且 "value" 在 txt_val 中:txt_val = txt_val["value"]
如果主键值且有文本值:
entries[str(pk_val).strip()] = str(txt_val).strip()
对于node.values()中的v:scan_dict(v)
否则如果isinstance(node, list):
遍历 node 中的每一项:scan_dict(item)
扫描字典(数据)
返回条目
def 清理锁匙值(val):
如果 not val: 返回 ""
val_str = str(val).strip()
如果“lockey#”在val_str.lower()中:
返回 val_str.lower().replace("lockey#", "").strip()
返回 val_str
def scan_journal_all(node, current_cat="其他", current_contact="未知", codex_cat="通用", mail_owner="Computer_Dateien"):
并行扫描整个日志中的所有4种内容类型
碎片,短信列表,法典,邮件 = [], [], [], []
如果节点是字典类型:
node_type = str(node.get("$type", ""))
node_id = str(node.get("id", "")).lower()
# 碎片与词典的分类映射
如果 node_id != “”:
s_map = {"world": "世界", "literature": "文学", "religion": "宗教与哲学", "art": "艺术", "prospectus": "宣传册", "articles": "文章", "tech": "科技", "note": "笔记", "poetry": "诗歌", "lyric": "诗歌", "character": "夜之城人物", "cyberpsycho": "赛博精神病"}
对于 s_map.items() 中的 eng、de:
如果 node_id 中包含 eng:current_cat = de;跳出
c_map = {"vehicle": "载具", "car": "载具", "weapon": "武器与制造商", "gang": "帮派与阵营", "corpo": "企业", "district": "区域与地点", "glossary": "术语表", "biography": "角色传记", "history": "历史与背景设定"}
对于 c_map.items() 中的 eng, de:
如果 node_id 中包含 eng:codex_cat = de; break
如果“files”在node_id中或者“email”在node_id中或者“group”在node_id中:
如果去除了首尾空白的node_id还有内容:将node_id中的“_files”和“_emails”替换为空字符串,再去除首尾空白,然后转换为首字母大写形式,赋值给mail_owner
如果节点类型中包含“gameJournalContact”或节点类型的小写形式中包含“contact”:
如果“id”在节点中且节点中的“id”是字符串类型:current_contact = str(node["id"]).strip().title()
1. 匹配:CODEX / LEXIKON(支持同一节点或子节点中的描述)
如果“gameJournalCodex”在节点类型中或“codex”在节点类型的小写形式中:
c_title = clean_lockey(node["title"].get("value")) if "title" in node and isinstance(node["title"], dict) else ""
在当前节点或嵌套节点中查找真实文本ID
c_body = ""
对于 ["description", "text", "content"] 中的 k:
如果k在node中且isinstance(node[k], dict):
c_body = clean_lockey(node[k].get("value"))
如果 c_body:中断
如果 c_body 或 c_title:
codex.append({"title_id": c_title, "body_id": c_body, "category": codex_cat})
# 2. 比赛:笔记本电脑文件与邮件
否则,如果节点类型中包含“gameJournalFile”或“gameJournalEmail”,或者节点类型的小写形式中包含“file”或“email”:
t_id = clean_lockey(node["title"].get("value")) if "title" in node and isinstance(node["title"], dict) else ""
b_id = ""
对于["content", "description", "text"]中的k:
如果 k 在 node 中且 isinstance(node[k], dict):
b_id = clean_lockey(node[k].get("value"))
如果b_id:中断
如果 b_id 或 t_id:mails.append({"title_id": t_id, "body_id": b_id, "owner": mail_owner})
# 3. 比赛:普通分裂者
elif "gameJournalOnscreen" in node_type or "onscreen" in node_type.lower():
t_id = clean_lockey(node["title"].get("value")) if "title" in node and isinstance(node["title"], dict) else ""
b_id = clean_lockey(node["description"].get("value")) if "description" in node and isinstance(node["description"], dict) else ""
s_id = clean_lockey(node["text"].get("value")) if "text" in node and isinstance(node["text"], dict) else ""
如果 t_id 或 b_id:shards.append({"title_id": t_id, "body_id": b_id, "sign_id": s_id, "category": current_cat})
# 4. 匹配:NPC-SMS
否则如果“gameJournalPhoneMessage”出现在节点类型中,或“phonemessage”出现在节点类型的小写形式中:
is_player = node.get("isPlayer", 0)
如果字符串形式的is_player在["0", "False", "false"]中:
msg_id = clean_lockey(node["text"].get("value")) if "text" in node and isinstance(node["text"], dict) else ""
如果 msg_id:sms_list.append({"msg_id": msg_id, "contact": current_contact})
对于node.values()中的v
如果v是字典或列表类型:
s_res, m_res, c_res, e_res = scan_journal_all(v, current_cat, current_contact, codex_cat, mail_owner)
shards.extend(s_res); sms_list.extend(m_res); codex.extend(c_res); mails.extend(e_res)
elif isinstance(node, list):
对于 node 中的 item:
s_res, m_res, c_res, e_res = scan_journal_all(item, current_cat, current_contact, codex_cat, mail_owner)
碎片列表扩展(s_res);短信列表扩展(m_res);图鉴扩展(c_res);邮件扩展(e_res)
返回碎片、短信列表、法典、邮件
提取分流器
print("启动赛博朋克全能工作室(V4 - 词典更新)...")
main_dir, dlc_dir = os.path.join(PROJECT_DIR, "主游戏"), os.path.join(PROJECT_DIR, "DLC")
main_text_path = find_exact_file(main_dir, "onscreens", ".json")
main_journal_path = find_exact_file(main_dir, "journal", ".json")
dlc_text_path = find_exact_file(dlc_dir, "ep1_onscreens", ".json")
dlc_journal_path = find_exact_file(dlc_dir, "ep1_cooked", ".json") or find_exact_file(dlc_dir, "cooked_journal", ".json")
print("\n[INFO] 正在读取语言文件...")
text_map = {}
对于[main_text_path, dlc_text_path]中的路径:
如果路径不存在或路径无效则继续
数据 = 加载JSON(路径)
如果数据存在:
found_map = parse_text_entries(data)
text_map.update(found_map)
print(f"-> 已从 {os.path.basename(path)} 学习到 {len(found_map)} 行文本。")
打印("\n[信息] 正在按碎片、短信、邮件和词典分析开发者数据……")
j_shards, j_sms, j_codex, j_mails = [], [], [], []
对于 j_path 在 [main_journal_path, dlc_journal_path] 中:
如果不满足 j_path 或 os.path.exists(j_path):继续
j_data = load_json(j_path)
如果 j_data:
s_f, m_f, c_f, e_f = scan_journal_all(j_data)
j_shards.extend(s_f); j_sms.extend(m_f); j_codex.extend(c_f); j_mails.extend(e_f)
print(f"-> 已成功处理 {os.path.basename(j_path)} 中的关联项。")
# === 处理 1:碎片 ===
print(f"\n[信息] 正在合成 {len(j_shards)} 个碎片...")
碎片计数, 已用碎片 = 0, set()
对于 j_shards 中的 pkg:
body_id = pkg["body_id"]
title_id = pkg["title_id"]
sign_id = pkg["sign_id"]
category = pkg["category"]
title_text = text_map.get(title_id, "").replace("\\n", "\n").strip()
body_text = text_map.get(body_id, "").replace("\n", "\n").strip()
sign_text = text_map.get(sign_id, "").replace("\\n", "\n").strip()
如果没有正文文本而有标题文本:
body_text, title_text, body_id = title_text, "", title_id
如果没有正文文本或正文标识已在已使用碎片中,则继续。
used_shards.add(body_id)
{身体标识}\n\n
如果 title_text:final_content += f“{title_text.upper()}\n\n”
final_content += body_text
如果 sign_text:final_content += f"\n\n{sign_text}"
cat_dir = os.path.join(OUTPUT_SHARDS, category)
创建目录,如果目录已存在则不报错
name_source = title_text if title_text else body_text.split("\n")
clean_name = "".join([c for c in name_source if c.isalpha() or c.isspace() or c.isdigit()]).strip()
file_name = f"{body_id}_{clean_name[:20]}.txt" if clean_name else f"Splitter_{body_id}.txt"
用 open(os.path.join(cat_dir, file_name), "w", encoding="utf-8") 作为 out_f:out_f.write(final_content)
碎片数量 += 1
# === 处理 2: NPC短信 ===
print(f"[INFO] 正在按角色整理收到的短信...")
短信计数,已用短信 = 0, set()
对于 j_sms 中的 msg:
msg_id = msg["msg_id"]
contact = msg["contact"]
sms_text = text_map.get(msg_id, "").replace("\\n", "\n").strip()
如果短信文本为空或消息ID已使用过,则跳过本次循环。
已使用短信.添加(消息编号)
final_sms_content = f"{msg_id}\n\n{sms_text}"
char_dir = os.path.join(OUTPUT_SMS, contact)
创建目录(如果不存在则自动创建)
clean_sms_name = "".join([c for c in sms_text.split("\n") if c.isalpha() or c.isspace() or c.isdigit()]).strip()
sms_file_name = f"{msg_id}_{clean_sms_name[:20]}.txt" if clean_sms_name else f"SMS_{msg_id}.txt"
with open(os.path.join(char_dir, sms_file_name), "w", encoding="utf-8") as out_f: out_f.write(final_sms_content)
短信计数 += 1
# === 处理 3:图鉴 / 词典 ===
print(f"[信息] 按类别整理Codex词条...")
图鉴数量,已使用图鉴 = 0, set()
对于j_codex中的c_entry:
body_id = c_entry["body_id"]
title_id = c_entry["title_id"]
类别 = c_entry["category"]
title_text = text_map.get(title_id, "").replace("\n", "\n").strip()
body_text = text_map.get(body_id, "").replace("\n", "\n").strip()
如果没有正文文本而有标题文本:
body_text, title_text, body_id = title_text, "", title_id
如果正文为空,或正文编号已在已用图鉴中,则继续
已使用.codex.add(body_id)
{body_id}\n\n
如果 title_text:final_c += f“{title_text.upper()}\n\n”
final_c += body_text
os.makedirs(os.path.join(OUTPUT_CODEX, category), exist_ok=True)
lines = body_text.split("\n")
name_source = title_text if title_text else (lines if lines else "Codex")
c_name = "".join([c for c in name_source if c.isalpha() or c.isspace() or c.isdigit()]).strip()
用 open(os.path.join(OUTPUT_CODEX, category, f"{body_id}_{c_name[:20]}.txt"), "w", encoding="utf-8") as f: f.write(final_c)
codex_count += 1
# === 处理 4:电脑邮件 ===
打印(f"[INFO] 按所有者对笔记本电脑邮件和系统文件进行排序...")
邮件数量,已用邮件 = 0,集合()
对于 j_mails 中的邮件:
body_id = mail["body_id"]
title_id = mail["title_id"]
owner = mail["owner"]
title_text = text_map.get(title_id, "").replace("\\n", "\n").strip()
body_text = text_map.get(body_id, "").replace("\\n", "\n").strip()
如果没有正文文本而有标题文本:
body_text, title_text, body_id = title_text, "", title_id
如果没有正文文本或正文ID已在已使用邮件中:继续
已用邮件.添加(正文编号)
{body_id}\n\n
如果 title_text: final_m += f"{title_text.upper()}\n\n"
final_m += body_text
使用exist_ok=True创建输出邮件目录中所有者对应的文件夹,路径通过os.path.join拼接。
lines = body_text.split("\n")
name_source = title_text if title_text else (lines if lines else "邮件")
c_name = "".join([c for c in name_source if c.isalpha() or c.isspace() or c.isdigit()]).strip()
用open(os.path.join(OUTPUT_MAILS, owner, f"{body_id}_{c_name[:20]}.txt"), "w", encoding="utf-8") as f: f.write(final_m)
邮件数量+=1
print(f"\n===========================================================")
print(f"[成功] {shard_count} 个碎片已原样集齐!")
print(f"[成功] 已提取 {sms_count} 条传入的 NPC 消息!")
print(f"[成功] {codex_count} 条法典词条已整理!")
print(f"[成功] 已将{mail_count}封电脑邮件按所有者分类!")
打印(f"\n-> 存档已准备于: {MAIN_WORKSPACE}")
print(f"===========================================================")
输入(“\n按回车键关闭……”)
如果 __name__ == "__main__":
提取分流器()
https://www.nexusmods.com/cyberpunk2077/mods/30884?tab=description <-- 我为德语语音制作的项目