Skip to content

Commit

Permalink
Merge branch 'NickHugi/master' into better-loader-installation-dialog
Browse files Browse the repository at this point in the history
  • Loading branch information
th3w1zard1 committed Mar 14, 2024
2 parents 0e4f4e5 + d2c2895 commit bcec9e4
Show file tree
Hide file tree
Showing 27 changed files with 119 additions and 75 deletions.
4 changes: 4 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@
"./Libraries/PyKotorFont/src",
"./Libraries/Utility/src",
],
"remote.WSL.fileWatcher.polling": true,
"files.watcherExclude": {
"**/.git/**": true
},
"python.testing.autoTestDiscoverOnSaveEnabled": true,
"python.testing.promptToConfigure": true,
"python.testing.pytestEnabled": false,
Expand Down
5 changes: 4 additions & 1 deletion Libraries/PyKotor/src/pykotor/common/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@

from __future__ import annotations

from collections.abc import Mapping
from enum import Enum, IntEnum
from typing import TYPE_CHECKING, ClassVar, Generic, ItemsView, Iterable, Iterator, Mapping, TypeVar, overload
from typing import TYPE_CHECKING, ClassVar, Generic, ItemsView, Iterable, Iterator, TypeVar, overload

from pykotor.common.geometry import Vector3

if TYPE_CHECKING:
import os

from collections.abc import ItemsView, Iterable, Iterator

T = TypeVar("T")
VT = TypeVar("VT")
_unique_sentinel = object()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ def compare(self, other: LIP, log_func=print) -> bool:

return ret

# mapping in LipSyncEditor is not very accurate. for example, shape 0 is neutral, not EE,
# mapping in LipSyncEditor is not very accurate. for example, shape 0 is neutral, not EE,
# meaning pauses are not properly represented when using LipSyncEditor. shape 15 is too open to be KG and etc.
# https://imgur.com/a/LIRZ8B1
class LIPShape(IntEnum):
Expand Down
28 changes: 23 additions & 5 deletions Libraries/PyKotor/src/pykotor/resource/formats/tpc/tpc_auto.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from __future__ import annotations

import os

from typing import TYPE_CHECKING

from pykotor.common.stream import BinaryReader
Expand Down Expand Up @@ -73,6 +75,7 @@ def read_tpc(
source: SOURCE_TYPES,
offset: int = 0,
size: int | None = None,
txi_source: SOURCE_TYPES | None = None,
) -> TPC:
"""Returns an TPC instance from the source.
Expand All @@ -83,6 +86,7 @@ def read_tpc(
source: The source of the data.
offset: The byte offset of the file inside the data.
size: Number of bytes to allowed to read from the stream. If not specified, uses the whole stream.
txi_source: An optional source to the TXI data to use. If this is a filepath, it *must* exist on disk.
Raises:
------
Expand All @@ -98,11 +102,25 @@ def read_tpc(
file_format: ResourceType = detect_tpc(source, offset)

if file_format == ResourceType.TPC:
return TPCBinaryReader(source, offset, size or 0).load()
if file_format == ResourceType.TGA:
return TPCTGAReader(source, offset, size or 0).load()
msg = "Failed to determine the format of the TPC/TGA file."
raise ValueError(msg)
loaded_tpc = TPCBinaryReader(source, offset, size or 0).load()
elif file_format == ResourceType.TGA:
loaded_tpc = TPCTGAReader(source, offset, size or 0).load()
else:
msg = "Failed to determine the format of the TPC/TGA file."
raise ValueError(msg)
if txi_source is None and isinstance(source, (os.PathLike, str)):
txi_source = CaseAwarePath.pathify(source).with_suffix(".txi")
if not txi_source.safe_isfile():
return loaded_tpc

elif isinstance(txi_source, (os.PathLike, str)):
txi_source = CaseAwarePath.pathify(txi_source).with_suffix(".txi")

if txi_source is None:
return loaded_tpc
with BinaryReader.from_auto(txi_source) as f:
loaded_tpc.txi = f.read_all().decode(encoding="ascii", errors="ignore")
return loaded_tpc


def write_tpc(
Expand Down
14 changes: 9 additions & 5 deletions Libraries/PyKotor/src/pykotor/tools/path.py
Original file line number Diff line number Diff line change
Expand Up @@ -401,9 +401,10 @@ def get_default_paths() -> dict[str, dict[Game, list[str]]]:
},
"Linux": {
Game.K1: [
"~/.local/share/Steam/common/SteamApps/swkotor",
"~/.local/share/Steam/common/steamapps/swkotor",
"~/.local/share/Steam/common/swkotor",
"~/.local/share/steam/common/steamapps/swkotor",
"~/.local/share/steam/common/steamapps/swkotor",
"~/.local/share/steam/common/swkotor",
"~/.steam/debian-installation/steamapps/common/swkotor" # verified
"~/.steam/root/steamapps/common/swkotor", # executable name is `KOTOR1` no extension
# wsl paths
"/mnt/C/Program Files/Steam/steamapps/common/swkotor",
Expand All @@ -414,10 +415,13 @@ def get_default_paths() -> dict[str, dict[Game, list[str]]]:
"/mnt/C/Amazon Games/Library/Star Wars - Knights of the Old",
],
Game.K2: [
"~/.local/share/Steam/common/SteamApps/Knights of the Old Republic II",
"~/.local/share/Steam/common/steamapps/Knights of the Old Republic II",
"~/.local/share/Steam/common/steamapps/kotor2", # guess
"~/.local/share/aspyr-media/kotor2",
"~/.local/share/Steam/common/Knights of the Old Republic II",
"~/.local/share/aspyr-media/Knights of the Old Republic II", # guess
"~/.local/share/Steam/common/Knights of the Old Republic II", # ??? wrong?
"~/.steam/debian-installation/steamapps/common/Knights of the Old Republic II" # guess
"~/.steam/debian-installation/steamapps/common/kotor2" # guess
"~/.steam/root/steamapps/common/Knights of the Old Republic II", # executable name is `KOTOR2` no extension
# wsl paths
"/mnt/C/Program Files/Steam/steamapps/common/Knights of the Old Republic II",
Expand Down
2 changes: 1 addition & 1 deletion Libraries/PyKotor/src/pykotor/tools/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ def create_registry_path(hive, path): # sourcery skip: raise-from-previous-erro
try:
winreg.CreateKey(hive, current_path)
except PermissionError:
raise PermissionError(f"Permission denied. Administrator privileges required.") from e # noqa: B904, TRY003, EM101
raise PermissionError("Permission denied. Administrator privileges required.") from e # noqa: B904, TRY003, EM101
except Exception as e: # pylint: disable=W0718 # noqa: BLE001
# sourcery skip: raise-specific-error
raise Exception(f"Failed to create registry key: {current_path}. Error: {e}") # noqa: TRY002, TRY003, EM102, B904
Expand Down
2 changes: 1 addition & 1 deletion Libraries/PyKotorGL/src/pykotor/gl/models/mdl.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ def draw(self, shader: Shader, transform: mat4, override_texture: str | None = N
if self.mesh and self.render:
self.mesh.draw(shader, transform, override_texture)

for child in self.children:
for child in self.children:
child.draw(shader, transform, override_texture=override_texture)


Expand Down
1 change: 1 addition & 0 deletions Libraries/Utility/src/utility/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ def generate_hash(
data_input: bytes | bytearray | memoryview | os.PathLike | str,
hash_algo: str = "sha1", # sha1 is faster than md5 in python somehow
chunk_size: int = 262144, # 256KB default
*,
always_chunk: bool = False, # Don't unnecessarily chunk bytes/bytearray inputs.
) -> str:
# Create a hash object for the specified algorithm
Expand Down
18 changes: 9 additions & 9 deletions Libraries/Utility/src/utility/string.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,21 @@
from typing_extensions import LiteralString, Self, SupportsIndex

def insert_newlines(text: str, length: int = 100) -> str:
words = text.split(' ')
new_string = ''
current_line = ''
words = text.split(" ")
new_string = ""
current_line = ""

for word in words:
if len(current_line) + len(word) + 1 <= length:
current_line += word + ' '
current_line += word + " "
else:
new_string += current_line.rstrip() + '\n'
current_line = word + ' '
new_string += current_line.rstrip() + "\n"
current_line = word + " "

# Add the last line if there's any content left.
if current_line:
new_string += current_line.rstrip()

return new_string

def ireplace(original: str, target: str, replacement: str) -> str:
Expand Down
2 changes: 1 addition & 1 deletion Tools/HoloPatcher/src/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ def parse_args() -> Namespace:
return kwargs


class App():
class App:
def __init__(self):
self.root = tk.Tk()
self.root.title(f"HoloPatcher {VERSION_LABEL}")
Expand Down
3 changes: 2 additions & 1 deletion Tools/HolocronToolset/src/toolset/gui/dialogs/asyncloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,8 @@ def _onFailed(self, error: Exception):
file.write("\n----------------------\n")

if self.errorTitle:
QMessageBox(QMessageBox.Critical, self.errorTitle, str(universal_simplify_exception(error))).exec_()
error_msg = str(universal_simplify_exception(error)).replace("\n", "<br>")
QMessageBox(QMessageBox.Critical, self.errorTitle, error_msg).exec_()


class AsyncWorker(QThread):
Expand Down
6 changes: 4 additions & 2 deletions Tools/HolocronToolset/src/toolset/gui/editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,10 +202,11 @@ def saveAs(self):
identifier = ResourceIdentifier.from_path(filepath_str).validate()
except ValueError as e:
print(format_exception_with_variables(e))
error_msg = str(universal_simplify_exception(e)).replace("\n", "<br>")
QMessageBox(
QMessageBox.Critical,
"Invalid filename/extension",
f"Check the filename and try again. Could not save!{os.linesep * 2}{universal_simplify_exception(e)}",
f"Check the filename and try again. Could not save!<br><br>{error_msg}",
).exec_()
return

Expand Down Expand Up @@ -266,7 +267,8 @@ def save(self):
lines = format_exception_with_variables(e)
file.writelines(lines)
file.write("\n----------------------\n")
QMessageBox(QMessageBox.Critical, "Failed to write to file", str(universal_simplify_exception(e))).exec_()
error_msg = str(universal_simplify_exception(e)).replace("\n", "<br>")
QMessageBox(QMessageBox.Critical, "Failed to write to file", error_msg).exec_()

def _saveEndsWithBif(self, data: bytes, data_ext: bytes):
"""Saves data if dialog returns specific options.
Expand Down
1 change: 1 addition & 0 deletions Tools/HolocronToolset/src/toolset/gui/editors/are.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

from PyQt5.QtWidgets import QLabel, QWidget

from pykotor.extract.file import ResourceResult
from pykotor.resource.formats.bwm.bwm_data import BWM
from pykotor.resource.formats.lyt.lyt_data import LYT
from pykotor.resource.formats.tpc.tpc_data import TPC
Expand Down
3 changes: 2 additions & 1 deletion Tools/HolocronToolset/src/toolset/gui/editors/erf.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,10 +298,11 @@ def addResources(self, filepaths: list[str]):
lines = format_exception_with_variables(e)
file.writelines(lines)
file.write("\n----------------------\n")
error_msg = str(universal_simplify_exception(e)).replace("\n", "<br>")
QMessageBox(
QMessageBox.Critical,
"Failed to add resource",
f"Could not add resource at {c_filepath.absolute()}:\n{universal_simplify_exception(e)}",
f"Could not add resource at {c_filepath.absolute()}:<br><br>{error_msg}",
).exec_()

def selectFilesToAdd(self):
Expand Down
6 changes: 0 additions & 6 deletions Tools/HolocronToolset/src/toolset/gui/editors/gff.py
Original file line number Diff line number Diff line change
Expand Up @@ -809,12 +809,6 @@ def selectTalkTable(self):
Args:
----
self: The class instance
Processing Logic:
----------------
- Open a file dialog to select a TLK file
- Get the selected file path and filter from the dialog
- If a file is selected, load it as a TalkTable and assign to self._talktable.
"""
filepath, filter = QFileDialog.getOpenFileName(self, "Select a TLK file", "", "TalkTable (*.tlk)")
if not filepath:
Expand Down
6 changes: 2 additions & 4 deletions Tools/HolocronToolset/src/toolset/gui/editors/nss.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,14 +183,12 @@ def build(self) -> tuple[bytes | None, bytes]:
if self._restype != ResourceType.NCS:
return self.ui.codeEdit.toPlainText().encode("windows-1252"), b""

compiled_bytes: bytes | None = compileScript(self.ui.codeEdit.toPlainText(), self._installation.tsl, self._installation.path())
print("compiling script from nsseditor")
compiled_bytes: bytes | None = compileScript(self.ui.codeEdit.toPlainText(), self._installation.tsl, self._installation.path())
if compiled_bytes is None:
print("user cancelled the compilation")
return None, b""

msg = "Could not convert to bytes - nsseditor.build()"
raise ValueError(msg)
return compiled_bytes, b""

def new(self):
super().new()
Expand Down
1 change: 0 additions & 1 deletion Tools/HolocronToolset/src/toolset/gui/editors/tlk.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
from toolset.gui.editor import Editor
from toolset.gui.widgets.settings.installations import GlobalSettings
from toolset.utils.window import addWindow, openResourceEditor
from utility.misc import is_debug_mode

if TYPE_CHECKING:
import os
Expand Down
6 changes: 5 additions & 1 deletion Tools/HolocronToolset/src/toolset/gui/editors/tpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from pykotor.resource.formats.tpc import TPC, TPCTextureFormat, read_tpc, write_tpc
from pykotor.resource.formats.tpc.io_tga import _DataTypes
from pykotor.resource.type import ResourceType
from pykotor.tools.path import CaseAwarePath
from toolset.gui.editor import Editor

if TYPE_CHECKING:
Expand Down Expand Up @@ -88,7 +89,10 @@ def load(self, filepath: os.PathLike | str, resref: str, restype: ResourceType,
# Read image, convert to RGB, and y_flip.
orig_format = None
if restype in {ResourceType.TPC, ResourceType.TGA}:
self._tpc = read_tpc(data)
txi_filepath: CaseAwarePath | None = CaseAwarePath.pathify(filepath).with_suffix(".txi")
if not txi_filepath.safe_isfile():
txi_filepath = None
self._tpc = read_tpc(data, txi_source=txi_filepath)
orig_format = self._tpc.format()
width, height, img_bytes = self._tpc.convert(TPCTextureFormat.RGB, 0, y_flip=True)
self._tpc.set_data(width, height, [img_bytes], TPCTextureFormat.RGB)
Expand Down
7 changes: 4 additions & 3 deletions Tools/HolocronToolset/src/toolset/gui/editors/twoda.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from pykotor.resource.formats.twoda import TwoDA, read_2da, write_2da
from pykotor.resource.type import ResourceType
from toolset.gui.editor import Editor
from utility.error_handling import assert_with_variable_trace
from utility.error_handling import assert_with_variable_trace, universal_simplify_exception

if TYPE_CHECKING:
import os
Expand Down Expand Up @@ -117,8 +117,9 @@ def load(self, filepath: os.PathLike | str, resref: str, restype: ResourceType,

try:
self._load_main(data)
except ValueError:
QMessageBox(QMessageBox.Critical, "Failed to load file.", "Failed to open or load file data.").exec_()
except ValueError as e:
error_msg = str(universal_simplify_exception(e)).replace("\n", "<br>")
QMessageBox(QMessageBox.Critical, "Failed to load file.", f"Failed to open or load file data.<br>{error_msg}").exec_()
self.proxyModel.setSourceModel(self.model)
self.new()

Expand Down
5 changes: 3 additions & 2 deletions Tools/HolocronToolset/src/toolset/gui/widgets/main_widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from typing import TYPE_CHECKING

from PyQt5 import QtCore
from PyQt5.QtCore import QPoint, QSortFilterProxyModel, QThread, QTimer
from PyQt5.QtCore import QPoint, QSortFilterProxyModel, QThread, QTimer, Qt
from PyQt5.QtGui import QIcon, QImage, QPixmap, QStandardItem, QStandardItemModel, QTransform
from PyQt5.QtWidgets import QHeaderView, QMenu, QWidget

Expand Down Expand Up @@ -311,6 +311,7 @@ def __init__(self, parent: QWidget):

self.texturesModel = QStandardItemModel()
self.texturesProxyModel = QSortFilterProxyModel()
self.texturesProxyModel.setFilterCaseSensitivity(Qt.CaseInsensitive)
self.texturesProxyModel.setSourceModel(self.texturesModel)
self.ui.resourceList.setModel(self.texturesProxyModel)

Expand Down Expand Up @@ -427,7 +428,7 @@ def scan(self):
sleep(0.1)

def onFilterStringUpdated(self):
self.texturesProxyModel.setFilterFixedString(self.ui.searchEdit.text().casefold())
self.texturesProxyModel.setFilterFixedString(self.ui.searchEdit.text())

def onSectionChanged(self):
self.sectionChanged.emit(self.ui.sectionCombo.currentData(QtCore.Qt.UserRole))
Expand Down
18 changes: 14 additions & 4 deletions Tools/HolocronToolset/src/toolset/gui/windows/help.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,15 +76,15 @@ def _setupContentsRecJSON(self, parent: QTreeWidgetItem | None, data: dict):
if "structure" in data:
for title in data["structure"]:
item = QTreeWidgetItem([title])
item.setData(0, QtCore.Qt.UserRole, data["structure"][title]["filename"]) # type: ignore[attr-defined]
item.setData(0, QtCore.Qt.UserRole, data["structure"][title]["filename"])
add(item)
self._setupContentsRecJSON(item, data["structure"][title])

def _setupContentsRecXML(self, parent: QTreeWidgetItem | None, element: ElemTree.Element):
add: Callable[..., None] = self.ui.contentsTree.addTopLevelItem if parent is None else parent.addChild

for child in element:
item = QTreeWidgetItem([child.get("name")]) # FIXME: typing
item = QTreeWidgetItem([child.get("name", "")])
item.setData(0, QtCore.Qt.UserRole, child.get("file"))
add(item)
self._setupContentsRecXML(item, child)
Expand Down Expand Up @@ -170,17 +170,27 @@ def task():
loader = AsyncLoader(self, "Download newer help files...", task, "Failed to update.")
if loader.exec_():
self._setupContents()
except Exception as e:
except (ConnectionError, requests.HTTPError, requests.ConnectionError, requests.RequestException):
error_msg = str(universal_simplify_exception(e)).replace("\n", "<br>")
QMessageBox(
QMessageBox.Information,
"Unable to fetch latest version of the help booklet.",
(
f"{universal_simplify_exception(e)}\n"
f"{error_msg}<br>"
"Check if you are connected to the internet."
),
QMessageBox.Ok,
self,
).exec_()
except Exception as e:
error_msg = str(universal_simplify_exception(e)).replace("\n", "<br>")
QMessageBox(
QMessageBox.Information,
"An unexpected error occurred while fetching the help booklet.",
error_msg,
QMessageBox.Ok,
self,
).exec_()

def _downloadUpdate(self):
help_path = Path("help").resolve()
Expand Down
Loading

0 comments on commit bcec9e4

Please sign in to comment.