diff --git a/src/ipl3checksum/__init__.py b/src/ipl3checksum/__init__.py index 9ce3d87..b4597ad 100644 --- a/src/ipl3checksum/__init__.py +++ b/src/ipl3checksum/__init__.py @@ -10,14 +10,4 @@ __version__ = ".".join(map(str, __version_info__)) + ".dev0" __author__ = "Decompollaborate" -from . import utils as utils - -from .cickinds import CICKind as CICKind - -from .checksum import calculateChecksum as calculateChecksum -from .checksum import calculateChecksumAutodetect as calculateChecksumAutodetect - -from .detect import detectCIC as detectCIC -from .detect import detectCICRaw as detectCICRaw - from .ipl3checksum import * diff --git a/src/ipl3checksum/checksum.py b/src/ipl3checksum/checksum.py deleted file mode 100644 index f12d996..0000000 --- a/src/ipl3checksum/checksum.py +++ /dev/null @@ -1,183 +0,0 @@ -#!/usr/bin/env python3 - -# SPDX-FileCopyrightText: © 2023 Decompollaborate -# SPDX-License-Identifier: MIT - -from __future__ import annotations - -import struct - -from . import utils -from .cickinds import CICKind -from .detect import detectCIC - - -def readWordFromRam(romWords: list[int], entrypointRam: int, ramAddr: int) -> int: - return romWords[utils.u32(ramAddr - entrypointRam + 0x1000) // 4] - - -def calculateChecksum(romBytes: bytes, kind: CICKind) -> tuple[int, int]|None: - """Calculates the checksum required by an official CIC of a N64 ROM. - - Args: - romBytes (bytes): The bytes of the N64 ROM in big endian format. It must have a minimum size of 0x101000 bytes. - kind (CICKind): The CIC kind variation used to calculate the checksum. - - Returns: - tuple[int, int]|None: If no error happens then the calculated checksum is returned, stored as a tuple - containing two 32-bits words. Otherwise, `None` is returned. Possible errors: - - `romBytes` not being big enough - """ - - if len(romBytes) < 0x101000: - return None - - romWords = list(struct.unpack_from(f">{0x101000//4}I", romBytes)) - - seed = kind.getSeed() - magic = kind.getMagic() - - s6 = seed - - a0 = romWords[8//4] - if kind == CICKind.CIC_X103: - a0 -= 0x100000 - if kind == CICKind.CIC_X106: - a0 -= 0x200000 - entrypointRam = a0 - - at = magic - lo = s6 * at - - if kind == CICKind.CIC_X105: - s6 = 0xA0000200 - - ra = 0x100000 - - v1 = 0 - t0 = 0 - - t1 = a0 - - t5 = 0x20 - - v0 = utils.u32(lo) - v0 += 1 - - a3 = v0 - t2 = v0 - t3 = v0 - s0 = v0 - a2 = v0 - t4 = v0 - - # poor man's do while - LA40005F0_loop = True - while LA40005F0_loop: - # v0 = *t1 - v0 = readWordFromRam(romWords, entrypointRam, t1) - - v1 = utils.u32(a3 + v0) - - at = utils.u32(v1) < utils.u32(a3) - - a1 = v1 - # if (at == 0) goto LA4000608; - - if at != 0: - t2 = utils.u32(t2 + 0x1) - - # LA4000608 - v1 = v0 & 0x1F - t7 = utils.u32(t5 - v1) - - - t8 = utils.u32(v0 >> t7) - t6 = utils.u32(v0 << v1) - - a0 = t6 | t8 - at = utils.u32(a2) < utils.u32(v0) - a3 = a1 - - t3 = t3 ^ v0 - - s0 = utils.u32(s0 + a0) - # if (at == 0) goto LA400063C; - if (at != 0): - t9 = a3 ^ v0 - - a2 = t9 ^ a2 - # goto LA4000640; - - # LA400063C: - else: - a2 = a2 ^ a0 - - - # LA4000640: - if kind == CICKind.CIC_X105: - # ipl3 6105 copies 0x330 bytes from the ROM's offset 0x000554 (or offset 0x000514 into IPL3) to vram 0xA0000004 - t7 = romWords[(s6 - 0xA0000004 + 0x000554) // 4] - - t0 = utils.u32(t0 + 0x4) - s6 = utils.u32(s6 + 0x4) - t7 = v0 ^ t7 - - t4 = utils.u32(t7 + t4) - - t7 = 0xA00002FF - - t1 = utils.u32(t1 + 0x4) - - s6 = utils.u32(s6 & t7) - else: - t0 = utils.u32(t0 + 0x4) - t7 = v0 ^ s0 - t1 = utils.u32(t1 + 0x4) - - t4 = utils.u32(t7 + t4) - - # if (t0 != ra) goto LA40005F0; - if t0 == ra: - LA40005F0_loop = False - - - if kind == CICKind.CIC_X103: - t6 = a3 ^ t2 - a3 = utils.u32(t6 + t3) - t8 = s0 ^ a2 - s0 = utils.u32(t8 + t4) - elif kind == CICKind.CIC_X106: - t6 = utils.u32(a3 * t2) - a3 = utils.u32(t6 + t3) - t8 = utils.u32(s0 * a2) - s0 = utils.u32(t8 + t4) - else: - t6 = a3 ^ t2 - a3 = t6 ^ t3 - t8 = s0 ^ a2 - s0 = t8 ^ t4 - - return (a3, s0) - -def calculateChecksumAutodetect(romBytes: bytes) -> tuple[int, int]|None: - """Calculates the checksum required by an official CIC of a N64 ROM. - - This function will try to autodetect the CIC kind automatically. If it fails to detect it then it will return `None`. - - Args: - romBytes (bytes): The bytes of the N64 ROM in big endian format. It must have a minimum size of 0x101000 bytes. - - Returns: - tuple[int, int]|None: If no error happens then the calculated checksum is returned, stored as a tuple - containing two 32-bits words. Otherwise, `None` is returned. Possible errors: - - `romBytes` not being big enough - - Not able to detect the CIC kind - """ - - kind = detectCIC(romBytes) - - if kind is None: - return None - - return calculateChecksum(romBytes, kind) diff --git a/src/ipl3checksum/checksum.pyi b/src/ipl3checksum/checksum.pyi new file mode 100644 index 0000000..c709541 --- /dev/null +++ b/src/ipl3checksum/checksum.pyi @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 + +# SPDX-FileCopyrightText: © 2023 Decompollaborate +# SPDX-License-Identifier: MIT + +from __future__ import annotations + +from .cickinds import CICKind + +def calculateChecksum(romBytes: bytes, kind: CICKind) -> tuple[int, int]|None: + """Calculates the checksum required by an official CIC of a N64 ROM. + + Args: + romBytes (bytes): The bytes of the N64 ROM in big endian format. It must have a minimum size of 0x101000 bytes. + kind (CICKind): The CIC kind variation used to calculate the checksum. + + Returns: + tuple[int, int]|None: If no error happens then the calculated checksum is returned, stored as a tuple + containing two 32-bits words. Otherwise, `None` is returned. Possible errors: + - `romBytes` not being big enough + """ + +def calculateChecksumAutodetect(romBytes: bytes) -> tuple[int, int]|None: + """Calculates the checksum required by an official CIC of a N64 ROM. + + This function will try to autodetect the CIC kind automatically. If it fails to detect it then it will return `None`. + + Args: + romBytes (bytes): The bytes of the N64 ROM in big endian format. It must have a minimum size of 0x101000 bytes. + + Returns: + tuple[int, int]|None: If no error happens then the calculated checksum is returned, stored as a tuple + containing two 32-bits words. Otherwise, `None` is returned. Possible errors: + - `romBytes` not being big enough + - Not able to detect the CIC kind + """ diff --git a/src/ipl3checksum/cickinds.py b/src/ipl3checksum/cickinds.py deleted file mode 100644 index 3beb0f3..0000000 --- a/src/ipl3checksum/cickinds.py +++ /dev/null @@ -1,82 +0,0 @@ -#!/usr/bin/env python3 - -# SPDX-FileCopyrightText: © 2023 Decompollaborate -# SPDX-License-Identifier: MIT - -from __future__ import annotations - -import enum - -class CICKind(enum.Enum): - CIC_6101 = enum.auto() - CIC_6102_7101 = enum.auto() - CIC_7102 = enum.auto() - CIC_X103 = enum.auto() # Both 6103 and 7103 - # 6104/7104 does not exist - CIC_X105 = enum.auto() # Both 6105 and 7105 - CIC_X106 = enum.auto() # Both 6106 and 7106 - - - def getSeed(self) -> int: - """ - Seed value set by the PIF ROM before the CPU (and the IPL3) is executed. - https://n64brew.dev/wiki/PIF-NUS#IPL3_checksum_algorithm - """ - return CICSeeds[self] - - def getMagic(self) -> int: - """ - Magic value hardcoded inside the IPL3 itself - """ - return CICMagics[self] - - def getHashMd5(self) -> str: - """ - Expected md5 hash of the IPL3 blob - """ - return CICHashMd5[self] - - @staticmethod - def fromValue(value: int) -> CICKind|None: - if value == 6102 or value == 7101: - return CICKind.CIC_6102_7101 - if value == 6101: - return CICKind.CIC_6101 - if value == 7102: - return CICKind.CIC_7102 - if value == 6103 or value == 7103: - return CICKind.CIC_X103 - if value == 6105 or value == 7105: - return CICKind.CIC_X105 - if value == 6106 or value == 7106: - return CICKind.CIC_X106 - - return None - - -CICSeeds: dict[CICKind, int] = { - CICKind.CIC_6101: 0x3F, - CICKind.CIC_6102_7101: 0x3F, - CICKind.CIC_7102: 0x3F, - CICKind.CIC_X103: 0x78, - CICKind.CIC_X105: 0x91, - CICKind.CIC_X106: 0x85, -} - -CICMagics: dict[CICKind, int] = { - CICKind.CIC_6101: 0x5D588B65, - CICKind.CIC_6102_7101: 0x5D588B65, - CICKind.CIC_7102: 0x5D588B65, - CICKind.CIC_X103: 0x6C078965, - CICKind.CIC_X105: 0x5D588B65, - CICKind.CIC_X106: 0x6C078965, -} - -CICHashMd5: dict[CICKind, str] = { - CICKind.CIC_6101: "900b4a5b68edb71f4c7ed52acd814fc5", - CICKind.CIC_6102_7101: "e24dd796b2fa16511521139d28c8356b", - CICKind.CIC_7102: "955894c2e40a698bf98a67b78a4e28fa", - CICKind.CIC_X103: "319038097346e12c26c3c21b56f86f23", - CICKind.CIC_X105: "ff22a296e55d34ab0a077dc2ba5f5796", - CICKind.CIC_X106: "6460387749ac0bd925aa5430bc7864fe", -} diff --git a/src/ipl3checksum/cickinds.pyi b/src/ipl3checksum/cickinds.pyi new file mode 100644 index 0000000..73456b0 --- /dev/null +++ b/src/ipl3checksum/cickinds.pyi @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 + +# SPDX-FileCopyrightText: © 2023 Decompollaborate +# SPDX-License-Identifier: MIT + +from __future__ import annotations + +class CICKind(): + CIC_6101: CICKind + CIC_6102_7101: CICKind + CIC_7102: CICKind + CIC_X103: CICKind # Both 6103 and 7103 + # 6104/7104 does not exist + CIC_X105: CICKind # Both 6105 and 7105 + CIC_X106: CICKind # Both 6106 and 7106 + + + def getSeed(self) -> int: + """ + Seed value set by the PIF ROM before the CPU (and the IPL3) is executed. + https://n64brew.dev/wiki/PIF-NUS#IPL3_checksum_algorithm + """ + + def getMagic(self) -> int: + """ + Magic value hardcoded inside the IPL3 itself + """ + + def getHashMd5(self) -> str: + """ + Expected md5 hash of the IPL3 blob + """ + + @staticmethod + def fromHashMd5(hash_str: str) -> CICKind|None: + ... + + @staticmethod + def fromValue(value: int) -> CICKind|None: + ... + diff --git a/src/ipl3checksum/detect.py b/src/ipl3checksum/detect.pyi similarity index 71% rename from src/ipl3checksum/detect.py rename to src/ipl3checksum/detect.pyi index cea20b5..d29892f 100644 --- a/src/ipl3checksum/detect.py +++ b/src/ipl3checksum/detect.pyi @@ -5,9 +5,7 @@ from __future__ import annotations -from . import utils -from .cickinds import CICKind, CICHashMd5 - +from .cickinds import CICKind def detectCICRaw(rawBytes: bytes) -> CICKind|None: """Tries to detect an IPL3 binary. @@ -21,17 +19,6 @@ def detectCICRaw(rawBytes: bytes) -> CICKind|None: CICKind|None: The detected CIC kind, or `None` if was not able to detect the CIC kind. """ - if len(rawBytes) < 0xFC0: - return None - - bytesHash = utils.getHashMd5(rawBytes[:0xFC0]) - - for kind, expectedHash in CICHashMd5.items(): - if bytesHash == expectedHash: - return kind - - return None - def detectCIC(romBytes: bytes) -> CICKind|None: """Tries to detect an IPL3 in a ROM. @@ -44,5 +31,3 @@ def detectCIC(romBytes: bytes) -> CICKind|None: Returns: CICKind|None: The detected CIC kind, or `None` if was not able to detect the CIC kind. """ - - return detectCICRaw(romBytes[0x40:0x1000]) diff --git a/src/ipl3checksum/ipl3checksum.pyi b/src/ipl3checksum/ipl3checksum.pyi new file mode 100644 index 0000000..9dda0b8 --- /dev/null +++ b/src/ipl3checksum/ipl3checksum.pyi @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 + +# SPDX-FileCopyrightText: © 2023 Decompollaborate +# SPDX-License-Identifier: MIT + +from __future__ import annotations + +from .cickinds import CICKind as CICKind + +from .checksum import calculateChecksum as calculateChecksum +from .checksum import calculateChecksumAutodetect as calculateChecksumAutodetect + +from .detect import detectCIC as detectCIC +from .detect import detectCICRaw as detectCICRaw diff --git a/src/ipl3checksum/utils.py b/src/ipl3checksum/utils.py deleted file mode 100644 index ee0ce30..0000000 --- a/src/ipl3checksum/utils.py +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env python3 - -# SPDX-FileCopyrightText: © 2023 Decompollaborate -# SPDX-License-Identifier: MIT - -from __future__ import annotations - -import hashlib - -def u32(value: int) -> int: - return value & 0xFFFFFFFF - -def getHashMd5(bytes: bytes) -> str: - return str(hashlib.md5(bytes).hexdigest())