Skip to content

Commit f97571a

Browse files
Initial Upload
Upload the first files.
1 parent b71ee7b commit f97571a

20 files changed

Lines changed: 3713 additions & 0 deletions

aboutWindow.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from uip import ui_aboutWindow
2+
from PySide6 import QtWidgets
3+
import webbrowser
4+
5+
6+
class AboutWindow(ui_aboutWindow.Ui_Dialog, QtWidgets.QDialog):
7+
VERSION = "1.0.0"
8+
9+
def __init__(self, parent=None):
10+
super().__init__(parent=parent)
11+
self.setupUi(self)
12+
13+
self.label_version.setText(self.VERSION)
14+
self.setupSignals()
15+
16+
def setupSignals(self):
17+
self.pushButton.clicked.connect(
18+
lambda: webbrowser.open(
19+
"https://github.com/wangruoyuyuyu/PySide6-GUI-For-Asset-Ripper"
20+
)
21+
)
22+
23+
24+
if __name__ == "__main__":
25+
qa = QtWidgets.QApplication(list())
26+
aw = AboutWindow()
27+
aw.show()
28+
qa.exec()

api.py

Lines changed: 352 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,352 @@
1+
import requests
2+
import _thread
3+
import parser
4+
import re
5+
import chardet # 导入chardet库用于编码检测
6+
from urllib.parse import quote, unquote
7+
from json import dumps
8+
9+
PORT = 18374
10+
11+
12+
class StatusMachine(object):
13+
onApiCallFinished = lambda res: (res)
14+
onReplaceFileFinished = lambda res: (res)
15+
16+
def __init__(self):
17+
pass
18+
19+
20+
def decode_response(response):
21+
"""通用解码函数,使用chardet检测编码并解码"""
22+
# 检测响应内容的编码
23+
detected_encoding = chardet.detect(response.content)["encoding"]
24+
25+
# 处理可能的编码检测错误
26+
if detected_encoding is None:
27+
detected_encoding = "utf-8" # 默认使用utf-8
28+
29+
try:
30+
# 尝试用检测到的编码解码
31+
return response.content.decode(detected_encoding)
32+
except UnicodeDecodeError:
33+
# 解码失败时 fallback 到utf-8并忽略错误
34+
return response.content.decode("utf-8", errors="ignore")
35+
36+
37+
def open_file(port=PORT):
38+
_thread.start_new_thread(_open_file, (port,))
39+
40+
41+
def _open_file(port=PORT):
42+
res = requests.post(
43+
f"http://localhost:{port}/LoadFile",
44+
headers={"Content-Type": "application/x-www-form-urlencoded"},
45+
data={"value": "Open File"},
46+
)
47+
# 修改响应处理方式
48+
decoded_content = decode_response(res)
49+
# 将解码后的内容附加到响应对象,方便后续使用
50+
res.decoded_text = decoded_content
51+
StatusMachine.onApiCallFinished(res)
52+
53+
54+
def open_folder(port=PORT):
55+
_thread.start_new_thread(_open_folder, (port,))
56+
57+
58+
def _open_folder(port=PORT):
59+
res = requests.post(
60+
f"http://localhost:{port}/LoadFolder",
61+
headers={"Content-Type": "application/x-www-form-urlencoded"},
62+
data={"value": "Open Folder"},
63+
)
64+
decoded_content = decode_response(res)
65+
res.decoded_text = decoded_content
66+
StatusMachine.onApiCallFinished(res)
67+
68+
69+
# 注意:原代码中有重复定义的_open_file函数,这里保留一个
70+
def _open_file(port=PORT):
71+
res = requests.post(
72+
f"http://localhost:{port}/LoadFile",
73+
headers={"Content-Type": "application/x-www-form-urlencoded"},
74+
data={"value": "Open File"},
75+
)
76+
decoded_content = decode_response(res)
77+
res.decoded_text = decoded_content
78+
StatusMachine.onApiCallFinished(res)
79+
80+
81+
def reset(port=PORT):
82+
_thread.start_new_thread(_reset, (port,))
83+
84+
85+
def _reset(port=PORT):
86+
res = requests.post(
87+
f"http://localhost:{port}/Reset",
88+
headers={"Content-Type": "application/x-www-form-urlencoded"},
89+
data={"value": "Reset"},
90+
)
91+
decoded_content = decode_response(res)
92+
res.decoded_text = decoded_content
93+
StatusMachine.onApiCallFinished(res)
94+
95+
96+
def check_is_file(port=PORT) -> bool:
97+
res = requests.get(f"http://localhost:{port}/")
98+
# 使用解码后的内容进行判断
99+
decoded_content = decode_response(res)
100+
return "View Loaded Files" in decoded_content
101+
102+
103+
def check_version(port=PORT) -> dict:
104+
res = requests.get(f"http://localhost:{port}/")
105+
decoded_content = decode_response(res)
106+
return parser.extract_version_info(decoded_content)
107+
108+
109+
def get_settings(port=PORT) -> dict:
110+
res = requests.get(f"http://localhost:{port}/Settings/Edit")
111+
decoded_content = decode_response(res)
112+
return parser.extract_form_data(decoded_content), parser.parse_form_names(
113+
decoded_content
114+
)
115+
116+
117+
def get_loaded_files(port=PORT, path={"P": []}, from_url=False, url="") -> dict:
118+
if not from_url:
119+
res = requests.get(
120+
f"http://localhost:{port}/Bundles/View?path={quote(dumps(path))}",
121+
headers={"host": "127.0.0.1"},
122+
)
123+
else:
124+
if url.startswith("/"):
125+
res = requests.get(f"http://localhost:{port}{url}")
126+
elif url.startswith("http"):
127+
res = requests.get(url)
128+
else:
129+
res = requests.get(f"http://localhost:{port}/{url}")
130+
131+
decoded_content = decode_response(res)
132+
return parser.parse(decoded_content)
133+
134+
135+
def get_loaded_collections(port=PORT, path={"P": []}, from_url=False, url="") -> dict:
136+
if not from_url:
137+
res = requests.get(
138+
f"http://localhost:{port}/Collections/View?path={quote(dumps(path))}",
139+
headers={"host": "127.0.0.1"},
140+
)
141+
else:
142+
if url.startswith("/"):
143+
res = requests.get(f"http://localhost:{port}{url}")
144+
elif url.startswith("http"):
145+
res = requests.get(url)
146+
else:
147+
res = requests.get(f"http://localhost:{port}/{url}")
148+
149+
decoded_content = decode_response(res)
150+
return parser.extract_tables(decoded_content)
151+
152+
153+
def get_loaded_assets(port=PORT, path={"P": []}, from_url=False, url="") -> dict:
154+
if not from_url:
155+
res = requests.get(
156+
f"http://localhost:{port}/Assets/View?path={quote(dumps(path))}",
157+
headers={"host": "127.0.0.1"},
158+
)
159+
else:
160+
if url.startswith("/"):
161+
res = requests.get(f"http://localhost:{port}{url}")
162+
elif url.startswith("http"):
163+
res = requests.get(url)
164+
else:
165+
res = requests.get(f"http://localhost:{port}/{url}")
166+
167+
decoded_content = decode_response(res)
168+
return parser.parse_tab_tables(decoded_content)
169+
170+
171+
def get_loaded_text(
172+
port=PORT, path={"P": []}, type_="Yaml", from_url=False, url=""
173+
) -> dict:
174+
if not from_url:
175+
res = requests.get(
176+
f"http://localhost:{port}/Assets/{type_}?path={quote(dumps(path))}",
177+
headers={"host": "127.0.0.1"},
178+
)
179+
else:
180+
if url.startswith("/"):
181+
res = requests.get(f"http://localhost:{port}{url}")
182+
elif url.startswith("http"):
183+
res = requests.get(url)
184+
else:
185+
res = requests.get(f"http://localhost:{port}/{url}")
186+
187+
return decode_response(res)
188+
189+
190+
def get_loaded_font_name(url, port=PORT):
191+
if url.startswith("/"):
192+
res = requests.get(f"http://localhost:{port}{url}")
193+
elif url.startswith("http"):
194+
res = requests.get(url)
195+
else:
196+
res = requests.get(f"http://localhost:{port}/{url}")
197+
198+
if "Content-Disposition" in res.headers.keys():
199+
return parser.extract_filename(res.headers["Content-Disposition"])
200+
return None
201+
202+
203+
def get_video_name(url, port=PORT):
204+
full_url = f"http://localhost:{port}{url}"
205+
header_res = requests.get(full_url, stream=True)
206+
cd = header_res.headers.get("content-disposition")
207+
return extract_filename(cd)
208+
209+
210+
def extract_filename(content_disposition: str) -> str:
211+
"""
212+
从Content-Disposition头部提取文件名,优先处理filename*=UTF-8''格式
213+
214+
参数:
215+
content_disposition: Content-Disposition头部字符串
216+
返回:
217+
解码后的文件名,优先使用filename*的值
218+
"""
219+
if not content_disposition:
220+
return ""
221+
222+
# 1. 优先匹配 filename*=UTF-8'' 格式(RFC 5987标准,支持中文等特殊字符)
223+
# 模式说明:匹配 filename*=UTF-8'' 后面的编码内容,直到分号或结束
224+
utf8_pattern = r"filename\*=UTF-8''(.*?)(?:;|$)"
225+
utf8_match = re.search(utf8_pattern, content_disposition, re.IGNORECASE)
226+
227+
if utf8_match and utf8_match.group(1):
228+
try:
229+
# 解码URL编码(如%E4%B8%AD%E6%96%87 -> 中文)
230+
return unquote(utf8_match.group(1))
231+
except Exception as e:
232+
print(f"解码UTF-8文件名失败: {e}")
233+
234+
# 2. 若未匹配到filename*,再匹配普通filename格式
235+
# 支持带引号、不带引号、单引号、双引号的情况
236+
normal_pattern = r"filename=(?:[\"']?)(.*?)(?:[\"']?)(?:;|$)"
237+
normal_match = re.search(normal_pattern, content_disposition, re.IGNORECASE)
238+
239+
if normal_match and normal_match.group(1):
240+
return normal_match.group(1).strip("\"'") # 去除可能的引号
241+
242+
# 3. 未匹配到任何文件名
243+
return ""
244+
245+
246+
def get_audio_name(url: str, port: int, file_name="Audio", default="ogg"):
247+
full_url = f"http://localhost:{port}{url}"
248+
head_req = requests.get(full_url, stream=True)
249+
extension = head_req.headers.get("content-type", default=default)
250+
if extension != default:
251+
extension = extension.split("/")[1]
252+
extension = "." + extension
253+
return file_name + extension
254+
255+
256+
def get_loaded_resources(port=PORT, path={"P": []}, from_url=False, url="") -> dict:
257+
if not from_url:
258+
res = requests.get(
259+
f"http://localhost:{port}/Resources/View?path={quote(dumps(path))}",
260+
headers={"host": "127.0.0.1"},
261+
)
262+
else:
263+
if url.startswith("/"):
264+
res = requests.get(f"http://localhost:{port}{url}")
265+
elif url.startswith("http"):
266+
res = requests.get(url)
267+
else:
268+
res = requests.get(f"http://localhost:{port}/{url}")
269+
270+
decoded_content = decode_response(res)
271+
return parser.parse_resources_html(decoded_content)
272+
273+
274+
def post_settings(settings: dict, port=PORT):
275+
res = requests.post(
276+
f"http://localhost:{port}/Settings/Update",
277+
headers={"content-type": "application/x-www-form-urlencoded"},
278+
data=settings,
279+
)
280+
return res.status_code
281+
282+
283+
def get_configs(port=PORT):
284+
res = requests.get(f"http://localhost:{port}/ConfigurationFiles")
285+
decoded_content = decode_response(res)
286+
return parser.extract_config_data(decoded_content)
287+
288+
289+
def replace_config_file(name: str, port=PORT):
290+
_thread.start_new_thread(_replace_config_file, (name, port))
291+
292+
293+
def _replace_config_file(name: str, port=PORT):
294+
res = requests.post(
295+
f"http://localhost:{port}/ConfigurationFiles/Singleton/Add", data={"Key": name}
296+
)
297+
decoded_content = decode_response(res)
298+
res.decoded_text = decoded_content
299+
StatusMachine.onReplaceFileFinished(res)
300+
301+
302+
def remove_config_file(name: str, port=PORT):
303+
_thread.start_new_thread(_remove_config_file, (name, port))
304+
305+
306+
def _remove_config_file(name: str, port=PORT):
307+
res = requests.post(
308+
f"http://localhost:{port}/ConfigurationFiles/Singleton/Remove",
309+
data={"Key": name},
310+
)
311+
decoded_content = decode_response(res)
312+
res.decoded_text = decoded_content
313+
StatusMachine.onReplaceFileFinished(res)
314+
315+
316+
def load_file_or_folder_from_path(path: str, port=PORT):
317+
_thread.start_new_thread(_load_file_or_folder_from_path, (path, port))
318+
319+
320+
def _load_file_or_folder_from_path(path: str, port=PORT):
321+
res = requests.post(
322+
f"http://localhost:{port}/LoadFolder",
323+
headers={"content-type": "application/x-www-form-urlencoded"},
324+
data={"path": path},
325+
)
326+
decoded_content = decode_response(res)
327+
res.decoded_text = decoded_content
328+
StatusMachine.onApiCallFinished(res)
329+
330+
331+
def export_unity_project(path: str, create_sub_folder: bool, port=PORT):
332+
_thread.start_new_thread(_export_unity_project, (path, create_sub_folder, port))
333+
334+
335+
def _export_unity_project(path: str, create_sub_folder: bool, port=PORT):
336+
requests.post(
337+
f"http://127.0.0.1:{port}/Export/UnityProject",
338+
headers={"content-type": "application/x-www-form-urlencoded"},
339+
data={"Path": path, "CreateSubfolder": create_sub_folder},
340+
)
341+
342+
343+
def export_primary_content(path: str, create_sub_folder: bool, port=PORT):
344+
_thread.start_new_thread(_export_primary_content, (path, create_sub_folder, port))
345+
346+
347+
def _export_primary_content(path: str, create_sub_folder: bool, port=PORT):
348+
requests.post(
349+
f"http://127.0.0.1:{port}/Export/PrimaryContent",
350+
headers={"content-type": "application/x-www-form-urlencoded"},
351+
data={"Path": path, "CreateSubfolder": create_sub_folder},
352+
)

0 commit comments

Comments
 (0)