PyLibMS 2.1.4__tar.gz → 3.0.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. {pylibms-2.1.4 → pylibms-3.0.2}/PKG-INFO +5 -7
  2. {pylibms-2.1.4 → pylibms-3.0.2}/PyLibMS.egg-info/PKG-INFO +5 -7
  3. pylibms-3.0.2/PyLibMS.egg-info/SOURCES.txt +58 -0
  4. pylibms-3.0.2/PyLibMS.egg-info/top_level.txt +1 -0
  5. {pylibms-2.1.4 → pylibms-3.0.2}/README.md +4 -6
  6. pylibms-2.1.4/LMS/Common/LMS_DataType.py → pylibms-3.0.2/lms/common/lms_datatype.py +7 -6
  7. pylibms-3.0.2/lms/common/lms_exceptions.py +7 -0
  8. pylibms-2.1.4/LMS/Common/LMS_FileInfo.py → pylibms-3.0.2/lms/common/lms_fileinfo.py +2 -2
  9. pylibms-2.1.4/LMS/Common/Stream/FileInfo.py → pylibms-3.0.2/lms/common/stream/fileinfo.py +11 -12
  10. pylibms-2.1.4/LMS/Common/Stream/Hashtable.py → pylibms-3.0.2/lms/common/stream/hashtable.py +5 -17
  11. pylibms-2.1.4/LMS/Common/Stream/Section.py → pylibms-3.0.2/lms/common/stream/section.py +4 -5
  12. pylibms-2.1.4/LMS/FileIO/Encoding.py → pylibms-3.0.2/lms/fileio/encoding.py +3 -6
  13. pylibms-2.1.4/LMS/FileIO/Stream.py → pylibms-3.0.2/lms/fileio/io.py +45 -43
  14. pylibms-2.1.4/LMS/Message/Definitions/Field/Stream.py → pylibms-3.0.2/lms/message/definitions/field/io.py +18 -15
  15. pylibms-2.1.4/LMS/Message/Definitions/Field/LMS_Field.py → pylibms-3.0.2/lms/message/definitions/field/lms_field.py +41 -23
  16. pylibms-3.0.2/lms/message/definitions/lms_messagetext.py +115 -0
  17. pylibms-2.1.4/LMS/Message/MSBT.py → pylibms-3.0.2/lms/message/msbt.py +18 -17
  18. pylibms-3.0.2/lms/message/msbtentry.py +86 -0
  19. pylibms-2.1.4/LMS/Message/MSBTStream.py → pylibms-3.0.2/lms/message/msbtio.py +60 -18
  20. pylibms-2.1.4/LMS/Message/Section/ATR1.py → pylibms-3.0.2/lms/message/section/atr1.py +18 -20
  21. pylibms-2.1.4/LMS/Message/Section/TSY1.py → pylibms-3.0.2/lms/message/section/tsy1.py +1 -1
  22. pylibms-3.0.2/lms/message/section/txt2.py +66 -0
  23. pylibms-3.0.2/lms/message/tag/io/param_io.py +90 -0
  24. pylibms-3.0.2/lms/message/tag/io/tag_io.py +107 -0
  25. pylibms-3.0.2/lms/message/tag/lms_tag.py +204 -0
  26. pylibms-2.1.4/LMS/Project/Definitions/Attribute.py → pylibms-3.0.2/lms/project/definitions/attribute.py +4 -7
  27. pylibms-3.0.2/lms/project/definitions/color.py +10 -0
  28. pylibms-3.0.2/lms/project/definitions/style.py +10 -0
  29. pylibms-3.0.2/lms/project/definitions/tag.py +95 -0
  30. pylibms-2.1.4/LMS/Project/MSBP.py → pylibms-3.0.2/lms/project/msbp.py +16 -16
  31. pylibms-3.0.2/lms/project/msbpread.py +109 -0
  32. pylibms-2.1.4/LMS/Project/Section/ALI2.py → pylibms-3.0.2/lms/project/section/ali2.py +1 -1
  33. pylibms-3.0.2/lms/project/section/ati2.py +18 -0
  34. pylibms-2.1.4/LMS/Project/Section/CLR1.py → pylibms-3.0.2/lms/project/section/clr1.py +2 -2
  35. pylibms-3.0.2/lms/project/section/string.py +17 -0
  36. pylibms-2.1.4/LMS/Project/Section/SYL3.py → pylibms-3.0.2/lms/project/section/syl3.py +2 -2
  37. pylibms-3.0.2/lms/project/section/tag2.py +18 -0
  38. pylibms-3.0.2/lms/project/section/tgg2.py +22 -0
  39. pylibms-3.0.2/lms/project/section/tgp2.py +28 -0
  40. pylibms-2.1.4/LMS/TitleConfig/Config.py → pylibms-3.0.2/lms/titleconfig/config.py +52 -38
  41. pylibms-2.1.4/LMS/TitleConfig/Definitions/Attributes.py → pylibms-3.0.2/lms/titleconfig/definitions/attribute.py +2 -2
  42. pylibms-2.1.4/LMS/TitleConfig/Definitions/Tags.py → pylibms-3.0.2/lms/titleconfig/definitions/tags.py +52 -42
  43. pylibms-2.1.4/LMS/TitleConfig/Definitions/Value.py → pylibms-3.0.2/lms/titleconfig/definitions/value.py +3 -3
  44. {pylibms-2.1.4 → pylibms-3.0.2}/pyproject.toml +1 -1
  45. pylibms-2.1.4/LMS/Common/LMS_Exceptions.py +0 -16
  46. pylibms-2.1.4/LMS/Message/Definitions/Field/LMS_FieldMap.py +0 -5
  47. pylibms-2.1.4/LMS/Message/Definitions/LMS_MessageText.py +0 -131
  48. pylibms-2.1.4/LMS/Message/MSBTEntry.py +0 -97
  49. pylibms-2.1.4/LMS/Message/Section/TXT2.py +0 -79
  50. pylibms-2.1.4/LMS/Message/Tag/LMS_Tag.py +0 -170
  51. pylibms-2.1.4/LMS/Message/Tag/Stream.py +0 -157
  52. pylibms-2.1.4/LMS/Message/Tag/System_Definitions.py +0 -96
  53. pylibms-2.1.4/LMS/Message/Tag/Tag_Formats.py +0 -10
  54. pylibms-2.1.4/LMS/Project/Definitions/Color.py +0 -10
  55. pylibms-2.1.4/LMS/Project/Definitions/Style.py +0 -14
  56. pylibms-2.1.4/LMS/Project/Definitions/Tag.py +0 -58
  57. pylibms-2.1.4/LMS/Project/MSBPRead.py +0 -102
  58. pylibms-2.1.4/LMS/Project/Section/ATI2.py +0 -17
  59. pylibms-2.1.4/LMS/Project/Section/String.py +0 -18
  60. pylibms-2.1.4/LMS/Project/Section/TAG2.py +0 -19
  61. pylibms-2.1.4/LMS/Project/Section/TGG2.py +0 -20
  62. pylibms-2.1.4/LMS/Project/Section/TGP2.py +0 -26
  63. pylibms-2.1.4/LMS/TitleConfig/Presets/Badge Arcade.yaml +0 -382
  64. pylibms-2.1.4/LMS/TitleConfig/Presets/Brain Age Concentration Training.yaml +0 -84
  65. pylibms-2.1.4/LMS/TitleConfig/Presets/Kirby Planet Robobot.yaml +0 -162
  66. pylibms-2.1.4/LMS/TitleConfig/Presets/Super Mario 3D Land.yaml +0 -320
  67. pylibms-2.1.4/LMS/TitleConfig/Presets/The Legend of Zelda a Link Between Worlds.yaml +0 -335
  68. pylibms-2.1.4/LMS/TitleConfig/Presets/Tomodachi Life.yaml +0 -7363
  69. pylibms-2.1.4/MANIFEST.in +0 -2
  70. pylibms-2.1.4/PyLibMS.egg-info/SOURCES.txt +0 -67
  71. pylibms-2.1.4/PyLibMS.egg-info/top_level.txt +0 -1
  72. {pylibms-2.1.4 → pylibms-3.0.2}/LICENSE +0 -0
  73. {pylibms-2.1.4 → pylibms-3.0.2}/PyLibMS.egg-info/dependency_links.txt +0 -0
  74. {pylibms-2.1.4 → pylibms-3.0.2}/PyLibMS.egg-info/requires.txt +0 -0
  75. {pylibms-2.1.4/LMS/Common → pylibms-3.0.2/lms}/__init__.py +0 -0
  76. {pylibms-2.1.4/LMS/Message/Definitions/Field → pylibms-3.0.2/lms/common}/__init__.py +0 -0
  77. {pylibms-2.1.4/LMS/Message/Definitions → pylibms-3.0.2/lms/message}/__init__.py +0 -0
  78. {pylibms-2.1.4/LMS/Message/Section → pylibms-3.0.2/lms/message/definitions}/__init__.py +0 -0
  79. {pylibms-2.1.4/LMS/Message/Tag → pylibms-3.0.2/lms/message/definitions/field}/__init__.py +0 -0
  80. {pylibms-2.1.4/LMS/Message → pylibms-3.0.2/lms/message/section}/__init__.py +0 -0
  81. {pylibms-2.1.4/LMS/Project/Definitions → pylibms-3.0.2/lms/message/tag}/__init__.py +0 -0
  82. /pylibms-2.1.4/LMS/Message/Tag/LMS_TagExceptions.py → /pylibms-3.0.2/lms/message/tag/lms_tagexceptions.py +0 -0
  83. {pylibms-2.1.4/LMS/Project → pylibms-3.0.2/lms/project}/__init__.py +0 -0
  84. {pylibms-2.1.4/LMS/TitleConfig/Definitions → pylibms-3.0.2/lms/project/definitions}/__init__.py +0 -0
  85. {pylibms-2.1.4/LMS/TitleConfig → pylibms-3.0.2/lms/titleconfig}/__init__.py +0 -0
  86. {pylibms-2.1.4/LMS → pylibms-3.0.2/lms/titleconfig/definitions}/__init__.py +0 -0
  87. {pylibms-2.1.4 → pylibms-3.0.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyLibMS
3
- Version: 2.1.4
3
+ Version: 3.0.2
4
4
  Summary: Python library built for the libMessageStudio (LMS) proprietary file formats from Nintendo. Supports MSBT, MSBP, and MSBF.
5
5
  Author: AbdyyEee
6
6
  License: Copyright 2025 AbdyyEee
@@ -39,17 +39,15 @@ Simple preview of the library is below. See [the wiki](https://github.com/AbdyyE
39
39
  ## Reading
40
40
  MSBT
41
41
  ```py
42
- from LMS.Message.MSBTStream import read_msbt
42
+ from lms.message.msbtio import read_msbt_path
43
43
 
44
- with open("Game.msbt", "rb+") as f:
45
- msbt = read_msbt(f)
44
+ msbt = read_msbt_path("Game.msbt")
46
45
  ```
47
46
  ## Writing
48
47
  ```py
49
- from LMS.Message.MSBTStream import write_msbt
48
+ from lms.message.msbtio import write_msbt_path
50
49
 
51
- with open("Out.msbt", "wb") as f:
52
- write_msbt(f, msbt)
50
+ write_msbt_path("Out_Game.msbt")
53
51
  ```
54
52
  # Adding/Editing Presets
55
53
  To add or edit Preset, you may create an issue with the relevant yaml file and the game it is for.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyLibMS
3
- Version: 2.1.4
3
+ Version: 3.0.2
4
4
  Summary: Python library built for the libMessageStudio (LMS) proprietary file formats from Nintendo. Supports MSBT, MSBP, and MSBF.
5
5
  Author: AbdyyEee
6
6
  License: Copyright 2025 AbdyyEee
@@ -39,17 +39,15 @@ Simple preview of the library is below. See [the wiki](https://github.com/AbdyyE
39
39
  ## Reading
40
40
  MSBT
41
41
  ```py
42
- from LMS.Message.MSBTStream import read_msbt
42
+ from lms.message.msbtio import read_msbt_path
43
43
 
44
- with open("Game.msbt", "rb+") as f:
45
- msbt = read_msbt(f)
44
+ msbt = read_msbt_path("Game.msbt")
46
45
  ```
47
46
  ## Writing
48
47
  ```py
49
- from LMS.Message.MSBTStream import write_msbt
48
+ from lms.message.msbtio import write_msbt_path
50
49
 
51
- with open("Out.msbt", "wb") as f:
52
- write_msbt(f, msbt)
50
+ write_msbt_path("Out_Game.msbt")
53
51
  ```
54
52
  # Adding/Editing Presets
55
53
  To add or edit Preset, you may create an issue with the relevant yaml file and the game it is for.
@@ -0,0 +1,58 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ PyLibMS.egg-info/PKG-INFO
5
+ PyLibMS.egg-info/SOURCES.txt
6
+ PyLibMS.egg-info/dependency_links.txt
7
+ PyLibMS.egg-info/requires.txt
8
+ PyLibMS.egg-info/top_level.txt
9
+ lms/__init__.py
10
+ lms/common/__init__.py
11
+ lms/common/lms_datatype.py
12
+ lms/common/lms_exceptions.py
13
+ lms/common/lms_fileinfo.py
14
+ lms/common/stream/fileinfo.py
15
+ lms/common/stream/hashtable.py
16
+ lms/common/stream/section.py
17
+ lms/fileio/encoding.py
18
+ lms/fileio/io.py
19
+ lms/message/__init__.py
20
+ lms/message/msbt.py
21
+ lms/message/msbtentry.py
22
+ lms/message/msbtio.py
23
+ lms/message/definitions/__init__.py
24
+ lms/message/definitions/lms_messagetext.py
25
+ lms/message/definitions/field/__init__.py
26
+ lms/message/definitions/field/io.py
27
+ lms/message/definitions/field/lms_field.py
28
+ lms/message/section/__init__.py
29
+ lms/message/section/atr1.py
30
+ lms/message/section/tsy1.py
31
+ lms/message/section/txt2.py
32
+ lms/message/tag/__init__.py
33
+ lms/message/tag/lms_tag.py
34
+ lms/message/tag/lms_tagexceptions.py
35
+ lms/message/tag/io/param_io.py
36
+ lms/message/tag/io/tag_io.py
37
+ lms/project/__init__.py
38
+ lms/project/msbp.py
39
+ lms/project/msbpread.py
40
+ lms/project/definitions/__init__.py
41
+ lms/project/definitions/attribute.py
42
+ lms/project/definitions/color.py
43
+ lms/project/definitions/style.py
44
+ lms/project/definitions/tag.py
45
+ lms/project/section/ali2.py
46
+ lms/project/section/ati2.py
47
+ lms/project/section/clr1.py
48
+ lms/project/section/string.py
49
+ lms/project/section/syl3.py
50
+ lms/project/section/tag2.py
51
+ lms/project/section/tgg2.py
52
+ lms/project/section/tgp2.py
53
+ lms/titleconfig/__init__.py
54
+ lms/titleconfig/config.py
55
+ lms/titleconfig/definitions/__init__.py
56
+ lms/titleconfig/definitions/attribute.py
57
+ lms/titleconfig/definitions/tags.py
58
+ lms/titleconfig/definitions/value.py
@@ -0,0 +1 @@
1
+ lms
@@ -18,17 +18,15 @@ Simple preview of the library is below. See [the wiki](https://github.com/AbdyyE
18
18
  ## Reading
19
19
  MSBT
20
20
  ```py
21
- from LMS.Message.MSBTStream import read_msbt
21
+ from lms.message.msbtio import read_msbt_path
22
22
 
23
- with open("Game.msbt", "rb+") as f:
24
- msbt = read_msbt(f)
23
+ msbt = read_msbt_path("Game.msbt")
25
24
  ```
26
25
  ## Writing
27
26
  ```py
28
- from LMS.Message.MSBTStream import write_msbt
27
+ from lms.message.msbtio import write_msbt_path
29
28
 
30
- with open("Out.msbt", "wb") as f:
31
- write_msbt(f, msbt)
29
+ write_msbt_path("Out_Game.msbt")
32
30
  ```
33
31
  # Adding/Editing Presets
34
32
  To add or edit Preset, you may create an issue with the relevant yaml file and the game it is for.
@@ -4,6 +4,7 @@ from typing import Literal, Type
4
4
 
5
5
  class LMS_DataType(Enum):
6
6
  """Enum that represents a datatype for a value entry in a MSBT/MSBP file."""
7
+
7
8
  UINT8 = 0
8
9
  UINT16 = 1
9
10
  UINT32 = 2
@@ -22,12 +23,12 @@ class LMS_DataType(Enum):
22
23
 
23
24
  STRING = 8
24
25
  LIST = 9
25
-
26
+
26
27
  # These types are not offical, but allow for abstraction from the value of the actual type
27
28
  # As an example, BOOL can be utilized for UInt8 values that act like a bool
28
29
  # Byte types can also be used for when the type/value is unknown or if there is extra data in the tag.
29
30
  BOOL = "bool"
30
- BYTE = "byte"
31
+ BYTES = "byte"
31
32
 
32
33
  def to_string(self) -> str:
33
34
  return self._name_.lower()
@@ -39,7 +40,7 @@ class LMS_DataType(Enum):
39
40
  LMS_DataType.STRING,
40
41
  LMS_DataType.LIST,
41
42
  LMS_DataType.BOOL,
42
- LMS_DataType.BYTE,
43
+ LMS_DataType.BYTES,
43
44
  ]:
44
45
  return self in [LMS_DataType.INT8, LMS_DataType.INT16, LMS_DataType.INT32]
45
46
 
@@ -59,11 +60,11 @@ class LMS_DataType(Enum):
59
60
  LMS_DataType.STRING: str,
60
61
  LMS_DataType.LIST: str,
61
62
  LMS_DataType.BOOL: bool,
62
- LMS_DataType.BYTE: bytes,
63
+ LMS_DataType.BYTES: bytes,
63
64
  }[self]
64
65
 
65
66
  @property
66
- def stream_size(self) -> Literal[1, 2, 4]:
67
+ def stream_size(self):
67
68
  """The size the datatype takes up in a stream."""
68
69
  return {
69
70
  LMS_DataType.UINT8: 1,
@@ -75,7 +76,7 @@ class LMS_DataType(Enum):
75
76
  LMS_DataType.FLOAT32: 4,
76
77
  LMS_DataType.LIST: 1,
77
78
  LMS_DataType.BOOL: 1,
78
- LMS_DataType.BYTE: 1,
79
+ LMS_DataType.BYTES: 1,
79
80
  }[self]
80
81
 
81
82
  @classmethod
@@ -0,0 +1,7 @@
1
+ class LMS_Error(Exception): ...
2
+
3
+
4
+ class LMS_UnexpectedMagicError(Exception): ...
5
+
6
+
7
+ class LMS_MisalignedSizeError(Exception): ...
@@ -1,11 +1,11 @@
1
1
  from dataclasses import dataclass
2
2
 
3
- from LMS.FileIO.Encoding import FileEncoding
3
+ from lms.fileio.encoding import FileEncoding
4
4
 
5
5
 
6
6
  @dataclass(frozen=True)
7
7
  class LMS_FileInfo:
8
- big_endian: bool
8
+ is_big_endian: bool
9
9
  encoding: FileEncoding
10
10
  version: int
11
11
  section_count: int
@@ -1,19 +1,19 @@
1
- from LMS.Common import LMS_Exceptions
2
- from LMS.Common.LMS_FileInfo import LMS_FileInfo
3
- from LMS.FileIO.Encoding import FileEncoding
4
- from LMS.FileIO.Stream import FileReader, FileWriter
1
+ from lms.common import lms_exceptions
2
+ from lms.common.lms_fileinfo import LMS_FileInfo
3
+ from lms.fileio.encoding import FileEncoding
4
+ from lms.fileio.io import FileReader, FileWriter
5
5
 
6
6
 
7
7
  def read_file_info(reader: FileReader, expected_magic: str) -> LMS_FileInfo:
8
8
  magic = reader.read_string_len(8)
9
9
 
10
10
  if magic != expected_magic:
11
- raise LMS_Exceptions.LMS_UnexpectedMagicError(
11
+ raise lms_exceptions.LMS_UnexpectedMagicError(
12
12
  f"Invalid magic!' Expected {expected_magic}', got '{magic}'."
13
13
  )
14
14
 
15
- big_endian = reader.read_bytes(2) == b"\xfe\xff"
16
- reader.big_endian = big_endian
15
+ is_big_endian = reader.read_bytes(2) == b"\xfe\xff"
16
+ reader.is_big_endian = is_big_endian
17
17
 
18
18
  reader.skip(2)
19
19
 
@@ -28,13 +28,13 @@ def read_file_info(reader: FileReader, expected_magic: str) -> LMS_FileInfo:
28
28
 
29
29
  reader.seek(0, 2)
30
30
  if file_size != reader.tell():
31
- raise LMS_Exceptions.LMS_MisalignedSizeError(f"Filesize is misaligned!")
31
+ raise lms_exceptions.LMS_MisalignedSizeError(f"Filesize is misaligned!")
32
32
 
33
33
  # Seek to the start of data
34
34
  reader.seek(0x20)
35
35
 
36
36
  return LMS_FileInfo(
37
- big_endian,
37
+ is_big_endian,
38
38
  encoding,
39
39
  version,
40
40
  section_count,
@@ -46,18 +46,17 @@ def write_file_info(writer: FileWriter, magic: str, file_info: LMS_FileInfo) ->
46
46
 
47
47
  :param writer: a Writer object.
48
48
  :param file_info: the file_info object."""
49
- writer.big_endian = file_info.big_endian
49
+ writer.is_big_endian = file_info.is_big_endian
50
50
  writer.encoding = file_info.encoding
51
51
 
52
52
  writer.write_string(magic)
53
- writer.write_bytes(b"\xff\xfe" if not file_info.big_endian else b"\xfe\xff")
53
+ writer.write_bytes(b"\xff\xfe" if not file_info.is_big_endian else b"\xfe\xff")
54
54
  writer.write_bytes(b"\x00\x00")
55
55
 
56
56
  writer.write_uint8(file_info.encoding.value)
57
57
  writer.write_uint8(file_info.version)
58
58
  writer.write_uint16(file_info.section_count)
59
59
 
60
- # Padding
61
60
  writer.write_bytes(b"\x00\x00")
62
61
  writer.write_bytes(b"\x00" * 4)
63
62
  writer.write_bytes(b"\x00" * 10)
@@ -1,4 +1,4 @@
1
- from LMS.FileIO.Stream import FileReader, FileWriter
1
+ from lms.fileio.io import FileReader, FileWriter
2
2
 
3
3
 
4
4
  def read_labels(reader: FileReader) -> tuple[dict[int, str], int]:
@@ -7,12 +7,10 @@ def read_labels(reader: FileReader) -> tuple[dict[int, str], int]:
7
7
  data_start = reader.tell()
8
8
  slot_count = reader.read_uint32()
9
9
  for _ in range(slot_count):
10
- # Read initial label data
11
10
  label_count = reader.read_uint32()
12
11
  offset = reader.read_uint32()
13
12
  next_offset = reader.tell()
14
13
 
15
- # Read the actual label data
16
14
  reader.seek(data_start + offset)
17
15
  for _ in range(label_count):
18
16
  length = reader.read_uint8()
@@ -26,16 +24,10 @@ def read_labels(reader: FileReader) -> tuple[dict[int, str], int]:
26
24
 
27
25
  # While the slot count is consistent for most files, some vary them.
28
26
  # Return the slot count to ensure that this change can be recorded.
29
- return (sorted_labels, slot_count)
27
+ return sorted_labels, slot_count
30
28
 
31
29
 
32
30
  def write_labels(writer: FileWriter, labels: list[str], slot_count: int) -> None:
33
- """Writes the hashtable block to a stream.
34
-
35
- :param writer: a Writer object.
36
- :param labels: the dictionary of labels.
37
- :param slot_count: the amount of hash slots.
38
- """
39
31
  writer.write_uint32(slot_count)
40
32
 
41
33
  hash_slots = {}
@@ -44,19 +36,18 @@ def write_labels(writer: FileWriter, labels: list[str], slot_count: int) -> None
44
36
  for i, label in enumerate(labels):
45
37
  hash = _calculate_hash(label, slot_count)
46
38
 
47
- # Add to the list if hash exists, and create a new list for each new hash
48
39
  if hash not in hash_slots:
49
40
  hash_slots[hash] = [label]
50
41
  else:
51
42
  hash_slots[hash].append(label)
52
43
 
53
44
  index_map[label] = i
45
+
54
46
  label_offsets = slot_count * 8 + 4
55
47
 
56
- # Sort by the hash slots
57
48
  hash_slots = dict(sorted(hash_slots.items(), key=lambda x: x[0]))
58
49
 
59
- # Write the slots
50
+
60
51
  for i in range(slot_count):
61
52
  if i in hash_slots:
62
53
  writer.write_uint32(len(hash_slots[i]))
@@ -76,11 +67,8 @@ def write_labels(writer: FileWriter, labels: list[str], slot_count: int) -> None
76
67
  writer.write_uint32(index_map[label])
77
68
 
78
69
 
70
+ # See https://nintendo-formats.com/libs/lms/overview.html#hash-tables
79
71
  def _calculate_hash(label: str, slot_count: int) -> int:
80
- """Calculates the hash of a label.
81
-
82
- See https://nintendo-formats.com/libs/lms/overview.html#hash-tables
83
- """
84
72
  hash = 0
85
73
  for character in label:
86
74
  hash = hash * 0x492 + ord(character)
@@ -1,6 +1,6 @@
1
1
  from typing import Any, Callable, Generator
2
2
 
3
- from LMS.FileIO.Stream import FileReader, FileWriter
3
+ from lms.fileio.io import FileReader, FileWriter
4
4
 
5
5
 
6
6
  def read_section_data(
@@ -11,7 +11,6 @@ def read_section_data(
11
11
  magic = reader.read_string_len(4)
12
12
  size = reader.read_uint32()
13
13
 
14
- # Skip to start of data
15
14
  reader.skip(8)
16
15
  end = reader.tell() + size
17
16
 
@@ -27,7 +26,7 @@ def write_section(
27
26
  magic: str,
28
27
  section_call: Callable,
29
28
  data: list[Any],
30
- *write_parameters,
29
+ *write_arguments,
31
30
  ) -> None:
32
31
  writer.write_string(magic)
33
32
  size_offset = writer.tell()
@@ -36,7 +35,7 @@ def write_section(
36
35
  writer.write_bytes(b"\x00" * 8)
37
36
  data_start = writer.tell()
38
37
 
39
- section_call(writer, data, *write_parameters)
38
+ section_call(writer, data, *write_arguments)
40
39
 
41
40
  _write_end_data(writer, data_start, size_offset)
42
41
 
@@ -57,4 +56,4 @@ def _write_end_data(writer: FileWriter, data_start: int, size_offset: int) -> No
57
56
  writer.seek(size_offset)
58
57
  writer.write_uint32(size)
59
58
  writer.seek(end)
60
- writer.write_alignment(b"\xAB", 16)
59
+ writer.write_alignment(b"\xab", 16)
@@ -11,8 +11,7 @@ class FileEncoding(Enum):
11
11
 
12
12
  def to_string_format(
13
13
  self, big_endian: bool = False
14
- ) -> Literal["UTF-8", "UTF-16-LE", "UTF-16-BE", "UTF-32-LE", "UTF-32-BE"]:
15
- """Returns the encoding in the string format"""
14
+ ):
16
15
  match self:
17
16
  case FileEncoding.UTF8:
18
17
  return "UTF-8"
@@ -26,8 +25,7 @@ class FileEncoding(Enum):
26
25
  return "UTF-32-LE"
27
26
 
28
27
  @property
29
- def width(self) -> Literal[1, 2, 4]:
30
- """The width of a character in a stream."""
28
+ def width(self):
31
29
  match self:
32
30
  case FileEncoding.UTF8:
33
31
  return 1
@@ -37,6 +35,5 @@ class FileEncoding(Enum):
37
35
  return 4
38
36
 
39
37
  @property
40
- def terminator(self) -> Literal[b"\x00", b"\x00\x00", b"\x00\x00\x00\x00"]:
41
- """The terminator of the string in a stream."""
38
+ def terminator(self):
42
39
  return b"\x00" * self.width
@@ -4,7 +4,7 @@ import struct
4
4
  from io import BytesIO
5
5
  from typing import BinaryIO, Generator
6
6
 
7
- from LMS.FileIO.Encoding import FileEncoding
7
+ from lms.fileio.encoding import FileEncoding
8
8
 
9
9
  STRUCT_TYPES = {
10
10
  "little": {
@@ -28,34 +28,34 @@ STRUCT_TYPES = {
28
28
  }
29
29
 
30
30
 
31
- class FileReader:
32
- def __init__(self, stream: BinaryIO | bytes, big_endian: bool = False):
33
- if isinstance(stream, bytes):
34
- self._data = BytesIO(stream)
31
+ class FileReader:
32
+ def __init__(self, data: BinaryIO | bytes, big_endian: bool = False):
33
+ if isinstance(data, BinaryIO):
34
+ self._stream = BytesIO(data.read())
35
35
  else:
36
- self._data = BytesIO(stream.read())
36
+ self._stream = BytesIO(data)
37
37
 
38
- self.encoding: FileEncoding = None
39
- self.big_endian = big_endian
38
+ self.encoding = FileEncoding.UTF8
39
+ self.is_big_endian = big_endian
40
40
 
41
41
  def tell(self) -> int:
42
- return self._data.tell()
42
+ return self._stream.tell()
43
43
 
44
44
  def skip(self, length: int) -> None:
45
- self._data.read(length)
45
+ self._stream.read(length)
46
46
 
47
- def align(self, alignment: int) -> int:
47
+ def align(self, alignment: int) -> None:
48
48
  alignment = (-self.tell() % alignment + alignment) % alignment
49
49
  self.skip(alignment)
50
50
 
51
51
  def read_bytes(self, length: int) -> bytes:
52
- return self._data.read(length)
52
+ return self._stream.read(length)
53
53
 
54
54
  def seek(self, offset: int, whence: int = 0) -> None:
55
55
  if offset < 0:
56
- self._data.seek(self.tell() + offset)
56
+ self._stream.seek(self.tell() + offset)
57
57
  else:
58
- self._data.seek(offset, whence)
58
+ self._stream.seek(offset, whence)
59
59
 
60
60
  def read_offset_array(self, count: int) -> Generator[int, None, None]:
61
61
  start = self.tell() - 4
@@ -68,28 +68,28 @@ class FileReader:
68
68
  return [self.read_uint16() for _ in range(count)]
69
69
 
70
70
  def read_int8(self) -> int:
71
- return struct.unpack(self._get_datatype("int8"), self._data.read(1))[0]
71
+ return struct.unpack(self._get_datatype("int8"), self._stream.read(1))[0]
72
72
 
73
73
  def read_int16(self) -> int:
74
- return struct.unpack(self._get_datatype("int16"), self._data.read(2))[0]
74
+ return struct.unpack(self._get_datatype("int16"), self._stream.read(2))[0]
75
75
 
76
76
  def read_int32(self) -> int:
77
- return struct.unpack(self._get_datatype("int32"), self._data.read(4))[0]
77
+ return struct.unpack(self._get_datatype("int32"), self._stream.read(4))[0]
78
78
 
79
79
  def read_uint8(self) -> int:
80
- return struct.unpack(self._get_datatype("uint8"), self._data.read(1))[0]
80
+ return struct.unpack(self._get_datatype("uint8"), self._stream.read(1))[0]
81
81
 
82
82
  def read_uint16(self) -> int:
83
- return struct.unpack(self._get_datatype("uint16"), self._data.read(2))[0]
83
+ return struct.unpack(self._get_datatype("uint16"), self._stream.read(2))[0]
84
84
 
85
85
  def read_uint32(self) -> int:
86
- return struct.unpack(self._get_datatype("uint32"), self._data.read(4))[0]
86
+ return struct.unpack(self._get_datatype("uint32"), self._stream.read(4))[0]
87
87
 
88
88
  def read_float32(self) -> float:
89
- return struct.unpack(self._get_datatype("float"), self._data.read(4))[0]
89
+ return struct.unpack(self._get_datatype("float"), self._stream.read(4))[0]
90
90
 
91
91
  def read_string_len(self, length: int) -> str:
92
- return self._data.read(length).decode("UTF-8")
92
+ return self._stream.read(length).decode("UTF-8")
93
93
 
94
94
  def read_str_variable_encoding(self):
95
95
  message = b""
@@ -97,24 +97,26 @@ class FileReader:
97
97
  raw_char := self.read_bytes(self.encoding.width)
98
98
  ) != self.encoding.terminator:
99
99
  message += raw_char
100
- return message.decode(self.encoding.to_string_format(self.big_endian))
100
+ return message.decode(self.encoding.to_string_format(self.is_big_endian))
101
101
 
102
102
  def read_len_string_variable_encoding(self):
103
- self.align(len("\x00".encode(self.encoding.to_string_format(self.big_endian))))
103
+ self.align(
104
+ len("\x00".encode(self.encoding.to_string_format(self.is_big_endian)))
105
+ )
104
106
  length = self.read_uint16()
105
107
  return self.read_bytes(length).decode(
106
- self.encoding.to_string_format(self.big_endian)
108
+ self.encoding.to_string_format(self.is_big_endian)
107
109
  )
108
110
 
109
111
  def _get_datatype(self, name: str) -> str:
110
- return STRUCT_TYPES["little" if not self.big_endian else "big"][name]
112
+ return STRUCT_TYPES["little" if not self.is_big_endian else "big"][name]
111
113
 
112
114
 
113
115
  class FileWriter:
114
116
  def __init__(self, encoding: FileEncoding):
115
117
  self.data = BytesIO(b"")
116
118
  self.encoding = encoding
117
- self.big_endian = False
119
+ self.is_big_endian = False
118
120
 
119
121
  def skip(self, length: int) -> None:
120
122
  self.data.seek(length, 1)
@@ -126,8 +128,8 @@ class FileWriter:
126
128
  self.seek(last_position)
127
129
  return size
128
130
 
129
- def write_bytes(self, data: bytes) -> bytes:
130
- return self.data.write(data)
131
+ def write_bytes(self, data: bytes) -> None:
132
+ self.data.write(data)
131
133
 
132
134
  def seek(self, offset: int, whence: int = 0) -> None:
133
135
  self.data.seek(offset, whence)
@@ -142,38 +144,38 @@ class FileWriter:
142
144
  for number in array:
143
145
  self.write_uint16(number)
144
146
 
145
- def write_int8(self, value: int) -> int:
146
- return self.data.write(struct.pack(self._get_datatype("int8"), value))
147
+ def write_int8(self, value: int) -> None:
148
+ self.data.write(struct.pack(self._get_datatype("int8"), value))
147
149
 
148
- def write_int16(self, value: int) -> int:
149
- return self.data.write(struct.pack(self._get_datatype("int16"), value))
150
+ def write_int16(self, value: int) -> None:
151
+ self.data.write(struct.pack(self._get_datatype("int16"), value))
150
152
 
151
153
  def write_int32(self, value: int) -> None:
152
- return self.data.write(struct.pack(self._get_datatype("int32"), value))
154
+ self.data.write(struct.pack(self._get_datatype("int32"), value))
153
155
 
154
156
  def write_uint8(self, value: int) -> None:
155
- return self.data.write(struct.pack(self._get_datatype("uint8"), value))
157
+ self.data.write(struct.pack(self._get_datatype("uint8"), value))
156
158
 
157
159
  def write_uint16(self, value: int) -> None:
158
- return self.data.write(struct.pack(self._get_datatype("uint16"), value))
160
+ self.data.write(struct.pack(self._get_datatype("uint16"), value))
159
161
 
160
162
  def write_uint32(self, value: int) -> None:
161
- return self.data.write(struct.pack(self._get_datatype("uint32"), value))
163
+ self.data.write(struct.pack(self._get_datatype("uint32"), value))
162
164
 
163
165
  def write_float32(self, value: float) -> None:
164
- return self.data.write(struct.pack(self._get_datatype("float"), value))
166
+ self.data.write(struct.pack(self._get_datatype("float"), value))
165
167
 
166
168
  def write_string(self, string: str):
167
169
  self.write_bytes(string.encode("UTF-8"))
168
170
 
169
171
  def write_len_variable_encoding_string(self, string: str) -> None:
170
172
  self.write_uint16(len(string) * self.encoding.width)
171
- self.write_variable_encoding_string.write_variable_encoding_string(
172
- string, False
173
- )
173
+ self.write_variable_encoding_string(string, False)
174
174
 
175
175
  def write_variable_encoding_string(self, string: str, terminate: bool = True):
176
- self.write_bytes(string.encode(self.encoding.to_string_format(self.big_endian)))
176
+ self.write_bytes(
177
+ string.encode(self.encoding.to_string_format(self.is_big_endian))
178
+ )
177
179
  if terminate:
178
180
  self.write_bytes(b"\x00" * self.encoding.width)
179
181
 
@@ -181,7 +183,7 @@ class FileWriter:
181
183
  return (-number % alignment + alignment) % alignment
182
184
 
183
185
  def _get_datatype(self, name: str) -> str:
184
- return STRUCT_TYPES["little" if not self.big_endian else "big"][name]
186
+ return STRUCT_TYPES["little" if not self.is_big_endian else "big"][name]
185
187
 
186
188
  def get_data(self) -> bytes:
187
189
  return self.data.getvalue()