Giter Club home page Giter Club logo

pykotor's People

Contributors

actions-user avatar bagelboi avatar chrisvigil avatar joenotcharles avatar nickhugi avatar th3w1zard1 avatar vkremianskii avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

pykotor's Issues

Missing support: !FieldPath

[gff_hk47_RepliesList_6]
FieldType=List
Label=RepliesList
AddField0=gff_hk47_RepliesList_0_6
[gff_hk47_RepliesList_0_6]
FieldType=Struct
Label=
TypeId=0
AddField0=gff_hk47_Index_8
[gff_hk47_Index_8]
FieldType=DWORD
Label=Index
Value=2DAMEMORY42
2DAMEMORY55=!FieldPath

Again from RepairHK47. Seems to need some inner way to connect all the nodes when a Path or a Label isn't provided as both of those seem to be optional for this syntax

Better handling of nwnnsscomp

  • Quickly set registry key when compiling with nwnnsscomp and set it back to whatever it was in a ‘finally’ block of a try-finally
  • prompt user for missing includes if the error contains any.

Incorrect resource search order for [GFFList]

The correct definition for GFFList patches is:

            capsule = None
            if is_capsule_file(gff_patch.destination.name):
                capsule = Capsule(gff_output_container_path)

            # TSLPatcher follows the following behavior:
            # if !ReplaceFile=0:
            # 1. Get the resource from the Modules (if we're patching to one).
            # 2. Get the resource from Override (if we're patching there).
            # 3. If still not found, get it from tslpatchdata
            # if !ReplaceFile=1:
            # 1. Get the resource from tslpatchdata.
            # 2. If not found, get the resource from the Module we're patching (if we're patching to one).
            # 3. If still not found, get the resource from Override (if we're patching there)
            game_resource_location = SearchLocation.OVERRIDE if capsule is None else SearchLocation.CUSTOM_MODULES
            if gff_patch.replace_file:
                search_order = [
                    SearchLocation.CUSTOM_FOLDERS,
                    game_resource_location,
                ]
            else:
                search_order = [
                    game_resource_location,
                    SearchLocation.CUSTOM_FOLDERS,
                ]

            resname, restype = ResourceIdentifier.from_path(gff_patch.filename)
            search = installation.resource(
                resname,
                restype,
                search_order,
                folders=[self.mod_path],
                capsules=[] if capsule is None else [capsule],
            )
            if search is None or search.data is None:
                self.log.add_error(
                    f"Didn't patch '{gff_patch.filename}' because search data is `None`.",
                )
                continue

KeyError in [CompileList] (Problem with NCS compiler?)

Had this error just a few times but for multiple mods:

[7/2/2023 7:10:11 PM] Using changes.ini path: C:\Users\*****\Documents\k1 mods\KillCzerkaJerk\tslpatchdata\changes.ini

[7/2/2023 7:10:12 PM] Traceback (most recent call last):
  File "pykotorcli.py", line 66, in <module>
  File "pykotor\tslpatcher\config.py", line 118, in install
  File "pykotor\tslpatcher\config.py", line 113, in config
  File "pykotor\tslpatcher\config.py", line 77, in load
  File "pykotor\tslpatcher\reader.py", line 57, in load
  File "pykotor\tslpatcher\reader.py", line 202, in load_nss
  File "configparser.py", line 979, in __getitem__
KeyError: 'kas22_attack.nss'

Relevant changes.ini example that'll reproduce:

; =====================================================[v1.0.5b1]====
; TSLPATCHER - GENERATED MODIFICATIONS FILE (25/03/2021)
; ===================================================================
; This file is automatically generated and as such has no formatting
; to speak of. You can insert blank lines between sections (but NOT
; between keys within a section!) and add comment lines starting
; with semicolon to make it more readable without breaking anything.
; -------------------------------------------------------------------

[Settings]
FileExists=1
WindowCaption=Request: Kill the Czerka jerk on Kashyyyk
ConfirmMessage=N/A
LogLevel=3
InstallerMode=1
BackupFiles=1
PlaintextLog=0
LookupGameFolder=0
LookupGameNumber=2
SaveProcessedScripts=0


[TLKList]


[InstallList]
install_folder0=Override


[2DAList]


[GFFList]


[CompileList]
Replace0=kas22_attack.nss


[SSFList]


; ===================================================================

[install_folder0]
Replace0=kas22_czerkag_01.dlg
Replace1=kas22_czerkag_01.utc
Replace2=kas22_czerkag_02.utc
Replace3=kas22_czerkag_03.utc
Replace4=kas22_czerkag_04.utc
Replace5=kas22_czerka2_01.utc

Issues while reading or writing mdl files

I am trying to batch convert KOTOR models to ASCII format. I executed pip install pykotor and wrote the following application(reduced)

from pykotor.resource.formats.mdl import read_mdl, write_mdl
from pykotor.resource.type import ResourceType
model = read_mdl(original_files_folder + file_name)
write_mdl(model, converted_files_folder + converted_file_name, ResourceType.MDL_ASCII)

but unfortunatelly around 30% of files from models.bif archite are failed with some errors.
For example:

write_mdl list index out of range
Files: dor_lhr02.mdl, dor_lma01.mdl, c_drdwar.mdl, c_drdastro.mdl ...

read_mdl 501 is not a valid SurfaceMaterial
Files: c_dewback.mdl,lqa_dewback.mdl, m01aa_c01_char02.mdl, m08aa_05a.mdl ...

read_mdl 38 is not a valid SurfaceMaterial
Files: m12aa_c03_char02.mdl, m12aa_c04_char01.mdl ...

read_mdl 8195 is not a valid SurfaceMaterial
Files: m02aa_09b.mdl...

read_mdl unpack requires a buffer of 4 bytes
Files: m12aa_c04_cam.mdl, m12ab_mgt02.mdl, m12ab_mgt05.mdl

Patcher - Missing support for rarely used keys: `!SourceFile`, `!DefaultDestination`, `!SourceFileF`

From the TSLPatcher docs:

  • Added the HackList modifiers to the Configuration Summary display. Added
    name of source file if different from the destination file name (configured with
    the new !SourceFile and !SaveAs keys)."
  • Added an optional !DefaultDestination key to the [CompileList] section
    which will determine where the NCS files should be put if no specific
    destination has been set. Default value if the key is left out is the override
    folder as before. In addition to override it can be set the the relative path (from
    the game folder) and name of an ERF or RIM file to insert the scripts into. This
    value can then be overridden with the !Destination key for individual files as
    before.

Decompiling with nwnnsscomp requires installations to match registry dirs

When NWNNSSComp.exe decompiles a ncs, it'll look for the game install based on what's in the registry. So if I have another htinstallation that's currently selected and that htinstallation does not point to a path that's mirrored in the registry, nwnnsscomp will fail with:

Lookup path root set to: C:\Program Files (x86)\Steam\steamapps\common\Knights of the Old Republic II\
Error: Couldn't initialize the NwnStdLoader

this happened when my installation was set to C:\Program Files (x86)\Steam\steamapps\common\Knights of the Old Republic II - TSLRCM

Convert Dependencies to Submodules

Nesting a .git in a subfolder of our main repo creates a submodule. This strategy allows us to point to another repository, enabling these repositories to connect and interact more efficiently. PyKotor has become a giant monster of a project, and it's been difficult to organize things with our current system. This is why I recommend the feature: sub-modules

Organizes Commits:

  • Example with Holopatcher: When releasing a new version of Holopatcher, the commits for other tools like PyKotorGL or PyKotorFont won't clutter the history. Submodules prevent this.
  • Developers can concentrate on specific components by filtering the commit history for the relevant submodule, reducing clutter and enhancing productivity.
  • Encourages a modular approach to code organization, leading to a cleaner, more navigable codebase.

Supports Version-Specific Dependencies:

  • Submodules allow for pinning specific versions of external repositories, crucial for maintaining stability in the face of external changes.

Reduces Risk of Merge Conflicts:

  • Isolating different project areas into submodules minimizes the chance of merge conflicts, making integration smoother.

Allows repos to communicate:

  • Allows the master repo PyKotor to always know where a nested sub-module repo should be pathed inside the master.
  • Submodules enable the master repo, like PyKotor, to reference specific commits in nested submodule repos. This maintains clear and precise references to the exact state of each component within the overall project.

More control over what the user is pulling:

  • Considering the size of toolset kits, submodules allow users to clone only the parts of PyKotor they need, like BWM or PyKotor.Extract.Installation, avoiding unnecessary downloads.

Improved Dependency Management:

  • Submodules help in managing dependencies by linking specific versions to the main project, ensuring consistent performance across various branches

Crash due to unchecked optional in install()

In tslpatcher/config.py line 186, the patcher will crash if search is None. Which seems to happen a lot with files inside mod archives. I am trying to install K1CP (admittedly an unfair test as most builds of TSLPatcher fail with it, but still one of the most popular mods)

This is the relevant section of the output prior to an unrelated crash (which is almost certainly a macOS compatibility issue-- double backslashes getting a FileNotFoundError). I had added a check which prints the "because it is None" message whenever search is None

Patching czerkasoldier.utc in the modules\korr_m33aa.mod archive.
Didn't patch czerkasoldier.utc because it is None.
Patching dan13_ahlan.dlg in the modules\danm13.mod archive.
Didn't patch dan13_ahlan.dlg because it is None.
Patching dan13_bluecryst.uti in the modules\danm13.mod archive.
Didn't patch dan13_bluecryst.uti because it is None.
Patching dan13_goldcryst.uti in the modules\danm13.mod archive.
Didn't patch dan13_goldcryst.uti because it is None.
Patching dan13_grncryst.uti in the modules\danm13.mod archive.
Didn't patch dan13_grncryst.uti because it is None.
Patching dan13_vandar.dlg in the modules\danm13.mod archive.
Didn't patch dan13_vandar.dlg because it is None.
Patching dan13_vrook.utc in the modules\danm13.mod archive.
Didn't patch dan13_vrook.utc because it is None.
Patching dan13_zhar.dlg in the modules\danm13.mod archive.
Didn't patch dan13_zhar.dlg because it is None.
Patching dan13_zhar.utc in the modules\danm13.mod archive.
Didn't patch dan13_zhar.utc because it is None.
Patching dan14_bolook.dlg in the modules\danm14ac.mod archive.
Didn't patch dan14_bolook.dlg because it is None.
Patching dan14_bolook.utc in the modules\danm14ac.mod archive.
Didn't patch dan14_bolook.utc because it is None.
Patching dan14_bolook02.utc in the modules\danm14ac.mod archive.
Didn't patch dan14_bolook02.utc because it is None.
Patching dan14_corpse.utp in the modules\danm14ac.mod archive.
Didn't patch dan14_corpse.utp because it is None.
Patching dan14_cutscene.dlg in the modules\danm14ad.mod archive.
Didn't patch dan14_cutscene.dlg because it is None.
Patching dan14_elise.dlg in the modules\danm14aa.mod archive.
Didn't patch dan14_elise.dlg because it is None.
Patching dan14_handon.utc in the modules\danm14ac.mod archive.
Didn't patch dan14_handon.utc because it is None.
Patching dan14_jedi.dlg in the modules\danm13.mod archive.
Didn't patch dan14_jedi.dlg because it is None.
Patching dan14_juhani.utc in the modules\danm14ac.mod archive.
Didn't patch dan14_juhani.utc because it is None.
Patching dan14_rickard.utc in the modules\danm14ac.mod archive.
Didn't patch dan14_rickard.utc because it is None.
Patching dan14_sdroid.utc in the modules\danm14ad.mod archive.
Didn't patch dan14_sdroid.utc because it is None.
Patching dan14_sherruk.dlg in the modules\danm14ac.mod archive.
Didn't patch dan14_sherruk.dlg because it is None.
Patching dan14aa_casus.utp in the modules\danm14ab.mod archive.
Didn't patch dan14aa_casus.utp because it is None.
Patching dan14ab_duros01.utc in the modules\danm14ab.mod archive.
Didn't patch dan14ab_duros01.utc because it is None.
Patching dan14ab_duros02.utc in the modules\danm14ab.mod archive.
Didn't patch dan14ab_duros02.utc because it is None.
Patching dan14ab_duros03.utc in the modules\danm14ab.mod archive.
Didn't patch dan14ab_duros03.utc because it is None.
Patching dan14ac_duros01.utc in the modules\danm14ac.mod archive.
Didn't patch dan14ac_duros01.utc because it is None.
Patching dan14ac_duros02.utc in the modules\danm14ac.mod archive.
Didn't patch dan14ac_duros02.utc because it is None.
Patching dan15_ancientdrd.dlg in the modules\danm15.mod archive.
Didn't patch dan15_ancientdrd.dlg because it is None.
Patching dan16_nurik.dlg in the modules\danm16.mod archive.
Didn't patch dan16_nurik.dlg because it is None.
Patching dan16_nurik.utc in the modules\danm16.mod archive.
Didn't patch dan16_nurik.utc because it is None.
Patching ebo_bast_vision.dlg in the modules\ebo_m12aa.mod archive.
Didn't patch ebo_bast_vision.dlg because it is None.
Patching ebo_carth.dlg in the modules\ebo_m40ad.mod archive.
Didn't patch ebo_carth.dlg because it is None.
Patching m12aa.git in the modules\ebo_m41aa.mod archive.
Didn't patch m12aa.git because it is None.
Patching end_cut01.dlg in the modules\end_m01aa.mod archive.
Didn't patch end_cut01.dlg because it is None.
Patching end_door19.utd in the modules\end_m01aa.mod archive.
Didn't patch end_door19.utd because it is None.
Patching footlker003.utp in the modules\manm27aa.mod archive.
Didn't patch footlker003.utp because it is None.

No TSLPatcher `!SaveAs` support.

Looking at PyKotor you have the following:

            for name, value in modifications_ini.items():
                if name.lower() == "!destination":
                    modificaitons.destination = value
                elif name.lower() == "!replacefile":
                    modificaitons.replace_file = bool(int(value))
                elif name.lower() == "!filename":
                    modificaitons.filename = value
                elif name.lower().startswith("addfield"):
                    modifier = self.add_field_gff(value, dict(self.ini[value]))
                    modificaitons.modifiers.append(modifier)
                else:
                    modifier = self.modify_field_gff(name, value)
                    modificaitons.modifiers.append(modifier)

TSLPatcher has another field less known called !SaveAs:

My impression was that Fair Strides added !Filename without realizing !SaveAs was already there, maybe because she was working from the original release that didn't have it, I dunno.

From the TSLPatcher readme it says:

"Added the HackList modifiers to the Configuration Summary display. Added
name of source file if different from the destination file name (configured with
the new !SourceFile and !SaveAs keys)."
"Added an optional !DefaultDestination key to the [CompileList] section
which will determine where the NCS files should be put if no specific
destination has been set. Default value if the key is left out is the override
folder as before. In addition to override it can be set the the relative path (from
the game folder) and name of an ERF or RIM file to insert the scripts into. This
value can then be overridden with the !Destination key for individual files as
before."

How to reproduce this bug

Relevant changes.ini snippet:

[man_calo.utc]
!Destination=modules\manm28aa.mod
!SaveAs=n_calonord001.utc
GoodEvil=25
!ReplaceFile=0

Expected TSLPatcher output:

• Modifying GFF format files...
 • GFF FILE: man_calo.utc and man_calo.utc.
 • GFF FILE: man_calo.utc and man_calo.utc.
 • GFF Check: 1_man_calo.utc.
 • GFF Check: 2_C:\Users\boden\Desktop\KotOR1\modules\manm28aa.mod.
 • GFF Check: 3.
 • GFF Check: 4_C:\Users\boden\Documents\k1 mods\NPC_Alignment_Fix_v1_1\tslpatchdata\_erfpath_temp.
 • GFF Check: 5_n_calonord001.utc man_calo.utc..

PyKotor throws some red herring of an error due to this.

Running into non-fatal error when parsing some rtf readmes

When installing: https://deadlystream.com/files/file/312-gaffi-stick-improvement/

It looks like some of the bits of the rtf README are throwing parsing errors in HoloPatcher, this is benign as far as I can tell, but interrupts an automated install (like with KOTORModSync).

Here's the stack trace: let me know if I can give you anything else :)

An unexpected error occurred while loading the patcher namespace. Exception ''utf-8' codec can't decode byte 0xbb in position 36: invalid start byte' of type '<class 'UnicodeDecodeError'>' occurred.
Stack Trace Variables:

Function 'on_namespace_option_chosen' at /tmp/_MEI9fHypt/__main__.py:863:
  self = App(
    browse_button=<tkinter.ttk.Button object .!frame.!button>,
    exit_button=<tkinter.ttk.Button object .!frame4.!button>,
    expand_namespace_description_button=<tkinter.ttk.Button object .!frame.!button2>,
    gamepaths=<tkinter.ttk.Combobox object .!frame.!combobox2>,
    gamepaths_browse_button=<tkinter.ttk.Button object .!frame.!button3>,
    install_button=<tkinter.ttk.Button object .!frame4.!button2>,
    install_running=False,
    log_file_path=PosixPath(/home/synchro/Games/Modding/kotormods...<truncated>
  event = Event(
    <tkinter.Event object at 0x7fa0328a3aa0>
  config_reader = ConfigReader(
    config=PatcherConfig(
        confirm_message='N/A',
        game_number=1,
        ignore_file_extensions=False,
        install_list=[],
        log_level=<LogLevel.WARNINGS: 3>,
        patches_2da...,
    ini=ConfigParser(
        BOOLEAN_STATES={'1': True, 'yes': True, 'true': True, 'on': True, '0': False, 'no': False, 'false': False, 'off': False},
        NONSPACECRE=re.compile('\\S'),
        OPTCR...,
    log=PatchLogger(
        all_logs=[<pykotor.tslpatcher.logger.PatchLog object at 0x7fa0328a3b90>],
        error_observable=Observable(
            callbacks=[]
        ),
        errors=[],
        n...,
    mod_path=CaseAwarePath("//home/synchro/Games/Modding/kotormods/mods/Gaffi Stick Improvement/Gaffi Stick Improvement/tslpatchdata"),
    previously_parsed_sections={'Settings'}
)
  namespace_option = PatcherNamespace(
    DEFAULT_INFO_FILENAME='info.rtf',
    DEFAULT_INI_FILENAME='changes.ini',
    data_folderpath=PurePosixPath(.),
    description='',
    info_filename='info.rtf',
    ini_filename='changes.ini',
    name='Gaffi Stick Improvement - KotOR 1',
    namespace_id=''
)
  changes_ini_path = CaseAwarePath("//home/synchro/Games/Modding/kotormods/mods/Gaffi Stick Improvement/Gaffi Stick Improvement/tslpatchdata/changes.ini")
  reader = ConfigReader(
    config=PatcherConfig(
        confirm_message='N/A',
        game_number=1,
        ignore_file_extensions=False,
        install_list=[],
        log_level=<LogLevel.WARNINGS: 3>,
        patches_2da...,
    ini=ConfigParser(
        BOOLEAN_STATES={'1': True, 'yes': True, 'true': True, 'on': True, '0': False, 'no': False, 'false': False, 'off': False},
        NONSPACECRE=re.compile('\\S'),
        OPTCR...,
    log=PatchLogger(
        all_logs=[<pykotor.tslpatcher.logger.PatchLog object at 0x7fa0328a3b90>],
        error_observable=Observable(
            callbacks=[]
        ),
        errors=[],
        n...,
    mod_path=CaseAwarePath("//home/synchro/Games/Modding/kotormods/mods/Gaffi Stick Improvement/Gaffi Stick Improvement/tslpatchdata"),
    previously_parsed_sections={'Settings'}
)
  game_number = 1
  game = <Game.K1: 1>
  info_rtf_path = CaseAwarePath("//home/synchro/Games/Modding/kotormods/mods/Gaffi Stick Improvement/Gaffi Stick Improvement/tslpatchdata/info.rtf")
  info_rte_path = CaseAwarePath("//home/synchro/Games/Modding/kotormods/mods/Gaffi Stick Improvement/Gaffi Stick Improvement/tslpatchdata/info.rte")
  data = b'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\r\n\xbb Gaffi Stick Improvement ReadMe\r\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\r\nAUTHOR: Fallen Guardian\r\nNAME: Gaffi Stick Improvement\r\nTYPE: Modification\r\nV...
  e = UnicodeDecodeError('utf-8', b'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\r\n\xbb Gaffi Stick Improvement ReadMe\r\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\r\nAUTHOR: Fallen Guardian\r\nNAME: Gaffi Stick Improvemen...

Function 'decode_bytes_with_fallbacks' at /tmp/_MEI9fHypt/pykotor/tools/encoding.pyc:61:
  byte_content = b'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\r\n\xbb Gaffi Stick Improvement ReadMe\r\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\r\nAUTHOR: Fallen Guardian\r\nNAME: Gaffi Stick Improvement\r\nTYPE: Modification\r\nV...
  errors = 'strict'
  encoding = None
  lang = None
  only_8bit_encodings = False
  provided_encoding = 'utf-8'
Traceback (most recent call last):
  File "__main__.py", line 863, in on_namespace_option_chosen
  File "pykotor/tools/encoding.py", line 61, in decode_bytes_with_fallbacks
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xbb in position 36: invalid start byte

[CompileList] saving as .nss extension

I fixed a bug recently where the compiled file would end in .nss extension instead of .ncs.

I am uncertain whether this bug manifested in the code before or after the release of holopatcher v1.4.3. Both master and bleeding-edge have this fixed.

Holocron Toolkit: NSS editor Compile option can write to .rim files even with save to RIM disabled

  1. Make sure the "Allow saving resources to RIM files" setting is turned off.
  2. Open a foo_s.rim file from an installed game.
  3. Open any NCS resource in the .rim. Say it's "a_script".

The NSS editor will open with the filename set to foo_s.rim/a_script.ncs. (Either it'll have the decompiled source of a_script.ncs, or it'll be blank. Doesn't matter either way.)

  1. Paste a completely new script into the editor.
  2. Choose "Compile". (I used nwnnsscomp.exe, haven't tested with the internal compiler.)

The new script will be compiled to an NCS resource and saved over a_script in foo_s.rim, even though "allow saving resources to RIM files" is disabled.

Linux - Issues with Libc on older systems

There's a forwards-compatibility issue with glibc that I've observed on some linux systems during testing. This issue applies directly to our releases, and directly applies to our PyInstaller publish workflow. Their FAQ explains the problem better than I can:

Under Linux, I get runtime dynamic linker errors, related to libc. What should I do?
The executable that PyInstaller builds is not fully static, in that it still depends on the system libc. Under Linux, the ABI of GLIBC is backward compatible, but not forward compatible. So if you link against a newer GLIBC, you can't run the resulting executable on an older system. The supplied binary bootloader should work with older GLIBC. However, the libpython.so and other dynamic libraries still depends on the newer GLIBC. The solution is to compile the Python interpreter with its modules (and also probably bootloader) on the oldest system you have around, so that it gets linked with the oldest version of GLIBC.

Another solution is to use a tool like StaticX to create a fully-static bundled version of your PyInstaller application. StaticX bundles all dependencies, including libc and ld.so. (Python code ➡️ PyInstaller ➡️ StaticX ➡️ Fully-static application)

To be clear: this a problem specific to building binaries with a tool like PyInstaller for a public release. Building from src or running the python scripts directly on Linux does not have any issues.

[CompileList] bug with include scripts

So in a usual [CompileList] section TSLPatcher does the following:

  1. copy all .nss files out of tslpatchdata into a temp working directory for processed scripts
  2. Execute the other lists TLKList, GFFList, InstallList etc
    Then it processes scripts one at a time:
  3. Replace memory tokens 2DAMEMORY and StrRef in each script and save back to that temp directory.
  4. Compile the scripts. If the script is an #include script, nwnnsscomp.exe will not compile it, and tslpatcher will skip it after doing step 3.
  5. Move compiled script to the destination (Override by default, otherwise whatever !DefaultDestination and !Destination are set to).

Version 1.4.3 of holopatcher fails step 3, it currently isolates the processed scripts from the others making memory token replacement in include scripts impossible.
Version 1.4.3 also fails to set the include directory (the library_lookup list argument) when compiling with the built-in. So the built-in compiler currently does not work with include scripts.

Master branch has all of the above fixed, but a bug still remains: step 4 is not observed by the built-in compiler.

Holocron Toolset: Save/Save As in NSS editor always gives an error saving to .ncs

Tested at commit ebcd217 on Windows:

Open any .nss file (I used Vanilla_KOTOR_Script_Source\TSL\Vanilla\Modules\001EBO_Ebon_Hawk_(prologue_interior)\a_end_001.nss) and then use "Save As" and choose an output file with the .ncs file name.

It should pop up a dialog to select a compiler, and then invoke the chosen compiler to save the script as an NCS.

There are 5 cases:

  1. User hits Cancel. It should return without saving, and without an error message.
  2. User chooses nwnnsscomp.exe, which succeeds. It should write to the .ncs file.
  3. User chooses nwnnsscomp.exe, but there's an error in the script. It should show an error message, and not write any file.
  4. User chooses built-in compiler, which succeeds. It should write to the .ncs file.
  5. User chooses built-in compiler, but there's an error in the script. It should show an error message, and not write any file.

For case 2 and 5, I added a call UndefinedFunction(); to the start of the script, which should fail to compile since, you know, the function's undefined.

Results

  1. OK. stdout says:
user exited
compiling script from nsseditor
user cancelled the compilation
  1. BAD: Error dialog with title Failed to write to file and message ('ValueError', ''Could not convert to bytes - nsseditor.build()').

stdout says:

user chose No, compiling with nwnnsscomp
Compiling: C:\Users\JoeNotCharles\temp\tempscript_243ff0.nss
Total Execution time = 15 ms


compiling script from nsseditor

errorlog.txt contains:

Assertion with Exception Trace: Exception 'Could not convert to bytes - nsseditor.build()' of type '<class 'ValueError'>' occurred.
Formatted Traceback:
Traceback (most recent call last):
  File "C:\src\gamedev\kotor\PyKotor\Tools\HolocronToolset\src\toolset\gui\editor.py", line 247, in save
    data, data_ext = self.build()
  File "C:\src\gamedev\kotor\PyKotor\Tools\HolocronToolset\src\toolset\gui\editors\nss.py", line 193, in build
    raise ValueError(msg)
ValueError: Could not convert to bytes - nsseditor.build()

Stack Trace Variables:

Function 'save' at C:\src\gamedev\kotor\PyKotor\Tools\HolocronToolset\src\toolset\gui\editor.py:247:
  self = <toolset.gui.editors.nss.NSSEditor object at 0x00000186F02DFEB0>
  e = ValueError('Could not convert to bytes - nsseditor.build()')
  file = <_io.TextIOWrapper name='errorlog.txt' mode='a' encoding='utf-8'>

Function 'build' at C:\src\gamedev\kotor\PyKotor\Tools\HolocronToolset\src\toolset\gui\editors\nss.py:193:
  self = <toolset.gui.editors.nss.NSSEditor object at 0x00000186F02DFEB0>
  compiled_bytes = b'NCS V1.0B\x00\x00\x04H\x1e\x00\x00\x00\x00\x08 
\x00\x02\x03\x04\x03\x00\x00\x00\x01\x05\x00\x03\x00\x01\x01\x01\xff\xff\xff\xf8\x00\x04\x1b\x00\xff\xff\xff\xfc\x03\x01\xff\xff\xff\xfc\x00\x04\x1f\x00\x00\x00\x00o\x04\x05\x00\x10001EBO_Movie_End\x05\x00\x02D\x01\x04\x03\x00\x00\x00\x00\x0b \x1f\x00\x00\x00\x00B\x04\x03\x00\x00\x00\x01\x04\x05\x00\x10001EBO_Movie_End\x05\x00\x02E\x02\x04\x03\x00\x00\x00\x00\x04\x05\x00\x08permov02\x05\x00\x02\xdd\x02\x1d\x00\x00\x00\x00\x06\x1d\x00\x00\x00\x00\x06\x05\x00\x...<truncated>
  msg = 'Could not convert to bytes - nsseditor.build()'
----------------------
  1. OK: Error dialog with title Failed to write to file and message (FileNotFoundError', "Could not find temp compiled script at 'C:\\Users\\JoeNotCharles\\temp\\tempscript_089466.ncs'").

stdout says:

user chose No, compiling with nwnnsscomp
Compiling: C:\Users\JoeNotCharles\temp\tempscript_089466.nss
Compilation aborted with errors
Total Execution time = 0 ms


C:\Users\JoeNotCharles\temp\tempscript_089466.nss(3): Error: Undeclared identifier "UndefinedFunction"

errorlog.txt contains:

----------------------
Assertion with Exception Trace: Exception 'Could not find temp compiled script at 'C:\Users\JoeNotCharles\temp\tempscript_089466.ncs'' of type '<class 'FileNotFoundError'>' occurred.
Formatted Traceback:
Traceback (most recent call last):
  File "C:\src\gamedev\kotor\PyKotor\Tools\HolocronToolset\src\toolset\gui\editor.py", line 247, in save
    data, data_ext = self.build()
  File "C:\src\gamedev\kotor\PyKotor\Tools\HolocronToolset\src\toolset\gui\editors\nss.py", line 186, in build
    compiled_bytes: bytes | None = compileScript(self.ui.codeEdit.toPlainText(), self._installation.tsl, self._installation.path())
  File "C:\src\gamedev\kotor\PyKotor\Tools\HolocronToolset\src\toolset\utils\script.py", line 181, in compileScript
    return _compile_windows(global_settings, extract_path, source, tsl, installation_path)
  File "C:\src\gamedev\kotor\PyKotor\Tools\HolocronToolset\src\toolset\utils\script.py", line 299, in _compile_windows
    raise FileNotFoundError(f"Could not find temp compiled script at '{tempCompiledPath}'")  # noqa: TRY003, EM102
FileNotFoundError: Could not find temp compiled script at 'C:\Users\JoeNotCharles\temp\tempscript_089466.ncs'

Stack Trace Variables:

Function 'save' at C:\src\gamedev\kotor\PyKotor\Tools\HolocronToolset\src\toolset\gui\editor.py:247:
  self = <toolset.gui.editors.nss.NSSEditor object at 0x00000186F02DFEB0>
  e = FileNotFoundError("Could not find temp compiled script at 'C:\\Users\\JoeNotCharles\\temp\\tempscript_089466.ncs'")
  file = <_io.TextIOWrapper name='errorlog.txt' mode='a' encoding='utf-8'>

Function 'build' at C:\src\gamedev\kotor\PyKotor\Tools\HolocronToolset\src\toolset\gui\editors\nss.py:186:
  self = <toolset.gui.editors.nss.NSSEditor object at 0x00000186F02DFEB0>

Function 'compileScript' at C:\src\gamedev\kotor\PyKotor\Tools\HolocronToolset\src\toolset\utils\script.py:181:
  source = 'void main() {\n\tint nParam1 = GetScriptParameter(1);\n    UndefinedFunction();\n\tif (nParam1) {\n\t\tif ((GetGlobalNumber("001EBO_Movie_End") == 0)) {\n\t\t\tSetGlobalNumber("001EBO_Movie_End", 1);\n\t\t\tPlayMovie("permov02", 0);\n\t\t}\n\t}\n\tif ((!GetIsXBox())) {\n\t\tSetPlanetAvailable(11, 0);\n\t\tSetPlanetSelectable(11, 0);\n\t}\n\tint int5 = 88;\n\tif ((!GetIsXBox())) {\n\t\tint5 = 89;\n\t}\n\tif (((GetJournalEntry("tutorial_3CFD") > 0) && (GetJournalEntry("tutorial_3CFD") < 90))) {\n\t\tAurPostS...<truncated>
  tsl = True
  installation_path = CaseAwarePath("C:\"\"Program Files (x86)"\"Steam"\"steamapps"\"common"\"Knights of the Old Republic II")
  global_settings = <toolset.gui.widgets.settings.installations.GlobalSettings object at 0x00000186ED2F4820>
  extract_path = WindowsPath(C:\Users\JoeNotCharles\temp)
  returnValue = 65536

Function '_compile_windows' at C:\src\gamedev\kotor\PyKotor\Tools\HolocronToolset\src\toolset\utils\script.py:299:
  global_settings = <toolset.gui.widgets.settings.installations.GlobalSettings object at 0x00000186ED2F4820>
  extract_path = WindowsPath(C:\Users\JoeNotCharles\temp)
  source = 'void main() {\n\tint nParam1 = GetScriptParameter(1);\n    UndefinedFunction();\n\tif (nParam1) {\n\t\tif ((GetGlobalNumber("001EBO_Movie_End") == 0)) {\n\t\t\tSetGlobalNumber("001EBO_Movie_End", 1);\n\t\t\tPlayMovie("permov02", 0);\n\t\t}\n\t}\n\tif ((!GetIsXBox())) {\n\t\tSetPlanetAvailable(11, 0);\n\t\tSetPlanetSelectable(11, 0);\n\t}\n\tint int5 = 88;\n\tif ((!GetIsXBox())) {\n\t\tint5 = 89;\n\t}\n\tif (((GetJournalEntry("tutorial_3CFD") > 0) && (GetJournalEntry("tutorial_3CFD") < 90))) {\n\t\tAurPostS...<truncated>
  tsl = True
  installation_path = CaseAwarePath("C:\"\"Program Files (x86)"\"Steam"\"steamapps"\"common"\"Knights of the Old Republic II")
  nss_compiler_path = WindowsPath(C:\src\gamedev\kotor\Streamlined Peragus\tslpatchdata\nwnnsscomp.exe)
  rand_id = '089466'
  tempscript_filestem = 'tempscript_089466'
  tempSourcePath = WindowsPath(C:\Users\JoeNotCharles\temp\tempscript_089466.nss)
  tempCompiledPath = WindowsPath(C:\Users\JoeNotCharles\temp\tempscript_089466.ncs)
  gameEnum = <Game.K2: 2>
  extCompiler = <pykotor.resource.formats.ncs.compilers.ExternalNCSCompiler object at 0x00000186ED2F41E0>
  orig_regkey_path = None
  orig_regkey_value = None
  stdout = 'Compiling: C:\\Users\\JoeNotCharles\\temp\\tempscript_089466.nss\nCompilation aborted with errors\nTotal Execution time = 0 ms\n' 
  stderr = '\nC:\\Users\\JoeNotCharles\\temp\\tempscript_089466.nss(3): Error: Undeclared identifier "UndefinedFunction"'
  pattern = 'Unable to open the include file "([^"\\n]*)"'
----------------------
  1. BAD: Error dialog with title Failed to write to file and message ('error, 'argument out of range').

stdout says:

user chose Yes, compiling with builtin

errorlog.txt contains:

Assertion with Exception Trace: Exception 'argument out of range' of type '<class 'struct.error'>' occurred.
Formatted Traceback:
Traceback (most recent call last):
  File "C:\src\gamedev\kotor\PyKotor\Tools\HolocronToolset\src\toolset\gui\editor.py", line 247, in save
    data, data_ext = self.build()
  File "C:\src\gamedev\kotor\PyKotor\Tools\HolocronToolset\src\toolset\gui\editors\nss.py", line 186, in build
    compiled_bytes: bytes | None = compileScript(self.ui.codeEdit.toPlainText(), self._installation.tsl, self._installation.path())
  File "C:\src\gamedev\kotor\PyKotor\Tools\HolocronToolset\src\toolset\utils\script.py", line 178, in compileScript
    return bytes_ncs(compile_nss(source, Game.K2 if tsl else Game.K1, library_lookup=[CaseAwarePath(extract_path)]))
  File "C:\src\gamedev\kotor\PyKotor\Libraries\PyKotor\src\pykotor\resource\formats\ncs\ncs_auto.py", line 91, in bytes_ncs
    write_ncs(ncs, data, file_format)
  File "C:\src\gamedev\kotor\PyKotor\Libraries\PyKotor\src\pykotor\resource\formats\ncs\ncs_auto.py", line 63, in write_ncs
    NCSBinaryWriter(ncs, target).write()
  File "C:\src\gamedev\kotor\PyKotor\Libraries\PyKotor\src\pykotor\resource\type.py", line 392, in _autoclose
    resource: R = func(self, auto_close)
  File "C:\src\gamedev\kotor\PyKotor\Libraries\PyKotor\src\pykotor\resource\formats\ncs\io_ncs.py", line 358, in write
    self._write_instruction(instruction)
  File "C:\src\gamedev\kotor\PyKotor\Libraries\PyKotor\src\pykotor\resource\formats\ncs\io_ncs.py", line 463, in _write_instruction
    self._writer.write_int32(instruction.args[0], big=True)
  File "C:\src\gamedev\kotor\PyKotor\Libraries\PyKotor\src\pykotor\common\stream.py", line 1762, in write_int32
    self._ba[self._position : self._position + 4] = struct.pack(
struct.error: argument out of range

Stack Trace Variables:

Function 'save' at C:\src\gamedev\kotor\PyKotor\Tools\HolocronToolset\src\toolset\gui\editor.py:247:
  self = <toolset.gui.editors.nss.NSSEditor object at 0x00000186F02DFEB0>
  e = error('argument out of range')
  file = <_io.TextIOWrapper name='errorlog.txt' mode='a' encoding='utf-8'>

Function 'build' at C:\src\gamedev\kotor\PyKotor\Tools\HolocronToolset\src\toolset\gui\editors\nss.py:186:
  self = <toolset.gui.editors.nss.NSSEditor object at 0x00000186F02DFEB0>

Function 'compileScript' at C:\src\gamedev\kotor\PyKotor\Tools\HolocronToolset\src\toolset\utils\script.py:178:
  source = 'void main() {\n\tint nParam1 = GetScriptParameter(1);\n\tif (nParam1) {\n\t\tif ((GetGlobalNumber("001EBO_Movie_End") == 0)) {\n\t\t\tSetGlobalNumber("001EBO_Movie_End", 1);\n\t\t\tPlayMovie("permov02", 0);\n\t\t}\n\t}\n\tif ((!GetIsXBox())) {\n\t\tSetPlanetAvailable(11, 0);\n\t\tSetPlanetSelectable(11, 0);\n\t}\n\tint int5 = 88;\n\tif ((!GetIsXBox())) {\n\t\tint5 = 89;\n\t}\n\tif (((GetJournalEntry("tutorial_3CFD") > 0) && (GetJournalEntry("tutorial_3CFD") < 90))) {\n\t\tAurPostString("Completing tutorial...<truncated>
  tsl = True
  installation_path = CaseAwarePath("C:\"\"Program Files (x86)"\"Steam"\"steamapps"\"common"\"Knights of the Old Republic II")
  global_settings = <toolset.gui.widgets.settings.installations.GlobalSettings object at 0x00000186ED114F50>
  extract_path = WindowsPath(C:\Users\JoeNotCharles\temp)
  returnValue = 16384

Function 'bytes_ncs' at C:\src\gamedev\kotor\PyKotor\Libraries\PyKotor\src\pykotor\resource\formats\ncs\ncs_auto.py:91:
  ncs = <pykotor.resource.formats.ncs.ncs_data.NCS object at 0x00000186ED114AF0>
  file_format = ResourceType.NCS
  data = bytearray(b'NCS V1.0B\x00\x00\x04\xb8\x1e\x00\x00\x00\x00\x08 \x00\x04\x03\x00\x00\x00\x01\x05\x00\x03\x00\x01\x03\x01\xff\xff\xff\xfc\x00\x04\x1f\x00\x00\x00\x00\x93\x04\x05\x00\x10001EBO_Movie_End\x05\x00\x02D\x01\x04\x03\x00\x00\x00\x00\x0b \x1f\x00\x00\x00\x00Z\x04\x03\x00\x00\x00\x01\x04\x05\x00\x10001EBO_Movie_End\x05\x00\x02E\x02\x1b\x00\x00\x00\x00\x00\x04\x03\x00\x00\x00\x00\x04\x05\x00\x08permov02\x05\x00\x02\xdd\x02\x1b\x00\x00\x00\x00\x00\x1b\x00\x00\x00\x00\x00\x1b\x00\x00\x00\x00\x00\x1d\x00\x...<truncated>

Function 'write_ncs' at C:\src\gamedev\kotor\PyKotor\Libraries\PyKotor\src\pykotor\resource\formats\ncs\ncs_auto.py:63:
  ncs = <pykotor.resource.formats.ncs.ncs_data.NCS object at 0x00000186ED114AF0>
  target = bytearray(b'NCS V1.0B\x00\x00\x04\xb8\x1e\x00\x00\x00\x00\x08 \x00\x04\x03\x00\x00\x00\x01\x05\x00\x03\x00\x01\x03\x01\xff\xff\xff\xfc\x00\x04\x1f\x00\x00\x00\x00\x93\x04\x05\x00\x10001EBO_Movie_End\x05\x00\x02D\x01\x04\x03\x00\x00\x00\x00\x0b \x1f\x00\x00\x00\x00Z\x04\x03\x00\x00\x00\x01\x04\x05\x00\x10001EBO_Movie_End\x05\x00\x02E\x02\x1b\x00\x00\x00\x00\x00\x04\x03\x00\x00\x00\x00\x04\x05\x00\x08permov02\x05\x00\x02\xdd\x02\x1b\x00\x00\x00\x00\x00\x1b\x00\x00\x00\x00\x00\x1b\x00\x00\x00\x00\x00\x1d\x00\x...<truncated>
  file_format = ResourceType.NCS

Function '_autoclose' at C:\src\gamedev\kotor\PyKotor\Libraries\PyKotor\src\pykotor\resource\type.py:392:
  self = <pykotor.resource.formats.ncs.io_ncs.NCSBinaryWriter object at 0x00000186ED221050>
  auto_close = True
  func = <function NCSBinaryWriter.write at 0x00000186DDA3C5F0>

Function 'write' at C:\src\gamedev\kotor\PyKotor\Libraries\PyKotor\src\pykotor\resource\formats\ncs\io_ncs.py:358:
  self = <pykotor.resource.formats.ncs.io_ncs.NCSBinaryWriter object at 0x00000186ED221050>
  auto_close = True
  offset = 1208
  instruction = NCSInstruction(NCSInstructionType.CONSTI, None, [4294967295])

Function '_write_instruction' at C:\src\gamedev\kotor\PyKotor\Libraries\PyKotor\src\pykotor\resource\formats\ncs\io_ncs.py:463:
  self = <pykotor.resource.formats.ncs.io_ncs.NCSBinaryWriter object at 0x00000186ED221050>
  instruction = NCSInstruction(NCSInstructionType.CONSTI, None, [4294967295])
  to_signed_32bit = <function NCSBinaryWriter._write_instruction.<locals>.to_signed_32bit at 0x00000186F03CEEB0>
  to_signed_16bit = <function NCSBinaryWriter._write_instruction.<locals>.to_signed_16bit at 0x00000186F03CECD0>

Function 'write_int32' at C:\src\gamedev\kotor\PyKotor\Libraries\PyKotor\src\pykotor\common\stream.py:1762:
  self = <pykotor.common.stream.BinaryWriterBytearray object at 0x00000186ED196AA0>
  value = 4294967295
  big = True
----------------------
  1. OK: Error dialog with title Failed to write to file and message ('CompileError', "Function 'UndefinedFunction' has not been defined.").

stdout says:

user chose Yes, compiling with builtin

errorlog.txt contains:

Assertion with Exception Trace: Exception 'Function 'UndefinedFunction' has not been defined.' of type '<class 'pykotor.resource.formats.ncs.compiler.classes.CompileError'>' occurred.
Formatted Traceback:
Traceback (most recent call last):
  File "C:\src\gamedev\kotor\PyKotor\Tools\HolocronToolset\src\toolset\gui\editor.py", line 247, in save
    data, data_ext = self.build()
  File "C:\src\gamedev\kotor\PyKotor\Tools\HolocronToolset\src\toolset\gui\editors\nss.py", line 186, in build
    compiled_bytes: bytes | None = compileScript(self.ui.codeEdit.toPlainText(), self._installation.tsl, self._installation.path())
  File "C:\src\gamedev\kotor\PyKotor\Tools\HolocronToolset\src\toolset\utils\script.py", line 178, in compileScript
    return bytes_ncs(compile_nss(source, Game.K2 if tsl else Game.K1, library_lookup=[CaseAwarePath(extract_path)]))
  File "C:\src\gamedev\kotor\PyKotor\Libraries\PyKotor\src\pykotor\resource\formats\ncs\ncs_auto.py", line 125, in compile_nss
    block.compile(ncs)
  File "C:\src\gamedev\kotor\PyKotor\Libraries\PyKotor\src\pykotor\resource\formats\ncs\compiler\classes.py", line 309, in compile
    obj.compile(ncs, self)
  File "C:\src\gamedev\kotor\PyKotor\Libraries\PyKotor\src\pykotor\resource\formats\ncs\compiler\classes.py", line 591, in compile
    self.block.compile(ncs, root, None, retn, None, None)
  File "C:\src\gamedev\kotor\PyKotor\Libraries\PyKotor\src\pykotor\resource\formats\ncs\compiler\classes.py", line 435, in compile
    statement.compile(
  File "C:\src\gamedev\kotor\PyKotor\Libraries\PyKotor\src\pykotor\resource\formats\ncs\compiler\classes.py", line 1392, in compile
    expression_type = self.expression.compile(ncs, root, block)
  File "C:\src\gamedev\kotor\PyKotor\Libraries\PyKotor\src\pykotor\resource\formats\ncs\compiler\classes.py", line 1033, in compile
    raise CompileError(msg)
pykotor.resource.formats.ncs.compiler.classes.CompileError: Function 'UndefinedFunction' has not been defined.

Stack Trace Variables:

Function 'save' at C:\src\gamedev\kotor\PyKotor\Tools\HolocronToolset\src\toolset\gui\editor.py:247:
  self = <toolset.gui.editors.nss.NSSEditor object at 0x00000186F02DFEB0>
  e = CompileError("Function 'UndefinedFunction' has not been defined.")
  file = <_io.TextIOWrapper name='errorlog.txt' mode='a' encoding='utf-8'>

Function 'build' at C:\src\gamedev\kotor\PyKotor\Tools\HolocronToolset\src\toolset\gui\editors\nss.py:186:
  self = <toolset.gui.editors.nss.NSSEditor object at 0x00000186F02DFEB0>

Function 'compileScript' at C:\src\gamedev\kotor\PyKotor\Tools\HolocronToolset\src\toolset\utils\script.py:178:
  source = 'void main() {\n    UndefinedFunction();\n\tint nParam1 = GetScriptParameter(1);\n\tif (nParam1) {\n\t\tif ((GetGlobalNumber("001EBO_Movie_End") == 0)) {\n\t\t\tSetGlobalNumber("001EBO_Movie_End", 1);\n\t\t\tPlayMovie("permov02", 0);\n\t\t}\n\t}\n\tif ((!GetIsXBox())) {\n\t\tSetPlanetAvailable(11, 0);\n\t\tSetPlanetSelectable(11, 0);\n\t}\n\tint int5 = 88;\n\tif ((!GetIsXBox())) {\n\t\tint5 = 89;\n\t}\n\tif (((GetJournalEntry("tutorial_3CFD") > 0) && (GetJournalEntry("tutorial_3CFD") < 90))) {\n\t\tAurPostS...<truncated>
  tsl = True
  installation_path = CaseAwarePath("C:\"\"Program Files (x86)"\"Steam"\"steamapps"\"common"\"Knights of the Old Republic II")
  global_settings = <toolset.gui.widgets.settings.installations.GlobalSettings object at 0x00000186ED196140>
  extract_path = WindowsPath(C:\Users\JoeNotCharles\temp)
  returnValue = 16384

Function 'compile_nss' at C:\src\gamedev\kotor\PyKotor\Libraries\PyKotor\src\pykotor\resource\formats\ncs\ncs_auto.py:125:
  source = 'void main() {\n    UndefinedFunction();\n\tint nParam1 = GetScriptParameter(1);\n\tif (nParam1) {\n\t\tif ((GetGlobalNumber("001EBO_Movie_End") == 0)) {\n\t\t\tSetGlobalNumber("001EBO_Movie_End", 1);\n\t\t\tPlayMovie("permov02", 0);\n\t\t}\n\t}\n\tif ((!GetIsXBox())) {\n\t\tSetPlanetAvailable(11, 0);\n\t\tSetPlanetSelectable(11, 0);\n\t}\n\tint int5 = 88;\n\tif ((!GetIsXBox())) {\n\t\tint5 = 89;\n\t}\n\tif (((GetJournalEntry("tutorial_3CFD") > 0) && (GetJournalEntry("tutorial_3CFD") < 90))) {\n\t\tAurPostS...<truncated>
  game = <Game.K2: 2>
  optimizers = None
  library_lookup = [CaseAwarePath("C:\"\"Users"\"JoeNotCharles"\"temp")]
  errorlog = None
  debug = False
  nss_parser = <pykotor.resource.formats.ncs.compiler.parser.NssParser object at 0x00000186ED30C230>
  ncs = <pykotor.resource.formats.ncs.ncs_data.NCS object at 0x00000186ED3EBF50>
  block = <pykotor.resource.formats.ncs.compiler.classes.CodeRoot object at 0x00000186ED4039B0>

Function 'compile' at C:\src\gamedev\kotor\PyKotor\Libraries\PyKotor\src\pykotor\resource\formats\ncs\compiler\classes.py:309:
  self = <pykotor.resource.formats.ncs.compiler.classes.CodeRoot object at 0x00000186ED4039B0>
  ncs = <pykotor.resource.formats.ncs.ncs_data.NCS object at 0x00000186ED3EBF50>
  others = [<pykotor.resource.formats.ncs.compiler.classes.FunctionDefinition object at 0x00000186ED331A00>]
  entry_index = 0
  obj = <pykotor.resource.formats.ncs.compiler.classes.FunctionDefinition object at 0x00000186ED331A00>
  included = []
  script_globals = []

Function 'compile' at C:\src\gamedev\kotor\PyKotor\Libraries\PyKotor\src\pykotor\resource\formats\ncs\compiler\classes.py:591:
  self = <pykotor.resource.formats.ncs.compiler.classes.FunctionDefinition object at 0x00000186ED331A00>
  ncs = <pykotor.resource.formats.ncs.ncs_data.NCS object at 0x00000186ED3EBF50>
  root = <pykotor.resource.formats.ncs.compiler.classes.CodeRoot object at 0x00000186ED4039B0>
  name = 'main'
  previous_is_default = False
  retn = NCSInstruction(NCSInstructionType.RETN, None, [])
  function_start = NCSInstruction(NCSInstructionType.NOP, None, [])

Function 'compile' at C:\src\gamedev\kotor\PyKotor\Libraries\PyKotor\src\pykotor\resource\formats\ncs\compiler\classes.py:435:
  self = <pykotor.resource.formats.ncs.compiler.classes.CodeBlock object at 0x00000186ED31ACD0>
  ncs = <pykotor.resource.formats.ncs.ncs_data.NCS object at 0x00000186ED3EBF50>
  root = <pykotor.resource.formats.ncs.compiler.classes.CodeRoot object at 0x00000186ED4039B0>
  block = None
  return_instruction = NCSInstruction(NCSInstructionType.RETN, None, [])
  break_instruction = None
  continue_instruction = None
  statement = <pykotor.resource.formats.ncs.compiler.classes.ExpressionStatement object at 0x00000186ED407140>

Function 'compile' at C:\src\gamedev\kotor\PyKotor\Libraries\PyKotor\src\pykotor\resource\formats\ncs\compiler\classes.py:1392:
  self = <pykotor.resource.formats.ncs.compiler.classes.ExpressionStatement object at 0x00000186ED407140>
  ncs = <pykotor.resource.formats.ncs.ncs_data.NCS object at 0x00000186ED3EBF50>
  root = <pykotor.resource.formats.ncs.compiler.classes.CodeRoot object at 0x00000186ED4039B0>
  block = <pykotor.resource.formats.ncs.compiler.classes.CodeBlock object at 0x00000186ED31ACD0>
  return_instruction = NCSInstruction(NCSInstructionType.RETN, None, [])
  break_instruction = None
  continue_instruction = None

Function 'compile' at C:\src\gamedev\kotor\PyKotor\Libraries\PyKotor\src\pykotor\resource\formats\ncs\compiler\classes.py:1033:
  self = <pykotor.resource.formats.ncs.compiler.classes.FunctionCallExpression object at 0x00000186ED403A50>
  ncs = <pykotor.resource.formats.ncs.ncs_data.NCS object at 0x00000186ED3EBF50>
  root = <pykotor.resource.formats.ncs.compiler.classes.CodeRoot object at 0x00000186ED4039B0>
  block = <pykotor.resource.formats.ncs.compiler.classes.CodeBlock object at 0x00000186ED31ACD0>
  msg = "Function 'UndefinedFunction' has not been defined."
----------------------

AttributeError: 'NoneType' object has no attribute 'data'

[Note] Adding file plc_hotelscomp.utp in the manm26ae.MOD archive...
[Note] Patching m26ae.git in the modules\manm26ae.MOD folder.

[7/1/2023 10:03:34 PM] Traceback (most recent call last):
  File "pykotorcli.py", line 66, in <module>
  File "pykotor\tslpatcher\config.py", line 186, in install
AttributeError: 'NoneType' object has no attribute 'data'

config.ini:

; =====================================================[v1.0.5b1]====
; TSLPATCHER - GENERATED MODIFICATIONS FILE (1/27/2009)
; ===================================================================
; This file is automatically generated and as such has no formatting
; to speak of. You can insert blank lines between sections (but NOT
; between keys within a section!) and add comment lines starting
; with semicolon to make it more readable without breaking anything.
; -------------------------------------------------------------------

[Settings]
FileExists=1
WindowCaption=K1 Utility Armbands
ConfirmMessage=Did you read the ReadMe carefully?
LogLevel=3
InstallerMode=1
BackupFiles=1
PlaintextLog=0
LookupGameFolder=0
LookupGameNumber=1
SaveProcessedScripts=0


[TLKList]


[InstallList]
install_folder0=Modules
install_folder1=Modules\manm26ae.MOD


[2DAList]


[GFFList]
File0=m26ae.git


[CompileList]


[SSFList]


; ===================================================================

[gff_sa_ope_doo_PropertiesList_0_0]
FieldType=Struct
Path=PropertiesList
Label=
TypeId=0
AddField0=gff_sa_ope_doo_PropertyName_0
AddField1=gff_sa_ope_doo_Subtype_0
AddField2=gff_sa_ope_doo_CostTable_0
AddField3=gff_sa_ope_doo_CostValue_0
AddField4=gff_sa_ope_doo_Param1_0
AddField5=gff_sa_ope_doo_Param1Value_0
AddField6=gff_sa_ope_doo_ChanceAppear_0
[gff_sa_ope_doo_PropertyName_0]
FieldType=Word
Label=PropertyName
Value=10
[gff_sa_ope_doo_Subtype_0]
FieldType=Word
Label=Subtype
Value=2DAMEMORY1
[gff_sa_ope_doo_CostTable_0]
FieldType=Byte
Label=CostTable
Value=3
[gff_sa_ope_doo_CostValue_0]
FieldType=Word
Label=CostValue
Value=13
[gff_sa_ope_doo_Param1_0]
FieldType=Byte
Label=Param1
Value=255
[gff_sa_ope_doo_Param1Value_0]
FieldType=Byte
Label=Param1Value
Value=0
[gff_sa_ope_doo_ChanceAppear_0]
FieldType=Byte
Label=ChanceAppear
Value=100
[gff_sa_ori_arm_PropertiesList_0_0]
FieldType=Struct
Path=PropertiesList
Label=
TypeId=0
AddField0=gff_sa_ori_arm_PropertyName_0
AddField1=gff_sa_ori_arm_Subtype_0
AddField2=gff_sa_ori_arm_CostTable_0
AddField3=gff_sa_ori_arm_CostValue_0
AddField4=gff_sa_ori_arm_Param1_0
AddField5=gff_sa_ori_arm_Param1Value_0
AddField6=gff_sa_ori_arm_ChanceAppear_0
[gff_sa_ori_arm_PropertyName_0]
FieldType=Word
Label=PropertyName
Value=10
[gff_sa_ori_arm_Subtype_0]
FieldType=Word
Label=Subtype
Value=2DAMEMORY2
[gff_sa_ori_arm_CostTable_0]
FieldType=Byte
Label=CostTable
Value=3
[gff_sa_ori_arm_CostValue_0]
FieldType=Word
Label=CostValue
Value=13
[gff_sa_ori_arm_Param1_0]
FieldType=Byte
Label=Param1
Value=255
[gff_sa_ori_arm_Param1Value_0]
FieldType=Byte
Label=Param1Value
Value=0
[gff_sa_ori_arm_ChanceAppear_0]
FieldType=Byte
Label=ChanceAppear
Value=100
[gff_m01aa_Placeable List_60_0]
FieldType=Struct
Path=Placeable List
Label=
TypeId=9
AddField0=gff_m01aa_TemplateResRef_0
AddField1=gff_m01aa_X_0
AddField2=gff_m01aa_Y_0
AddField3=gff_m01aa_Z_0
AddField4=gff_m01aa_Bearing_0
[gff_m01aa_TemplateResRef_0]
FieldType=ResRef
Label=TemplateResRef
Value=sa_uti_arm
[gff_m01aa_X_0]
FieldType=Float
Label=X
Value=11.8500003814697
[gff_m01aa_Y_0]
FieldType=Float
Label=Y
Value=15.9099998474121
[gff_m01aa_Z_0]
FieldType=Float
Label=Z
Value=-1.26999998092651
[gff_m01aa_Bearing_0]
FieldType=Float
Label=Bearing
Value=0
[m26ae.git]
AddField0=gff_m26ae_Placeable List_43_0
AddField1=gff_m26ae_Placeable List_44_0
AddField2=gff_m26ae_Placeable List_45_0
AddField3=gff_m26ae_Placeable List_46_0
CameraList\16\Orientation=0.122737869620323|0|0|0.992439150810242
AddField4=gff_m26ae_CameraList_19_0
!Destination=modules\manm26ae.MOD
[gff_m26ae_Placeable List_43_0]
FieldType=Struct
Path=Placeable List
Label=
TypeId=9
AddField0=gff_m26ae_TemplateResRef_0
AddField1=gff_m26ae_X_0
AddField2=gff_m26ae_Y_0
AddField3=gff_m26ae_Z_0
AddField4=gff_m26ae_Bearing_0
[gff_m26ae_TemplateResRef_0]
FieldType=ResRef
Label=TemplateResRef
Value=plc_hotelscomp
[gff_m26ae_X_0]
FieldType=Float
Label=X
Value=21
[gff_m26ae_Y_0]
FieldType=Float
Label=Y
Value=37.422420501709
[gff_m26ae_Z_0]
FieldType=Float
Label=Z
Value=57.5034408569336
[gff_m26ae_Bearing_0]
FieldType=Float
Label=Bearing
Value=1.57878005504608
[gff_m26ae_Placeable List_44_0]
FieldType=Struct
Path=Placeable List
Label=
TypeId=9
AddField0=gff_m26ae_TemplateResRef_1
AddField1=gff_m26ae_X_1
AddField2=gff_m26ae_Y_1
AddField3=gff_m26ae_Z_1
AddField4=gff_m26ae_Bearing_1
[gff_m26ae_TemplateResRef_1]
FieldType=ResRef
Label=TemplateResRef
Value=plc_hotelcomp
[gff_m26ae_X_1]
FieldType=Float
Label=X
Value=5.44999980926514
[gff_m26ae_Y_1]
FieldType=Float
Label=Y
Value=36.8073883056641
[gff_m26ae_Z_1]
FieldType=Float
Label=Z
Value=57.5034408569336
[gff_m26ae_Bearing_1]
FieldType=Float
Label=Bearing
Value=4.69497013092041
[gff_m26ae_Placeable List_45_0]
FieldType=Struct
Path=Placeable List
Label=
TypeId=9
AddField0=gff_m26ae_TemplateResRef_2
AddField1=gff_m26ae_X_2
AddField2=gff_m26ae_Y_2
AddField3=gff_m26ae_Z_2
AddField4=gff_m26ae_Bearing_2
[gff_m26ae_TemplateResRef_2]
FieldType=ResRef
Label=TemplateResRef
Value=plc_hotelcomp
[gff_m26ae_X_2]
FieldType=Float
Label=X
Value=21
[gff_m26ae_Y_2]
FieldType=Float
Label=Y
Value=47.3524208068848
[gff_m26ae_Z_2]
FieldType=Float
Label=Z
Value=57.5034408569336
[gff_m26ae_Bearing_2]
FieldType=Float
Label=Bearing
Value=1.57878005504608
[gff_m26ae_Placeable List_46_0]
FieldType=Struct
Path=Placeable List
Label=
TypeId=9
AddField0=gff_m26ae_TemplateResRef_3
AddField1=gff_m26ae_X_3
AddField2=gff_m26ae_Y_3
AddField3=gff_m26ae_Z_3
AddField4=gff_m26ae_Bearing_3
[gff_m26ae_TemplateResRef_3]
FieldType=ResRef
Label=TemplateResRef
Value=plc_hotelcomp
[gff_m26ae_X_3]
FieldType=Float
Label=X
Value=19.5557098388672
[gff_m26ae_Y_3]
FieldType=Float
Label=Y
Value=65.870002746582
[gff_m26ae_Z_3]
FieldType=Float
Label=Z
Value=57.5034408569336
[gff_m26ae_Bearing_3]
FieldType=Float
Label=Bearing
Value=0.00441000005230308
[gff_m26ae_CameraList_19_0]
FieldType=Struct
Path=CameraList
Label=
TypeId=14
AddField0=gff_m26ae_CameraID_0
AddField1=gff_m26ae_Position_0
AddField2=gff_m26ae_Pitch_0
AddField3=gff_m26ae_MicRange_0
AddField4=gff_m26ae_Orientation_0
AddField5=gff_m26ae_Height_0
AddField6=gff_m26ae_FieldOfView_0
[gff_m26ae_CameraID_0]
FieldType=Int
Label=CameraID
Value=21
[gff_m26ae_Position_0]
FieldType=Position
Label=Position
Value=21.3220901489258|40.1263618469238|57.5034408569336
[gff_m26ae_Pitch_0]
FieldType=Float
Label=Pitch
Value=80
[gff_m26ae_MicRange_0]
FieldType=Float
Label=MicRange
Value=0
[gff_m26ae_Orientation_0]
FieldType=Orientation
Label=Orientation
Value=0.145659998059273|0|0|-0.989340007305145
[gff_m26ae_Height_0]
FieldType=Float
Label=Height
Value=1.5
[gff_m26ae_FieldOfView_0]
FieldType=Float
Label=FieldOfView
Value=55
[install_folder0]
File0=manm26ae.mod
[install_folder1]
Replace0=k_pman_planet43.ncs
Replace1=man26_bigcomp.dlg
Replace2=plc_hotelcomp.dlg
Replace3=smre_closesunrydor.ncs
Replace4=smre_elassa.utc
Replace5=smre_fadein.ncs
Replace6=smre_fadeout.ncs
Replace7=smre_kill.ncs
Replace8=smre_killelassa.ncs
Replace9=smre_opendoor.ncs
Replace10=smre_opendoor2.ncs
Replace11=smre_rec_c.ncs
Replace12=smre_spawn_elas.ncs
Replace13=smre_spawn_sunry.ncs
Replace14=smre_sun_fin.dlg
Replace15=smre_sun_rec.dlg
Replace16=smre_sun_rec.ncs
Replace17=smre_sunry.utc
Replace18=smre_term_conv.ncs
Replace19=smre_wait1.ncs
Replace20=plc_hotelcomp.utp
Replace21=plc_hotelscomp.utp
[gff_m26ae_Placeable List_43_1]
FieldType=Struct
Path=Placeable List
Label=
TypeId=9
AddField0=gff_m26ae_TemplateResRef_4
AddField1=gff_m26ae_X_4
AddField2=gff_m26ae_Y_4
AddField3=gff_m26ae_Z_4
AddField4=gff_m26ae_Bearing_4
[gff_m26ae_TemplateResRef_4]
FieldType=ResRef
Label=TemplateResRef
Value=plc_hotelscomp
[gff_m26ae_X_4]
FieldType=Float
Label=X
Value=21
[gff_m26ae_Y_4]
FieldType=Float
Label=Y
Value=37.422420501709
[gff_m26ae_Z_4]
FieldType=Float
Label=Z
Value=57.5034408569336
[gff_m26ae_Bearing_4]
FieldType=Float
Label=Bearing
Value=1.57878005504608
[gff_m26ae_Placeable List_44_1]
FieldType=Struct
Path=Placeable List
Label=
TypeId=9
AddField0=gff_m26ae_TemplateResRef_5
AddField1=gff_m26ae_X_5
AddField2=gff_m26ae_Y_5
AddField3=gff_m26ae_Z_5
AddField4=gff_m26ae_Bearing_5
[gff_m26ae_TemplateResRef_5]
FieldType=ResRef
Label=TemplateResRef
Value=plc_hotelcomp
[gff_m26ae_X_5]
FieldType=Float
Label=X
Value=5.44999980926514
[gff_m26ae_Y_5]
FieldType=Float
Label=Y
Value=36.8073883056641
[gff_m26ae_Z_5]
FieldType=Float
Label=Z
Value=57.5034408569336
[gff_m26ae_Bearing_5]
FieldType=Float
Label=Bearing
Value=4.69497013092041
[gff_m26ae_Placeable List_45_1]
FieldType=Struct
Path=Placeable List
Label=
TypeId=9
AddField0=gff_m26ae_TemplateResRef_6
AddField1=gff_m26ae_X_6
AddField2=gff_m26ae_Y_6
AddField3=gff_m26ae_Z_6
AddField4=gff_m26ae_Bearing_6
[gff_m26ae_TemplateResRef_6]
FieldType=ResRef
Label=TemplateResRef
Value=plc_hotelcomp
[gff_m26ae_X_6]
FieldType=Float
Label=X
Value=21
[gff_m26ae_Y_6]
FieldType=Float
Label=Y
Value=47.3524208068848
[gff_m26ae_Z_6]
FieldType=Float
Label=Z
Value=57.5034408569336
[gff_m26ae_Bearing_6]
FieldType=Float
Label=Bearing
Value=1.57878005504608
[gff_m26ae_Placeable List_46_1]
FieldType=Struct
Path=Placeable List
Label=
TypeId=9
AddField0=gff_m26ae_TemplateResRef_7
AddField1=gff_m26ae_X_7
AddField2=gff_m26ae_Y_7
AddField3=gff_m26ae_Z_7
AddField4=gff_m26ae_Bearing_7
[gff_m26ae_TemplateResRef_7]
FieldType=ResRef
Label=TemplateResRef
Value=plc_hotelcomp
[gff_m26ae_X_7]
FieldType=Float
Label=X
Value=19.5557098388672
[gff_m26ae_Y_7]
FieldType=Float
Label=Y
Value=65.870002746582
[gff_m26ae_Z_7]
FieldType=Float
Label=Z
Value=57.5034408569336
[gff_m26ae_Bearing_7]
FieldType=Float
Label=Bearing
Value=0.00441000005230308
[gff_m26ae_CameraList_19_1]
FieldType=Struct
Path=CameraList
Label=
TypeId=14
AddField0=gff_m26ae_CameraID_1
AddField1=gff_m26ae_Position_1
AddField2=gff_m26ae_Pitch_1
AddField3=gff_m26ae_MicRange_1
AddField4=gff_m26ae_Orientation_1
AddField5=gff_m26ae_Height_1
AddField6=gff_m26ae_FieldOfView_1
[gff_m26ae_CameraID_1]
FieldType=Int
Label=CameraID
Value=21
[gff_m26ae_Position_1]
FieldType=Position
Label=Position
Value=21.3220901489258|40.1263618469238|57.5034408569336
[gff_m26ae_Pitch_1]
FieldType=Float
Label=Pitch
Value=80
[gff_m26ae_MicRange_1]
FieldType=Float
Label=MicRange
Value=0
[gff_m26ae_Orientation_1]
FieldType=Orientation
Label=Orientation
Value=0.145659998059273|0|0|-0.989340007305145
[gff_m26ae_Height_1]
FieldType=Float
Label=Height
Value=1.5
[gff_m26ae_FieldOfView_1]
FieldType=Float
Label=FieldOfView
Value=55

v1.6.0 beta releases do not launch on Linux

When launching these HoloPatcher releases on Linux I get the following error:

[81862] Module object for struct is NULL!
Traceback (most recent call last):
  File "struct.py", line 13, in <module>
ModuleNotFoundError: No module named '_struct'

To my ignorant self it looks like there is a missing python module during your packaging process, but I am not all too familiar with the packaging process for python apps.

This is reproducable for all of the 1.6.0 beta releases of holopatcher so far.

Error with `Sunry Murder Recording Enhancement` mod

[8/4/2023 2:15:19 PM] [Error] Traceback (most recent call last):
[8/4/2023 2:15:19 PM] Warning: A file named manm26ae.mod already exists in the Modules folder. Skipping file...
[8/4/2023 2:15:19 PM] Note: Replacing file k_pman_planet43.ncs in the manm26ae.MOD archive...
[8/4/2023 2:15:19 PM] Note: Replacing file man26_bigcomp.dlg in the manm26ae.MOD archive...
[8/4/2023 2:15:19 PM] Note: Replacing file plc_hotelcomp.dlg in the manm26ae.MOD archive...
[8/4/2023 2:15:19 PM] Note: Adding file smre_closesunrydor.ncs in the manm26ae.MOD archive...
[8/4/2023 2:15:19 PM] Note: Replacing file smre_elassa.utc in the manm26ae.MOD archive...
[8/4/2023 2:15:19 PM] Note: Replacing file smre_fadein.ncs in the manm26ae.MOD archive...
[8/4/2023 2:15:19 PM] Note: Replacing file smre_fadeout.ncs in the manm26ae.MOD archive...
[8/4/2023 2:15:19 PM] Note: Replacing file smre_kill.ncs in the manm26ae.MOD archive...
[8/4/2023 2:15:19 PM] Note: Replacing file smre_killelassa.ncs in the manm26ae.MOD archive...
[8/4/2023 2:15:19 PM] Note: Replacing file smre_opendoor.ncs in the manm26ae.MOD archive...
[8/4/2023 2:15:19 PM] Note: Replacing file smre_opendoor2.ncs in the manm26ae.MOD archive...
[8/4/2023 2:15:19 PM] Note: Replacing file smre_rec_c.ncs in the manm26ae.MOD archive...
[8/4/2023 2:15:19 PM] Note: Replacing file smre_spawn_elas.ncs in the manm26ae.MOD archive...
[8/4/2023 2:15:19 PM] Note: Replacing file smre_spawn_sunry.ncs in the manm26ae.MOD archive...
[8/4/2023 2:15:19 PM] Note: Replacing file smre_sun_fin.dlg in the manm26ae.MOD archive...
[8/4/2023 2:15:19 PM] Note: Replacing file smre_sun_rec.dlg in the manm26ae.MOD archive...
[8/4/2023 2:15:19 PM] Note: Replacing file smre_sun_rec.ncs in the manm26ae.MOD archive...
[8/4/2023 2:15:19 PM] Note: Replacing file smre_sunry.utc in the manm26ae.MOD archive...
[8/4/2023 2:15:19 PM] Note: Replacing file smre_term_conv.ncs in the manm26ae.MOD archive...
[8/4/2023 2:15:19 PM] [Error]   File "pykotorcli.py", line 80, in <module>
[8/4/2023 2:15:19 PM] Note: Replacing file smre_wait1.ncs in the manm26ae.MOD archive...
[8/4/2023 2:15:19 PM] [Error]   File "pykotor\tslpatcher\config.py", line 187, in install
[8/4/2023 2:15:19 PM] Note: Replacing file plc_hotelcomp.utp in the manm26ae.MOD archive...
[8/4/2023 2:15:19 PM] Note: Replacing file plc_hotelscomp.utp in the manm26ae.MOD archive...
[8/4/2023 2:15:19 PM] Note: Patching m26ae.git in the modules\manm26ae.MOD folder.
[8/4/2023 2:15:19 PM] [Error] AttributeError: 'NoneType' object has no attribute 'data'
[8/4/2023 2:15:19 PM] [Error] [20572] Failed to execute script 'pykotorcli' due to unhandled exception!

Missing support `AddStructToGFFList` (see `test_gff_add_inside_list` in `test_reader.py`)

See the test test_gff_add_inside_list for why this is broken.
https://github.com/NickHugi/PyKotor/blob/master/tests/tslpatcher/test_reader.py

Logs:

Note: Patching drdparts.dlg in the \override\ folder.
Note: apply in ModifyFieldGFF: add value({1: '725'}, 2)
Note: apply in ModifyFieldGFF: add value({1: '725'}, 2)

relevant changes.ini snippet:

[GFFList]
File1=drdparts.dlg
[drdparts.dlg]
AddField0=gff_drdparts_EntryList_14_0
AddField1=gff_drdparts_EntriesList_2_0
AddField2=gff_drdparts_ReplyList_11_0
ReplyList\7\EntriesList\0\ParamStrA=HK_REPAIR_STATE
ReplyList\7\EntriesList\0\Index=2DAMEMORY2
...omitted for brevity...
ReplyList\7\EntriesList\1\ParamStrA=000_HK_Total_Parts
[gff_drdparts_EntryList_14_0]
FieldType=Struct
Path=EntryList
Label=
TypeId=14
AddField0=gff_drdparts_Speaker_0
...omitted for brevity...
AddField42=gff_drdparts_RecordNoVOOverri_0
2DAMEMORY2=ListIndex

The issue is due to a lack of support for the syntax 2DAMEMORY2=ListIndex and then referencing the value 2DAMEMORY2 later in the GFFList. The problem is caused by missing support for adding structs to GFF lists. The struct is never added, therefore it can never be found in patcher memory, thus the KeyError.

I've noticed the mod Visually Repair HK-47 is one of the only ones to have this error.

Error message:
image

Issue parsing floats in GFF fields

Using PyKotor to attempt to install the Juhani Romance Enhancement mod, I get this error:

[7/2/2023 6:59:31 PM] Using changes.ini path: C:\Users\*****\Documents\k1 mods\Juhani_Romance_Enhancement\tslpatchdata\changes.ini

[7/2/2023 6:59:32 PM] Traceback (most recent call last):
  File "pykotorcli.py", line 66, in <module>
  File "pykotor\tslpatcher\config.py", line 118, in install
  File "pykotor\tslpatcher\config.py", line 113, in config
  File "pykotor\tslpatcher\config.py", line 77, in load
  File "pykotor\tslpatcher\reader.py", line 56, in load
  File "pykotor\tslpatcher\reader.py", line 189, in load_gff
  File "pykotor\tslpatcher\reader.py", line 331, in add_field_gff
  File "pykotor\tslpatcher\reader.py", line 294, in add_field_gff
ValueError: could not convert string to float: '113,430000305176'

Relevant changes.ini snippet:

[m44ac.git]
AddField0=gff_m44ac_TriggerList_18_0
AddField1=gff_m44ac_CameraList_22_0
AddField2=gff_m44ac_CameraList_23_0
!Destination=Modules\unk_m44ac.mod
[gff_m44ac_TriggerList_18_0]
FieldType=Struct
Path=TriggerList
Label=
TypeId=1
AddField0=gff_m44ac_TemplateResRef_0
AddField1=gff_m44ac_XPosition_0
AddField2=gff_m44ac_YPosition_0
AddField3=gff_m44ac_ZPosition_0
AddField4=gff_m44ac_XOrientation_0
AddField5=gff_m44ac_YOrientation_0
AddField6=gff_m44ac_ZOrientation_0
AddField7=gff_m44ac_Geometry_0
[gff_m44ac_TemplateResRef_0]
FieldType=ResRef
Label=TemplateResRef
Value=jre_trigger
[gff_m44ac_XPosition_0]
FieldType=Float
Label=XPosition
Value=113,430000305176
[gff_m44ac_YPosition_0]
FieldType=Float
Label=YPosition
Value=69,6999969482422
[gff_m44ac_ZPosition_0]
FieldType=Float
Label=ZPosition
Value=13,7505826950073

Perhaps the comma simply needs to be treated as a dot and handle both cases?

Module conflict causing overwrite

I try to install your project using Pip. I found that your project will depend on the project "PyKotor==1.5.4". After installing the dependencies, some of the files in your project will overwrite some of the files in PyKotor. This includes mainly the modules in the "pykotor" root directory and the "venv" root directory.

Problems packing `.mod` files

Not sure what the cause is, but installing both the K1 and K2 modbuild with PyKotor results in all characters having no dialogue (you try to talk to them and they say nothing). Doesn't affect playable characters in the party.
Loading into a new zone (walking through a door into a new area) causes the game to return to the main menu with all buttons there disabled. This is an instantaneous action. Attempting to alt+f4 does not even bring up the confirmation 'are you sure' screen.

I'm attempting to compare the .mod files as I think it is a lip problem, I will post my results here.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.