From 9cc190ae05822eb84220b061cae1de171929b031 Mon Sep 17 00:00:00 2001 From: angie Date: Thu, 14 Dec 2023 21:32:45 -0300 Subject: [PATCH 01/17] Initial setup for Rust code --- Cargo.lock | 518 +++++++++++++++++++++++++++++++++++ Cargo.toml | 27 ++ pyproject.toml | 18 +- src/ipl3checksum/__init__.py | 7 +- src/rs/lib.rs | 22 ++ 5 files changed, 586 insertions(+), 6 deletions(-) create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/rs/lib.rs diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..a95e793 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,518 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "futures" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" + +[[package]] +name = "futures-executor" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" + +[[package]] +name = "futures-macro" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" + +[[package]] +name = "futures-task" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" + +[[package]] +name = "futures-timer" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" + +[[package]] +name = "futures-util" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "indoc" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" + +[[package]] +name = "ipl3checksum" +version = "1.1.0" +dependencies = [ + "pyo3", + "rstest", + "thiserror", +] + +[[package]] +name = "libc" +version = "0.2.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "proc-macro2" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pyo3" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04e8453b658fe480c3e70c8ed4e3d3ec33eb74988bd186561b0cc66b85c3bc4b" +dependencies = [ + "cfg-if", + "indoc", + "libc", + "memoffset", + "parking_lot", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96fe70b176a89cff78f2fa7b3c930081e163d5379b4dcdf993e3ae29ca662e5" +dependencies = [ + "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "214929900fd25e6604661ed9cf349727c8920d47deff196c4e28165a6ef2a96b" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dac53072f717aa1bfa4db832b39de8c875b7c7af4f4a6fe93cdbf9264cf8383b" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7774b5a8282bd4f25f803b1f0d945120be959a36c72e08e7cd031c792fdfd424" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "relative-path" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c707298afce11da2efef2f600116fa93ffa7a032b5d7b628aa17711ec81383ca" + +[[package]] +name = "rstest" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97eeab2f3c0a199bc4be135c36c924b6590b88c377d416494288c14f2db30199" +dependencies = [ + "futures", + "futures-timer", + "rstest_macros", + "rustc_version", +] + +[[package]] +name = "rstest_macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d428f8247852f894ee1be110b375111b586d4fa431f6c46e64ba5a0dcccbe605" +dependencies = [ + "cfg-if", + "glob", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version", + "syn", + "unicode-ident", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" + +[[package]] +name = "syn" +version = "2.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-lexicon" +version = "0.12.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c39fd04924ca3a864207c66fc2cd7d22d7c016007f9ce846cbb9326331930a" + +[[package]] +name = "thiserror" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unindent" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..62f2da4 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,27 @@ +# SPDX-FileCopyrightText: © 2023 Decompollaborate +# SPDX-License-Identifier: MIT + +[package] +name = "ipl3checksum" +# Version should be synced with src/ipl3checksum/__init__.py and pyproject.toml +version = "1.1.0" +edition = "2021" +description = "Library to calculate the IPL3 checksum for N64 ROMs" +repository = "https://github.com/decompollaborate/ipl3checksum" +license = "MIT" + +[lib] +name = "ipl3checksum" +path = "src/rs/lib.rs" +crate-type = ["lib", "cdylib"] + +[dependencies] +pyo3 = { version="0.20.0", features = ["extension-module"], optional = true } +thiserror = "1.0" + +[dev-dependencies] +rstest = "0.18.2" + +[features] +c_bindings = [] +python_bindings = ["dep:pyo3"] diff --git a/pyproject.toml b/pyproject.toml index 85329df..b8f5f34 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,19 +3,24 @@ [project] name = "ipl3checksum" -version = "1.0.1" +# Version should be synced with src/ipl3checksum/__init__.py and Cargo.toml +version = "1.1.0.dev0" description = "Library to calculate the IPL3 checksum for N64 ROMs" readme = "README.md" requires-python = ">=3.7" -dynamic = ["dependencies"] +classifiers = [ + "Programming Language :: Rust", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", +] [project.urls] "Homepage" = "https://github.com/decompollaborate/ipl3checksum" "Bug Tracker" = "https://github.com/decompollaborate/ipl3checksum/issues" [build-system] -requires = ["hatchling", "hatch-requirements-txt"] -build-backend = "hatchling.build" +requires = ["maturin>=1.2,<2.0"] +build-backend = "maturin" [project.scripts] ipl3checksum = "ipl3checksum.frontends.climain:ipl3checksumMain" @@ -25,3 +30,8 @@ skip = ["cp36-*"] [tool.setuptools.package-data] ipl3checksum = ["py.typed"] + +[tool.maturin] +features = ["pyo3/extension-module", "python_bindings"] +# https://github.com/PyO3/maturin/blob/0dee40510083c03607834c821eea76964140a126/Readme.md#mixed-rustpython-projects +python-source = "src" diff --git a/src/ipl3checksum/__init__.py b/src/ipl3checksum/__init__.py index c8086d8..9ce3d87 100644 --- a/src/ipl3checksum/__init__.py +++ b/src/ipl3checksum/__init__.py @@ -5,8 +5,9 @@ from __future__ import annotations -__version_info__: tuple[int, int, int] = (1, 0, 1) -__version__ = ".".join(map(str, __version_info__)) +# Version should be synced with pyproject.toml and Cargo.toml +__version_info__: tuple[int, int, int] = (1, 1, 0) +__version__ = ".".join(map(str, __version_info__)) + ".dev0" __author__ = "Decompollaborate" from . import utils as utils @@ -18,3 +19,5 @@ from .detect import detectCIC as detectCIC from .detect import detectCICRaw as detectCICRaw + +from .ipl3checksum import * diff --git a/src/rs/lib.rs b/src/rs/lib.rs new file mode 100644 index 0000000..1675401 --- /dev/null +++ b/src/rs/lib.rs @@ -0,0 +1,22 @@ +/* SPDX-FileCopyrightText: © 2023 Decompollaborate */ +/* SPDX-License-Identifier: MIT */ + +#[cfg(feature = "python_bindings")] +use pyo3::prelude::*; + +#[cfg(feature = "python_bindings")] +#[pyfunction] +fn testfunc(a: i32) -> String { + a.to_string() +} + +#[cfg(feature = "python_bindings")] +#[pymodule] +fn ipl3checksum(_py: Python<'_>, m: &PyModule) -> PyResult<()> { + m.add_function(wrap_pyfunction!(testfunc, m)?)?; + Ok(()) +} + +#[cfg(test)] +mod tests { +} From 4ae908399289267bad94d022144951c994c68ce1 Mon Sep 17 00:00:00 2001 From: angie Date: Thu, 14 Dec 2023 22:28:14 -0300 Subject: [PATCH 02/17] Port CICKind to Rust --- Cargo.lock | 238 --------------------------------------------- Cargo.toml | 6 +- src/rs/cickinds.rs | 78 +++++++++++++++ src/rs/lib.rs | 10 +- 4 files changed, 84 insertions(+), 248 deletions(-) create mode 100644 src/rs/cickinds.rs diff --git a/Cargo.lock b/Cargo.lock index a95e793..e0cbfa8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,15 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "aho-corasick" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" -dependencies = [ - "memchr", -] - [[package]] name = "autocfg" version = "1.1.0" @@ -29,107 +20,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "futures" -version = "0.3.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" - -[[package]] -name = "futures-executor" -version = "0.3.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" - -[[package]] -name = "futures-macro" -version = "0.3.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-sink" -version = "0.3.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" - -[[package]] -name = "futures-task" -version = "0.3.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" - -[[package]] -name = "futures-timer" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" - -[[package]] -name = "futures-util" -version = "0.3.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "glob" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" - [[package]] name = "heck" version = "0.4.1" @@ -147,8 +37,6 @@ name = "ipl3checksum" version = "1.1.0" dependencies = [ "pyo3", - "rstest", - "thiserror", ] [[package]] @@ -167,12 +55,6 @@ dependencies = [ "scopeguard", ] -[[package]] -name = "memchr" -version = "2.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" - [[package]] name = "memoffset" version = "0.9.0" @@ -211,18 +93,6 @@ dependencies = [ "windows-targets", ] -[[package]] -name = "pin-project-lite" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - [[package]] name = "proc-macro2" version = "1.0.70" @@ -311,100 +181,12 @@ dependencies = [ "bitflags", ] -[[package]] -name = "regex" -version = "1.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" - -[[package]] -name = "relative-path" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c707298afce11da2efef2f600116fa93ffa7a032b5d7b628aa17711ec81383ca" - -[[package]] -name = "rstest" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97eeab2f3c0a199bc4be135c36c924b6590b88c377d416494288c14f2db30199" -dependencies = [ - "futures", - "futures-timer", - "rstest_macros", - "rustc_version", -] - -[[package]] -name = "rstest_macros" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d428f8247852f894ee1be110b375111b586d4fa431f6c46e64ba5a0dcccbe605" -dependencies = [ - "cfg-if", - "glob", - "proc-macro2", - "quote", - "regex", - "relative-path", - "rustc_version", - "syn", - "unicode-ident", -] - -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver", -] - [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "semver" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" - -[[package]] -name = "slab" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] - [[package]] name = "smallvec" version = "1.11.2" @@ -428,26 +210,6 @@ version = "0.12.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c39fd04924ca3a864207c66fc2cd7d22d7c016007f9ce846cbb9326331930a" -[[package]] -name = "thiserror" -version = "1.0.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "unicode-ident" version = "1.0.12" diff --git a/Cargo.toml b/Cargo.toml index 62f2da4..7a74c32 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,10 +17,10 @@ crate-type = ["lib", "cdylib"] [dependencies] pyo3 = { version="0.20.0", features = ["extension-module"], optional = true } -thiserror = "1.0" +# thiserror = "1.0" -[dev-dependencies] -rstest = "0.18.2" +# [dev-dependencies] +# rstest = "0.18.2" [features] c_bindings = [] diff --git a/src/rs/cickinds.rs b/src/rs/cickinds.rs new file mode 100644 index 0000000..37785b8 --- /dev/null +++ b/src/rs/cickinds.rs @@ -0,0 +1,78 @@ +/* SPDX-FileCopyrightText: © 2023 Decompollaborate */ +/* SPDX-License-Identifier: MIT */ + +#[cfg(feature = "python_bindings")] +use pyo3::prelude::*; + +#[cfg_attr(feature = "python_bindings", pyclass(module = "ipl3checksum"))] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum CICKind { + Cic6101, + Cic6102_7101, + Cic7102, + CicX103, // Both 6103 and 7103 + // 6104/7104 does not exist + CicX105, // Both 6105 and 7105 + CicX106, // Both 6106 and 7106 +} + +#[cfg_attr(feature = "python_bindings", pymethods)] +impl CICKind { + pub fn get_seed(&self) -> u32 { + match self { + CICKind::Cic6101 => 0x3F, + CICKind::Cic6102_7101 => 0x3F, + CICKind::Cic7102 => 0x3F, + CICKind::CicX103 => 0x78, + CICKind::CicX105 => 0x91, + CICKind::CicX106 => 0x85, + } + } + + pub fn get_magic(&self) -> u32 { + match self { + CICKind::Cic6101 => 0x5D588B65, + CICKind::Cic6102_7101 => 0x5D588B65, + CICKind::Cic7102 => 0x5D588B65, + CICKind::CicX103 => 0x6C078965, + CICKind::CicX105 => 0x5D588B65, + CICKind::CicX106 => 0x6C078965, + } + } + + pub fn get_hash_md5(&self) -> &str { + match self { + CICKind::Cic6101 => "900b4a5b68edb71f4c7ed52acd814fc5", + CICKind::Cic6102_7101 => "e24dd796b2fa16511521139d28c8356b", + CICKind::Cic7102 => "955894c2e40a698bf98a67b78a4e28fa", + CICKind::CicX103 => "319038097346e12c26c3c21b56f86f23", + CICKind::CicX105 => "ff22a296e55d34ab0a077dc2ba5f5796", + CICKind::CicX106 => "6460387749ac0bd925aa5430bc7864fe", + } + } + + #[cfg(feature = "python_bindings")] + #[staticmethod] + pub fn from_value(value: usize) -> Option { + CICKind::from_value_impl(value) + } + + #[cfg(not(feature = "python_bindings"))] + pub fn from_value(value: usize) -> Option { + CICKind::from_value_impl(value) + } +} + +impl CICKind { + fn from_value_impl(value: usize) -> Option { + match value { + 6101 => Some(CICKind::Cic6101), + 6102 | 7101 => Some(CICKind::Cic6102_7101), + 7102 => Some(CICKind::Cic7102), + 6103 | 7103 => Some(CICKind::CicX103), + 6105 | 7105 => Some(CICKind::CicX105), + 6106 | 7106 => Some(CICKind::CicX106), + _ => None + } + } +} diff --git a/src/rs/lib.rs b/src/rs/lib.rs index 1675401..dda1a4c 100644 --- a/src/rs/lib.rs +++ b/src/rs/lib.rs @@ -1,19 +1,15 @@ /* SPDX-FileCopyrightText: © 2023 Decompollaborate */ /* SPDX-License-Identifier: MIT */ -#[cfg(feature = "python_bindings")] -use pyo3::prelude::*; +pub mod cickinds; #[cfg(feature = "python_bindings")] -#[pyfunction] -fn testfunc(a: i32) -> String { - a.to_string() -} +use pyo3::prelude::*; #[cfg(feature = "python_bindings")] #[pymodule] fn ipl3checksum(_py: Python<'_>, m: &PyModule) -> PyResult<()> { - m.add_function(wrap_pyfunction!(testfunc, m)?)?; + m.add_class::()?; Ok(()) } From a96b4633dcf24f26be92da02d43a8e7932ef2f4c Mon Sep 17 00:00:00 2001 From: angie Date: Fri, 15 Dec 2023 10:52:37 -0300 Subject: [PATCH 03/17] Literal port of python code for checksum --- src/rs/checksum.rs | 200 +++++++++++++++++++++++++++++++++++++++++++++ src/rs/cickinds.rs | 61 +++++++------- src/rs/lib.rs | 3 + src/rs/utils.rs | 31 +++++++ 4 files changed, 265 insertions(+), 30 deletions(-) create mode 100644 src/rs/checksum.rs create mode 100644 src/rs/utils.rs diff --git a/src/rs/checksum.rs b/src/rs/checksum.rs new file mode 100644 index 0000000..bb77d44 --- /dev/null +++ b/src/rs/checksum.rs @@ -0,0 +1,200 @@ +/* SPDX-FileCopyrightText: © 2023 Decompollaborate */ +/* SPDX-License-Identifier: MIT */ + +#[cfg(feature = "python_bindings")] +use pyo3::prelude::*; + +use crate::cickinds::CICKind; +use crate::utils; + + +fn readWordFromRam(romWords: &[u32], entrypointRam: u32, ramAddr: u32) -> u32 { + //return romWords[utils.u32(ramAddr - entrypointRam + 0x1000) / 4] + romWords[((ramAddr - entrypointRam + 0x1000) / 4) as usize] +} + + +pub fn calculateChecksum(romBytes: &[u8], kind: &CICKind) -> Option<(u32, u32)> { + /* + 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 romBytes.len() < 0x101000 { + return None; + } + + let romWords = utils::read_u32_vec(romBytes, 0, 0x101000); + + let seed = kind.get_seed(); + let magic = kind.get_magic(); + + let mut s6 = seed; + + let mut a0 = romWords[8/4]; + if *kind == CICKind::CIC_X103 { + a0 -= 0x100000; + } + if *kind == CICKind::CIC_X106 { + a0 -= 0x200000; + } + let entrypointRam = a0; + + let mut at = magic; + let mut lo = s6 * at; + + if *kind == CICKind::CIC_X105 { + s6 = 0xA0000200; + } + + let mut ra = 0x100000; + + let mut v1 = 0; + let mut t0 = 0; + + let mut t1 = a0; + + let mut t5 = 0x20; + + //let mut v0 = utils.u32(lo); + let mut v0 = lo; + v0 += 1; + + let mut a3 = v0; + let mut t2 = v0; + let mut t3 = v0; + let mut s0 = v0; + let mut a2 = v0; + let mut t4 = v0; + + // poor man's do while + let mut LA40005F0_loop = true; + while LA40005F0_loop { + // v0 = *t1 + v0 = readWordFromRam(&romWords, entrypointRam, t1); + + //v1 = utils.u32(a3 + v0); + v1 = a3 + v0; + + //at = utils.u32(v1) < utils.u32(a3); + at = if v1 < a3 { 1 } else { 0 }; + + let a1 = v1; + // if (at == 0) goto LA4000608; + + if at != 0 { + //t2 = utils.u32(t2 + 0x1) + t2 = t2 + 0x1; + } + + // LA4000608 + v1 = v0 & 0x1F; + //t7 = utils.u32(t5 - v1) + let t7 = t5 - v1; + + + //let t8 = utils.u32(v0 >> t7) + //let t6 = utils.u32(v0 << v1) + let t8 = v0 >> t7; + let t6 = v0 << v1; + + a0 = t6 | t8; + // at = utils.u32(a2) < utils.u32(v0); + at = if a2 < v0 { 1 } else { 0 }; + a3 = a1; + + t3 = t3 ^ v0; + + //s0 = utils.u32(s0 + a0) + s0 = s0 + a0; + // if (at == 0) goto LA400063C; + if at != 0 { + let 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 + let mut t7 = romWords[((s6 - 0xA0000004 + 0x000554) / 4) as usize]; + + //t0 = utils.u32(t0 + 0x4); + //s6 = utils.u32(s6 + 0x4); + t0 = t0 + 0x4; + s6 = s6 + 0x4; + + t7 = v0 ^ t7; + + // t4 = utils.u32(t7 + t4); + t4 = t7 + t4; + + t7 = 0xA00002FF; + + // t1 = utils.u32(t1 + 0x4); + t1 = t1 + 0x4; + + // s6 = utils.u32(s6 & t7); + s6 = s6 & t7; + } else { + // t0 = utils.u32(t0 + 0x4); + t0 = t0 + 0x4; + + let t7 = v0 ^ s0; + + // t1 = utils.u32(t1 + 0x4); + t1 = t1 + 0x4; + + // t4 = utils.u32(t7 + t4); + t4 = t7 + t4; + } + + + // if (t0 != ra) goto LA40005F0; + if t0 == ra { + LA40005F0_loop = false; + } + } + + if *kind == CICKind::CIC_X103 { + let t6 = a3 ^ t2; + // a3 = utils.u32(t6 + t3); + a3 = t6 + t3; + + let t8 = s0 ^ a2; + // s0 = utils.u32(t8 + t4); + s0 = t8 + t4; + } else if *kind == CICKind::CIC_X106 { + /* + let t6 = utils.u32(a3 * t2); + a3 = utils.u32(t6 + t3); + let t8 = utils.u32(s0 * a2); + s0 = utils.u32(t8 + t4); + */ + let t6 = a3 * t2; + a3 = t6 + t3; + let t8 = s0 * a2; + s0 = t8 + t4; + } else { + let t6 = a3 ^ t2; + a3 = t6 ^ t3; + let t8 = s0 ^ a2; + s0 = t8 ^ t4; + } + + return Some((a3, s0)) +} diff --git a/src/rs/cickinds.rs b/src/rs/cickinds.rs index 37785b8..98979aa 100644 --- a/src/rs/cickinds.rs +++ b/src/rs/cickinds.rs @@ -6,48 +6,49 @@ use pyo3::prelude::*; #[cfg_attr(feature = "python_bindings", pyclass(module = "ipl3checksum"))] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +#[allow(non_camel_case_types)] pub enum CICKind { - Cic6101, - Cic6102_7101, - Cic7102, - CicX103, // Both 6103 and 7103 + CIC_6101, + CIC_6102_7101, + CIC_7102, + CIC_X103, // Both 6103 and 7103 // 6104/7104 does not exist - CicX105, // Both 6105 and 7105 - CicX106, // Both 6106 and 7106 + CIC_X105, // Both 6105 and 7105 + CIC_X106, // Both 6106 and 7106 } #[cfg_attr(feature = "python_bindings", pymethods)] impl CICKind { pub fn get_seed(&self) -> u32 { match self { - CICKind::Cic6101 => 0x3F, - CICKind::Cic6102_7101 => 0x3F, - CICKind::Cic7102 => 0x3F, - CICKind::CicX103 => 0x78, - CICKind::CicX105 => 0x91, - CICKind::CicX106 => 0x85, + 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, } } pub fn get_magic(&self) -> u32 { match self { - CICKind::Cic6101 => 0x5D588B65, - CICKind::Cic6102_7101 => 0x5D588B65, - CICKind::Cic7102 => 0x5D588B65, - CICKind::CicX103 => 0x6C078965, - CICKind::CicX105 => 0x5D588B65, - CICKind::CicX106 => 0x6C078965, + 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, } } pub fn get_hash_md5(&self) -> &str { match self { - CICKind::Cic6101 => "900b4a5b68edb71f4c7ed52acd814fc5", - CICKind::Cic6102_7101 => "e24dd796b2fa16511521139d28c8356b", - CICKind::Cic7102 => "955894c2e40a698bf98a67b78a4e28fa", - CICKind::CicX103 => "319038097346e12c26c3c21b56f86f23", - CICKind::CicX105 => "ff22a296e55d34ab0a077dc2ba5f5796", - CICKind::CicX106 => "6460387749ac0bd925aa5430bc7864fe", + 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", } } @@ -66,12 +67,12 @@ impl CICKind { impl CICKind { fn from_value_impl(value: usize) -> Option { match value { - 6101 => Some(CICKind::Cic6101), - 6102 | 7101 => Some(CICKind::Cic6102_7101), - 7102 => Some(CICKind::Cic7102), - 6103 | 7103 => Some(CICKind::CicX103), - 6105 | 7105 => Some(CICKind::CicX105), - 6106 | 7106 => Some(CICKind::CicX106), + 6101 => Some(CICKind::CIC_6101), + 6102 | 7101 => Some(CICKind::CIC_6102_7101), + 7102 => Some(CICKind::CIC_7102), + 6103 | 7103 => Some(CICKind::CIC_X103), + 6105 | 7105 => Some(CICKind::CIC_X105), + 6106 | 7106 => Some(CICKind::CIC_X106), _ => None } } diff --git a/src/rs/lib.rs b/src/rs/lib.rs index dda1a4c..1cbdbab 100644 --- a/src/rs/lib.rs +++ b/src/rs/lib.rs @@ -2,6 +2,9 @@ /* SPDX-License-Identifier: MIT */ pub mod cickinds; +pub mod checksum; + +mod utils; #[cfg(feature = "python_bindings")] use pyo3::prelude::*; diff --git a/src/rs/utils.rs b/src/rs/utils.rs new file mode 100644 index 0000000..ce06214 --- /dev/null +++ b/src/rs/utils.rs @@ -0,0 +1,31 @@ +/* SPDX-FileCopyrightText: © 2023 Decompollaborate */ +/* SPDX-License-Identifier: MIT */ + +pub(crate) fn read_u32(bytes: &[u8], offset: usize) -> u32 { + if offset % 4 != 0 { + panic!("Unaligned read"); + } + + if offset + 4 > bytes.len() { + panic!("Out of bounds"); + } + + /* + match bytes[offset..offset + 4].try_into() { + Ok(bytes) => u32::from_be_bytes(bytes), + Err(_error) => todo!(), + } + */ + + u32::from_be_bytes(bytes[offset..offset + 4].try_into().unwrap()) +} + +pub(crate) fn read_u32_vec(bytes: &[u8], offset: usize, len: usize) -> Vec { + let mut ret = vec![0;len]; + + for i in 0..(len*4) { + ret[i] = read_u32(bytes, offset + i); + } + + ret +} From 69791948d437c2bb4c144cc421c74980ad5fb0f4 Mon Sep 17 00:00:00 2001 From: angie Date: Fri, 15 Dec 2023 12:12:08 -0300 Subject: [PATCH 04/17] Write tests for calculateChecksum --- src/rs/checksum.rs | 119 ++++++++++++++++++++++++++++++++++----------- src/rs/utils.rs | 6 +-- 2 files changed, 94 insertions(+), 31 deletions(-) diff --git a/src/rs/checksum.rs b/src/rs/checksum.rs index bb77d44..b82d582 100644 --- a/src/rs/checksum.rs +++ b/src/rs/checksum.rs @@ -32,7 +32,7 @@ pub fn calculateChecksum(romBytes: &[u8], kind: &CICKind) -> Option<(u32, u32)> return None; } - let romWords = utils::read_u32_vec(romBytes, 0, 0x101000); + let romWords = utils::read_u32_vec(romBytes, 0, 0x101000/4); let seed = kind.get_seed(); let magic = kind.get_magic(); @@ -41,28 +41,28 @@ pub fn calculateChecksum(romBytes: &[u8], kind: &CICKind) -> Option<(u32, u32)> let mut a0 = romWords[8/4]; if *kind == CICKind::CIC_X103 { - a0 -= 0x100000; + a0 = a0.wrapping_sub(0x100000); } if *kind == CICKind::CIC_X106 { - a0 -= 0x200000; + a0 = a0.wrapping_sub(0x200000); } let entrypointRam = a0; let mut at = magic; - let mut lo = s6 * at; + let lo = s6.wrapping_mul(at); if *kind == CICKind::CIC_X105 { s6 = 0xA0000200; } - let mut ra = 0x100000; + let ra = 0x100000; - let mut v1 = 0; - let mut t0 = 0; + let mut v1: u32 = 0; + let mut t0: u32 = 0; - let mut t1 = a0; + let mut t1: u32 = a0; - let mut t5 = 0x20; + let t5: u32 = 0x20; //let mut v0 = utils.u32(lo); let mut v0 = lo; @@ -82,7 +82,7 @@ pub fn calculateChecksum(romBytes: &[u8], kind: &CICKind) -> Option<(u32, u32)> v0 = readWordFromRam(&romWords, entrypointRam, t1); //v1 = utils.u32(a3 + v0); - v1 = a3 + v0; + v1 = a3.wrapping_add(v0); //at = utils.u32(v1) < utils.u32(a3); at = if v1 < a3 { 1 } else { 0 }; @@ -92,19 +92,19 @@ pub fn calculateChecksum(romBytes: &[u8], kind: &CICKind) -> Option<(u32, u32)> if at != 0 { //t2 = utils.u32(t2 + 0x1) - t2 = t2 + 0x1; + t2 = t2.wrapping_add(0x1); } // LA4000608 v1 = v0 & 0x1F; //t7 = utils.u32(t5 - v1) - let t7 = t5 - v1; + let t7: u32 = t5.wrapping_sub(v1); //let t8 = utils.u32(v0 >> t7) //let t6 = utils.u32(v0 << v1) - let t8 = v0 >> t7; - let t6 = v0 << v1; + let t8 = v0.wrapping_shr(t7); + let t6 = v0.wrapping_shl(v1); a0 = t6 | t8; // at = utils.u32(a2) < utils.u32(v0); @@ -114,7 +114,7 @@ pub fn calculateChecksum(romBytes: &[u8], kind: &CICKind) -> Option<(u32, u32)> t3 = t3 ^ v0; //s0 = utils.u32(s0 + a0) - s0 = s0 + a0; + s0 = s0.wrapping_add(a0); // if (at == 0) goto LA400063C; if at != 0 { let t9 = a3 ^ v0; @@ -135,32 +135,32 @@ pub fn calculateChecksum(romBytes: &[u8], kind: &CICKind) -> Option<(u32, u32)> //t0 = utils.u32(t0 + 0x4); //s6 = utils.u32(s6 + 0x4); - t0 = t0 + 0x4; - s6 = s6 + 0x4; + t0 = t0.wrapping_add(0x4); + s6 = s6.wrapping_add(0x4); t7 = v0 ^ t7; // t4 = utils.u32(t7 + t4); - t4 = t7 + t4; + t4 = t7.wrapping_add(t4); t7 = 0xA00002FF; // t1 = utils.u32(t1 + 0x4); - t1 = t1 + 0x4; + t1 = t1.wrapping_add(0x4); // s6 = utils.u32(s6 & t7); s6 = s6 & t7; } else { // t0 = utils.u32(t0 + 0x4); - t0 = t0 + 0x4; + t0 = t0.wrapping_add(0x4); let t7 = v0 ^ s0; // t1 = utils.u32(t1 + 0x4); - t1 = t1 + 0x4; + t1 = t1.wrapping_add(0x4); // t4 = utils.u32(t7 + t4); - t4 = t7 + t4; + t4 = t7.wrapping_add(t4); } @@ -173,11 +173,11 @@ pub fn calculateChecksum(romBytes: &[u8], kind: &CICKind) -> Option<(u32, u32)> if *kind == CICKind::CIC_X103 { let t6 = a3 ^ t2; // a3 = utils.u32(t6 + t3); - a3 = t6 + t3; + a3 = t6.wrapping_add(t3); let t8 = s0 ^ a2; // s0 = utils.u32(t8 + t4); - s0 = t8 + t4; + s0 = t8.wrapping_add(t4); } else if *kind == CICKind::CIC_X106 { /* let t6 = utils.u32(a3 * t2); @@ -185,10 +185,10 @@ pub fn calculateChecksum(romBytes: &[u8], kind: &CICKind) -> Option<(u32, u32)> let t8 = utils.u32(s0 * a2); s0 = utils.u32(t8 + t4); */ - let t6 = a3 * t2; - a3 = t6 + t3; - let t8 = s0 * a2; - s0 = t8 + t4; + let t6 = a3.wrapping_mul(t2); + a3 = t6.wrapping_add(t3); + let t8 = s0.wrapping_mul(a2); + s0 = t8.wrapping_add(t4); } else { let t6 = a3 ^ t2; a3 = t6 ^ t3; @@ -198,3 +198,66 @@ pub fn calculateChecksum(romBytes: &[u8], kind: &CICKind) -> Option<(u32, u32)> return Some((a3, s0)) } + +#[cfg(test)] +mod tests { + //use rstest::rstest; + //use std::path::PathBuf; + + use std::fs; + + use crate::{cickinds::CICKind, utils}; + + #[test] + fn test_dummy_files() -> Result<(), ()> { + println!("asdf"); + + for path_result in fs::read_dir("tests/dummytests").unwrap() { + let ipl3_folder = path_result.unwrap(); + let folder_name = ipl3_folder.file_name(); + + println!("{:?}", folder_name); + + let kind = match folder_name.to_str().unwrap() { + "CIC_6101" => CICKind::CIC_6101, + "CIC_6102_7101" => CICKind::CIC_6102_7101, + "CIC_7102" => CICKind::CIC_7102, + "CIC_X103" => CICKind::CIC_X103, + "CIC_X105" => CICKind::CIC_X105, + "CIC_X106" => CICKind::CIC_X106, + _ => panic!("Unknown cic kind"), + }; + println!("CIC Kind: {:?}", kind); + + for bin_path_result in fs::read_dir(ipl3_folder.path() ).unwrap() { + let bin_path = bin_path_result.unwrap(); + + println!("{:?}", bin_path); + + println!(" Reading..."); + + let bin_bytes = fs::read(&bin_path.path()).unwrap(); + + println!(" Calculating checksum..."); + let checksum = super::calculateChecksum(&bin_bytes, &kind).unwrap(); + + println!(" Calculated checksum is: 0x{:08X} 0x{:08X}", checksum.0, checksum.1); + + println!(" Checking checksum..."); + let bin_checksum = utils::read_u32_vec(&bin_bytes, 0x10, 2); + + println!(" Expected checksum is: 0x{:08X} 0x{:08X}", bin_checksum[0], bin_checksum[1]); + + assert_eq!(checksum.0, bin_checksum[0]); + assert_eq!(checksum.1, bin_checksum[1]); + + println!(" {:?} OK", bin_path); + + println!(); + } + + println!(); + } + Ok(()) + } +} diff --git a/src/rs/utils.rs b/src/rs/utils.rs index ce06214..972e128 100644 --- a/src/rs/utils.rs +++ b/src/rs/utils.rs @@ -7,7 +7,7 @@ pub(crate) fn read_u32(bytes: &[u8], offset: usize) -> u32 { } if offset + 4 > bytes.len() { - panic!("Out of bounds"); + panic!("Out of bounds. Offset {:X}, len {:X}", offset, bytes.len()); } /* @@ -23,8 +23,8 @@ pub(crate) fn read_u32(bytes: &[u8], offset: usize) -> u32 { pub(crate) fn read_u32_vec(bytes: &[u8], offset: usize, len: usize) -> Vec { let mut ret = vec![0;len]; - for i in 0..(len*4) { - ret[i] = read_u32(bytes, offset + i); + for i in 0..len { + ret[i] = read_u32(bytes, offset + i*4); } ret From 2d7cd61502360c33a41f0f3a96cf8e8a5af753bc Mon Sep 17 00:00:00 2001 From: angie Date: Fri, 15 Dec 2023 12:34:05 -0300 Subject: [PATCH 05/17] Port detect --- Cargo.lock | 7 +++++++ Cargo.toml | 1 + src/rs/cickinds.rs | 12 ++++++++++++ src/rs/detect.rs | 41 +++++++++++++++++++++++++++++++++++++++++ src/rs/lib.rs | 1 + src/rs/utils.rs | 6 ++++++ 6 files changed, 68 insertions(+) create mode 100644 src/rs/detect.rs diff --git a/Cargo.lock b/Cargo.lock index e0cbfa8..cfeddea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -36,6 +36,7 @@ checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" name = "ipl3checksum" version = "1.1.0" dependencies = [ + "md5", "pyo3", ] @@ -55,6 +56,12 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "md5" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" + [[package]] name = "memoffset" version = "0.9.0" diff --git a/Cargo.toml b/Cargo.toml index 7a74c32..d9d8dad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ path = "src/rs/lib.rs" crate-type = ["lib", "cdylib"] [dependencies] +md5 = "0.7.0" pyo3 = { version="0.20.0", features = ["extension-module"], optional = true } # thiserror = "1.0" diff --git a/src/rs/cickinds.rs b/src/rs/cickinds.rs index 98979aa..42b939f 100644 --- a/src/rs/cickinds.rs +++ b/src/rs/cickinds.rs @@ -65,6 +65,18 @@ impl CICKind { } impl CICKind { + pub fn from_hash_md5(hash_str: &str) -> Option { + match hash_str { + "900b4a5b68edb71f4c7ed52acd814fc5" => Some(CICKind::CIC_6101), + "e24dd796b2fa16511521139d28c8356b" => Some(CICKind::CIC_6102_7101), + "955894c2e40a698bf98a67b78a4e28fa" => Some(CICKind::CIC_7102), + "319038097346e12c26c3c21b56f86f23" => Some(CICKind::CIC_X103), + "ff22a296e55d34ab0a077dc2ba5f5796" => Some(CICKind::CIC_X105), + "6460387749ac0bd925aa5430bc7864fe" => Some(CICKind::CIC_X106), + _ => None, + } + } + fn from_value_impl(value: usize) -> Option { match value { 6101 => Some(CICKind::CIC_6101), diff --git a/src/rs/detect.rs b/src/rs/detect.rs new file mode 100644 index 0000000..ca383ae --- /dev/null +++ b/src/rs/detect.rs @@ -0,0 +1,41 @@ +/* SPDX-FileCopyrightText: © 2023 Decompollaborate */ +/* SPDX-License-Identifier: MIT */ + +use crate::cickinds::CICKind; +use crate::utils; + +pub fn detectCICRaw(rawBytes: &[u8]) -> Option { + /*Tries to detect an IPL3 binary. + + The argument to this function must be exactly the IPL3 binary, stripping the rest of the ROM. + + Args: + rawBytes (bytes): IPL3 binary in big endian format. + + Returns: + CICKind|None: The detected CIC kind, or `None` if was not able to detect the CIC kind. + */ + + if rawBytes.len() != 0xFC0 { + return None; + } + + let bytesHash = utils::get_hash_md5(rawBytes); + + CICKind::from_hash_md5(&bytesHash) +} + +pub fn detectCIC(romBytes: &[u8]) -> Option { + /*Tries to detect an IPL3 in a ROM. + + The argument to this function must be a ROM in big endian format. + + Args: + romBytes (bytes): ROMbinary in big endian format. + + Returns: + CICKind|None: The detected CIC kind, or `None` if was not able to detect the CIC kind. + */ + + detectCICRaw(&romBytes[0x40..0x1000]) +} diff --git a/src/rs/lib.rs b/src/rs/lib.rs index 1cbdbab..bcacd96 100644 --- a/src/rs/lib.rs +++ b/src/rs/lib.rs @@ -3,6 +3,7 @@ pub mod cickinds; pub mod checksum; +pub mod detect; mod utils; diff --git a/src/rs/utils.rs b/src/rs/utils.rs index 972e128..48ef42d 100644 --- a/src/rs/utils.rs +++ b/src/rs/utils.rs @@ -1,6 +1,8 @@ /* SPDX-FileCopyrightText: © 2023 Decompollaborate */ /* SPDX-License-Identifier: MIT */ +use md5; + pub(crate) fn read_u32(bytes: &[u8], offset: usize) -> u32 { if offset % 4 != 0 { panic!("Unaligned read"); @@ -29,3 +31,7 @@ pub(crate) fn read_u32_vec(bytes: &[u8], offset: usize, len: usize) -> Vec ret } + +pub(crate) fn get_hash_md5(bytes: &[u8]) -> String { + format!("{:x}", md5::compute(bytes)) +} From 9b26d1501128dc53bb3864ab0c1e7c7ff8d15116 Mon Sep 17 00:00:00 2001 From: angie Date: Fri, 15 Dec 2023 12:48:06 -0300 Subject: [PATCH 06/17] Cleanup CICKind --- .vscode/settings.json | 6 +++++ src/rs/cickinds.rs | 52 ++++++++++++++++++++++++++++++------------- 2 files changed, 43 insertions(+), 15 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..861e9e5 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "rust-analyzer.cargo.features": [ + "c_bindings", + "python_bindings" + ] +} \ No newline at end of file diff --git a/src/rs/cickinds.rs b/src/rs/cickinds.rs index 42b939f..55bf928 100644 --- a/src/rs/cickinds.rs +++ b/src/rs/cickinds.rs @@ -17,7 +17,6 @@ pub enum CICKind { CIC_X106, // Both 6106 and 7106 } -#[cfg_attr(feature = "python_bindings", pymethods)] impl CICKind { pub fn get_seed(&self) -> u32 { match self { @@ -52,19 +51,6 @@ impl CICKind { } } - #[cfg(feature = "python_bindings")] - #[staticmethod] - pub fn from_value(value: usize) -> Option { - CICKind::from_value_impl(value) - } - - #[cfg(not(feature = "python_bindings"))] - pub fn from_value(value: usize) -> Option { - CICKind::from_value_impl(value) - } -} - -impl CICKind { pub fn from_hash_md5(hash_str: &str) -> Option { match hash_str { "900b4a5b68edb71f4c7ed52acd814fc5" => Some(CICKind::CIC_6101), @@ -77,7 +63,7 @@ impl CICKind { } } - fn from_value_impl(value: usize) -> Option { + pub fn from_value(value: usize) -> Option { match value { 6101 => Some(CICKind::CIC_6101), 6102 | 7101 => Some(CICKind::CIC_6102_7101), @@ -89,3 +75,39 @@ impl CICKind { } } } + +#[cfg(feature = "python_bindings")] +mod python_bindings { + use pyo3::prelude::*; + + #[pymethods] + impl super::CICKind { + #[allow(non_snake_case)] + pub fn getSeed(&self) -> u32 { + self.get_seed() + } + + #[allow(non_snake_case)] + pub fn getMagic(&self) -> u32 { + self.get_magic() + } + + #[allow(non_snake_case)] + pub fn getHashMd5(&self) -> &str { + self.get_hash_md5() + } + + #[staticmethod] + #[allow(non_snake_case)] + pub fn fromHashMd5(hash_str: &str) -> Option { + super::CICKind::from_hash_md5(hash_str) + } + + #[staticmethod] + #[allow(non_snake_case)] + pub fn fromValue(value: usize) -> Option { + super::CICKind::from_value(value) + } + + } +} From 39aa352d79884f9bc8960a692d70cb9c33f195f7 Mon Sep 17 00:00:00 2001 From: angie Date: Fri, 15 Dec 2023 13:14:35 -0300 Subject: [PATCH 07/17] Wire up function bindings --- src/rs/checksum.rs | 78 ++++++++++++++++++++++++++++++++-------------- src/rs/cickinds.rs | 6 +--- src/rs/detect.rs | 32 ++++++++++++++----- src/rs/lib.rs | 4 +++ 4 files changed, 83 insertions(+), 37 deletions(-) diff --git a/src/rs/checksum.rs b/src/rs/checksum.rs index b82d582..7dab6bc 100644 --- a/src/rs/checksum.rs +++ b/src/rs/checksum.rs @@ -1,52 +1,46 @@ /* SPDX-FileCopyrightText: © 2023 Decompollaborate */ /* SPDX-License-Identifier: MIT */ -#[cfg(feature = "python_bindings")] -use pyo3::prelude::*; - use crate::cickinds::CICKind; -use crate::utils; - +use crate::{utils, detect}; -fn readWordFromRam(romWords: &[u32], entrypointRam: u32, ramAddr: u32) -> u32 { - //return romWords[utils.u32(ramAddr - entrypointRam + 0x1000) / 4] - romWords[((ramAddr - entrypointRam + 0x1000) / 4) as usize] +fn read_word_from_ram(rom_words: &[u32], entrypoint_ram: u32, ram_addr: u32) -> u32 { + rom_words[((ram_addr - entrypoint_ram + 0x1000) / 4) as usize] } - -pub fn calculateChecksum(romBytes: &[u8], kind: &CICKind) -> Option<(u32, u32)> { +pub fn calculate_checksum(rom_bytes: &[u8], kind: &CICKind) -> Option<(u32, u32)> { /* 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. + rom_bytes (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 + - `rom_bytes` not being big enough */ - if romBytes.len() < 0x101000 { + if rom_bytes.len() < 0x101000 { return None; } - let romWords = utils::read_u32_vec(romBytes, 0, 0x101000/4); + let rom_words = utils::read_u32_vec(rom_bytes, 0, 0x101000/4); let seed = kind.get_seed(); let magic = kind.get_magic(); let mut s6 = seed; - let mut a0 = romWords[8/4]; + let mut a0 = rom_words[8/4]; if *kind == CICKind::CIC_X103 { a0 = a0.wrapping_sub(0x100000); } if *kind == CICKind::CIC_X106 { a0 = a0.wrapping_sub(0x200000); } - let entrypointRam = a0; + let entrypoint_ram = a0; let mut at = magic; let lo = s6.wrapping_mul(at); @@ -57,7 +51,6 @@ pub fn calculateChecksum(romBytes: &[u8], kind: &CICKind) -> Option<(u32, u32)> let ra = 0x100000; - let mut v1: u32 = 0; let mut t0: u32 = 0; let mut t1: u32 = a0; @@ -76,13 +69,14 @@ pub fn calculateChecksum(romBytes: &[u8], kind: &CICKind) -> Option<(u32, u32)> let mut t4 = v0; // poor man's do while - let mut LA40005F0_loop = true; - while LA40005F0_loop { + // LA40005F0_loop + let mut loop_variable = true; + while loop_variable { // v0 = *t1 - v0 = readWordFromRam(&romWords, entrypointRam, t1); + v0 = read_word_from_ram(&rom_words, entrypoint_ram, t1); //v1 = utils.u32(a3 + v0); - v1 = a3.wrapping_add(v0); + let mut v1 = a3.wrapping_add(v0); //at = utils.u32(v1) < utils.u32(a3); at = if v1 < a3 { 1 } else { 0 }; @@ -131,7 +125,7 @@ pub fn calculateChecksum(romBytes: &[u8], kind: &CICKind) -> Option<(u32, u32)> // 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 - let mut t7 = romWords[((s6 - 0xA0000004 + 0x000554) / 4) as usize]; + let mut t7 = rom_words[((s6 - 0xA0000004 + 0x000554) / 4) as usize]; //t0 = utils.u32(t0 + 0x4); //s6 = utils.u32(s6 + 0x4); @@ -166,7 +160,7 @@ pub fn calculateChecksum(romBytes: &[u8], kind: &CICKind) -> Option<(u32, u32)> // if (t0 != ra) goto LA40005F0; if t0 == ra { - LA40005F0_loop = false; + loop_variable = false; } } @@ -199,6 +193,26 @@ pub fn calculateChecksum(romBytes: &[u8], kind: &CICKind) -> Option<(u32, u32)> return Some((a3, s0)) } +pub fn calculate_checksum_autodetect(rom_bytes: &[u8]) -> Option<(u32, u32)> { + /*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: + rom_bytes (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: + - `rom_bytes` not being big enough + - Not able to detect the CIC kind + */ + + let kind = detect::detect_cic(rom_bytes)?; + + calculate_checksum(rom_bytes, &kind) +} + #[cfg(test)] mod tests { //use rstest::rstest; @@ -239,7 +253,7 @@ mod tests { let bin_bytes = fs::read(&bin_path.path()).unwrap(); println!(" Calculating checksum..."); - let checksum = super::calculateChecksum(&bin_bytes, &kind).unwrap(); + let checksum = super::calculate_checksum(&bin_bytes, &kind).unwrap(); println!(" Calculated checksum is: 0x{:08X} 0x{:08X}", checksum.0, checksum.1); @@ -261,3 +275,19 @@ mod tests { Ok(()) } } + +#[cfg(feature = "python_bindings")] +#[allow(non_snake_case)] +pub(crate) mod python_bindings { + use pyo3::prelude::*; + + #[pyfunction] + pub(crate) fn calculateChecksum(rom_bytes: &[u8], kind: &super::CICKind) -> Option<(u32, u32)> { + super::calculate_checksum(rom_bytes, kind) + } + + #[pyfunction] + pub(crate) fn calculateChecksumAutodetect(rom_bytes: &[u8]) -> Option<(u32, u32)> { + super::calculate_checksum_autodetect(rom_bytes) + } +} diff --git a/src/rs/cickinds.rs b/src/rs/cickinds.rs index 55bf928..ba900c4 100644 --- a/src/rs/cickinds.rs +++ b/src/rs/cickinds.rs @@ -77,34 +77,30 @@ impl CICKind { } #[cfg(feature = "python_bindings")] +#[allow(non_snake_case)] mod python_bindings { use pyo3::prelude::*; #[pymethods] impl super::CICKind { - #[allow(non_snake_case)] pub fn getSeed(&self) -> u32 { self.get_seed() } - #[allow(non_snake_case)] pub fn getMagic(&self) -> u32 { self.get_magic() } - #[allow(non_snake_case)] pub fn getHashMd5(&self) -> &str { self.get_hash_md5() } #[staticmethod] - #[allow(non_snake_case)] pub fn fromHashMd5(hash_str: &str) -> Option { super::CICKind::from_hash_md5(hash_str) } #[staticmethod] - #[allow(non_snake_case)] pub fn fromValue(value: usize) -> Option { super::CICKind::from_value(value) } diff --git a/src/rs/detect.rs b/src/rs/detect.rs index ca383ae..9c8adce 100644 --- a/src/rs/detect.rs +++ b/src/rs/detect.rs @@ -4,38 +4,54 @@ use crate::cickinds::CICKind; use crate::utils; -pub fn detectCICRaw(rawBytes: &[u8]) -> Option { +pub fn detect_cic_raw(raw_bytes: &[u8]) -> Option { /*Tries to detect an IPL3 binary. The argument to this function must be exactly the IPL3 binary, stripping the rest of the ROM. Args: - rawBytes (bytes): IPL3 binary in big endian format. + raw_bytes (bytes): IPL3 binary in big endian format. Returns: CICKind|None: The detected CIC kind, or `None` if was not able to detect the CIC kind. */ - if rawBytes.len() != 0xFC0 { + if raw_bytes.len() != 0xFC0 { return None; } - let bytesHash = utils::get_hash_md5(rawBytes); + let bytes_hash = utils::get_hash_md5(raw_bytes); - CICKind::from_hash_md5(&bytesHash) + CICKind::from_hash_md5(&bytes_hash) } -pub fn detectCIC(romBytes: &[u8]) -> Option { +pub fn detect_cic(rom_bytes: &[u8]) -> Option { /*Tries to detect an IPL3 in a ROM. The argument to this function must be a ROM in big endian format. Args: - romBytes (bytes): ROMbinary in big endian format. + rom_bytes (bytes): ROMbinary in big endian format. Returns: CICKind|None: The detected CIC kind, or `None` if was not able to detect the CIC kind. */ - detectCICRaw(&romBytes[0x40..0x1000]) + detect_cic_raw(&rom_bytes[0x40..0x1000]) +} + +#[cfg(feature = "python_bindings")] +#[allow(non_snake_case)] +pub(crate) mod python_bindings { + use pyo3::prelude::*; + + #[pyfunction] + pub(crate) fn detectCICRaw(raw_bytes: &[u8]) -> Option { + super::detect_cic_raw(raw_bytes) + } + + #[pyfunction] + pub(crate) fn detectCIC(rom_bytes: &[u8]) -> Option { + super::detect_cic(rom_bytes) + } } diff --git a/src/rs/lib.rs b/src/rs/lib.rs index bcacd96..acec691 100644 --- a/src/rs/lib.rs +++ b/src/rs/lib.rs @@ -14,6 +14,10 @@ use pyo3::prelude::*; #[pymodule] fn ipl3checksum(_py: Python<'_>, m: &PyModule) -> PyResult<()> { m.add_class::()?; + m.add_function(wrap_pyfunction!(checksum::python_bindings::calculateChecksum, m)?)?; + m.add_function(wrap_pyfunction!(checksum::python_bindings::calculateChecksumAutodetect, m)?)?; + m.add_function(wrap_pyfunction!(detect::python_bindings::detectCICRaw, m)?)?; + m.add_function(wrap_pyfunction!(detect::python_bindings::detectCIC, m)?)?; Ok(()) } From acda9bff228aafa1ebb8cd3d03c9c64749bd39e8 Mon Sep 17 00:00:00 2001 From: angie Date: Fri, 15 Dec 2023 13:24:38 -0300 Subject: [PATCH 08/17] yeet python logic --- src/ipl3checksum/__init__.py | 10 -- src/ipl3checksum/checksum.py | 183 --------------------- src/ipl3checksum/checksum.pyi | 36 ++++ src/ipl3checksum/cickinds.py | 82 --------- src/ipl3checksum/cickinds.pyi | 41 +++++ src/ipl3checksum/{detect.py => detect.pyi} | 17 +- src/ipl3checksum/ipl3checksum.pyi | 14 ++ src/ipl3checksum/utils.py | 14 -- 8 files changed, 92 insertions(+), 305 deletions(-) delete mode 100644 src/ipl3checksum/checksum.py create mode 100644 src/ipl3checksum/checksum.pyi delete mode 100644 src/ipl3checksum/cickinds.py create mode 100644 src/ipl3checksum/cickinds.pyi rename src/ipl3checksum/{detect.py => detect.pyi} (71%) create mode 100644 src/ipl3checksum/ipl3checksum.pyi delete mode 100644 src/ipl3checksum/utils.py 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()) From 49b8b26bbd61f875ec98e95ad7d656b904be2a8d Mon Sep 17 00:00:00 2001 From: angie Date: Fri, 15 Dec 2023 13:47:22 -0300 Subject: [PATCH 09/17] Add name attribute --- src/ipl3checksum/cickinds.pyi | 3 +++ src/rs/cickinds.rs | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/src/ipl3checksum/cickinds.pyi b/src/ipl3checksum/cickinds.pyi index 73456b0..256840e 100644 --- a/src/ipl3checksum/cickinds.pyi +++ b/src/ipl3checksum/cickinds.pyi @@ -39,3 +39,6 @@ class CICKind(): def fromValue(value: int) -> CICKind|None: ... + @property + def name(self) -> str: + ... diff --git a/src/rs/cickinds.rs b/src/rs/cickinds.rs index ba900c4..3ae1a2e 100644 --- a/src/rs/cickinds.rs +++ b/src/rs/cickinds.rs @@ -105,5 +105,16 @@ mod python_bindings { super::CICKind::from_value(value) } + #[getter] + pub fn name(&self) -> &str { + match self { + super::CICKind::CIC_6101 => "CIC_6101", + super::CICKind::CIC_6102_7101 => "CIC_6102_7101", + super::CICKind::CIC_7102 => "CIC_7102", + super::CICKind::CIC_X103 => "CIC_X103", + super::CICKind::CIC_X105 => "CIC_X105", + super::CICKind::CIC_X106 => "CIC_X106", + } + } } } From ca84eb5ddc46c42385db7c703eb140f4e201cf37 Mon Sep 17 00:00:00 2001 From: angie Date: Fri, 15 Dec 2023 13:48:34 -0300 Subject: [PATCH 10/17] Start fixing CI --- .github/workflows/mypy.yml | 2 +- .github/workflows/tests.yml | 4 ++++ .github/workflows/tests_other_repo.yml | 4 ++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml index 029b278..d44b301 100644 --- a/.github/workflows/mypy.yml +++ b/.github/workflows/mypy.yml @@ -18,7 +18,7 @@ jobs: - name: Install Dependencies run: | - pip3 install mypy -U + pip3 install -U mypy - name: mypy run: mypy --show-column-numbers --hide-error-context . diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 36937c5..7c9fc6a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,6 +12,10 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 + - name: Install Dependencies + run: | + pip3 install -U maturin + - name: Install local ipl3checksum run: pip install . diff --git a/.github/workflows/tests_other_repo.yml b/.github/workflows/tests_other_repo.yml index 9beaa70..45710bd 100644 --- a/.github/workflows/tests_other_repo.yml +++ b/.github/workflows/tests_other_repo.yml @@ -17,6 +17,10 @@ jobs: with: ref: ${{ github.event.pull_request.head.sha }} + - name: Install Dependencies + run: | + pip3 install -U maturin + - name: Install local ipl3checksum run: pip install . From f6fba0e6c0eb226ed3d0c46dae5ad26f9cd583c3 Mon Sep 17 00:00:00 2001 From: angie Date: Fri, 15 Dec 2023 14:13:06 -0300 Subject: [PATCH 11/17] ci for rust --- .github/workflows/publish_crate.yml | 42 ++++++++++++++++++++++++++ .github/workflows/tests_other_repo.yml | 8 ++--- tests/check_recursive.py | 5 --- 3 files changed, 46 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/publish_crate.yml diff --git a/.github/workflows/publish_crate.yml b/.github/workflows/publish_crate.yml new file mode 100644 index 0000000..cfdb811 --- /dev/null +++ b/.github/workflows/publish_crate.yml @@ -0,0 +1,42 @@ +name: Build and upload Rust crate + +# Build on every branch push, tag push, and pull request change: +on: [push, pull_request] + +jobs: + build_rust: + name: Build Rust stuff and run Rust tests + runs-on: ubuntu-latest + + steps: + - name: Checkout reposistory + uses: actions/checkout@v4 + + - name: Setup Rust toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + + - name: Check format + run: cargo fmt --check + + - name: Setup clippy + run: rustup component add clippy + + - name: Run clippy + run: cargo clippy --all-targets --all-features -- -D warnings + + - name: Build Rust package + run: cargo build --release --workspace + + - name: Build Rust tests + run: cargo test --workspace + + - name: Publish dry run + if: github.event_name == 'push' && !startsWith(github.ref, 'refs/tags/') + run: cargo publish --dry-run + + - name: Upload crate + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') + run: cargo publish --token ${{ secrets.CARGO_REGISTRY_TOKEN }} diff --git a/.github/workflows/tests_other_repo.yml b/.github/workflows/tests_other_repo.yml index 45710bd..faf6470 100644 --- a/.github/workflows/tests_other_repo.yml +++ b/.github/workflows/tests_other_repo.yml @@ -4,8 +4,8 @@ name: Test other repo on: [push, pull_request_target] jobs: - build_repo: - name: Test other repo + test_other_repo_py: + name: Test other repo (Python) runs-on: ubuntu-latest strategy: @@ -19,10 +19,10 @@ jobs: - name: Install Dependencies run: | - pip3 install -U maturin + python3 -m pip install -U maturin - name: Install local ipl3checksum - run: pip install . + run: python3 -m pip install . - name: Get extra dependencies uses: actions/checkout@v4 diff --git a/tests/check_recursive.py b/tests/check_recursive.py index ebdbe36..4a43925 100755 --- a/tests/check_recursive.py +++ b/tests/check_recursive.py @@ -59,11 +59,6 @@ def recursePaths(folder: Path) -> int: errors += recursePaths(subpath) continue - if subpath.parts[-2] == "drmario64" and subpath.name == "baserom.cn.z64": - # iQue has a wrong checksum for some reason - print(f"Skipping {subpath}") - continue - romBytes = subpath.read_bytes() romMagic = struct.unpack_from(f">I", romBytes, 0x0)[0] From e478835c6e8b0c4ba584936b8668a6e6e68fd34b Mon Sep 17 00:00:00 2001 From: angie Date: Fri, 15 Dec 2023 14:15:00 -0300 Subject: [PATCH 12/17] cargo fmt --- src/rs/checksum.rs | 25 ++++++++++++++----------- src/rs/cickinds.rs | 2 +- src/rs/lib.rs | 15 ++++++++++----- src/rs/utils.rs | 4 ++-- 4 files changed, 27 insertions(+), 19 deletions(-) diff --git a/src/rs/checksum.rs b/src/rs/checksum.rs index 7dab6bc..a7542dd 100644 --- a/src/rs/checksum.rs +++ b/src/rs/checksum.rs @@ -2,7 +2,7 @@ /* SPDX-License-Identifier: MIT */ use crate::cickinds::CICKind; -use crate::{utils, detect}; +use crate::{detect, utils}; fn read_word_from_ram(rom_words: &[u32], entrypoint_ram: u32, ram_addr: u32) -> u32 { rom_words[((ram_addr - entrypoint_ram + 0x1000) / 4) as usize] @@ -26,14 +26,14 @@ pub fn calculate_checksum(rom_bytes: &[u8], kind: &CICKind) -> Option<(u32, u32) return None; } - let rom_words = utils::read_u32_vec(rom_bytes, 0, 0x101000/4); + let rom_words = utils::read_u32_vec(rom_bytes, 0, 0x101000 / 4); let seed = kind.get_seed(); let magic = kind.get_magic(); let mut s6 = seed; - let mut a0 = rom_words[8/4]; + let mut a0 = rom_words[8 / 4]; if *kind == CICKind::CIC_X103 { a0 = a0.wrapping_sub(0x100000); } @@ -94,7 +94,6 @@ pub fn calculate_checksum(rom_bytes: &[u8], kind: &CICKind) -> Option<(u32, u32) //t7 = utils.u32(t5 - v1) let t7: u32 = t5.wrapping_sub(v1); - //let t8 = utils.u32(v0 >> t7) //let t6 = utils.u32(v0 << v1) let t8 = v0.wrapping_shr(t7); @@ -116,12 +115,11 @@ pub fn calculate_checksum(rom_bytes: &[u8], kind: &CICKind) -> Option<(u32, u32) a2 = t9 ^ a2; // goto LA4000640; - // LA400063C: + // 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 @@ -157,7 +155,6 @@ pub fn calculate_checksum(rom_bytes: &[u8], kind: &CICKind) -> Option<(u32, u32) t4 = t7.wrapping_add(t4); } - // if (t0 != ra) goto LA40005F0; if t0 == ra { loop_variable = false; @@ -190,7 +187,7 @@ pub fn calculate_checksum(rom_bytes: &[u8], kind: &CICKind) -> Option<(u32, u32) s0 = t8 ^ t4; } - return Some((a3, s0)) + Some((a3, s0)) } pub fn calculate_checksum_autodetect(rom_bytes: &[u8]) -> Option<(u32, u32)> { @@ -243,7 +240,7 @@ mod tests { }; println!("CIC Kind: {:?}", kind); - for bin_path_result in fs::read_dir(ipl3_folder.path() ).unwrap() { + for bin_path_result in fs::read_dir(ipl3_folder.path()).unwrap() { let bin_path = bin_path_result.unwrap(); println!("{:?}", bin_path); @@ -255,12 +252,18 @@ mod tests { println!(" Calculating checksum..."); let checksum = super::calculate_checksum(&bin_bytes, &kind).unwrap(); - println!(" Calculated checksum is: 0x{:08X} 0x{:08X}", checksum.0, checksum.1); + println!( + " Calculated checksum is: 0x{:08X} 0x{:08X}", + checksum.0, checksum.1 + ); println!(" Checking checksum..."); let bin_checksum = utils::read_u32_vec(&bin_bytes, 0x10, 2); - println!(" Expected checksum is: 0x{:08X} 0x{:08X}", bin_checksum[0], bin_checksum[1]); + println!( + " Expected checksum is: 0x{:08X} 0x{:08X}", + bin_checksum[0], bin_checksum[1] + ); assert_eq!(checksum.0, bin_checksum[0]); assert_eq!(checksum.1, bin_checksum[1]); diff --git a/src/rs/cickinds.rs b/src/rs/cickinds.rs index 3ae1a2e..07ff445 100644 --- a/src/rs/cickinds.rs +++ b/src/rs/cickinds.rs @@ -71,7 +71,7 @@ impl CICKind { 6103 | 7103 => Some(CICKind::CIC_X103), 6105 | 7105 => Some(CICKind::CIC_X105), 6106 | 7106 => Some(CICKind::CIC_X106), - _ => None + _ => None, } } } diff --git a/src/rs/lib.rs b/src/rs/lib.rs index acec691..3612687 100644 --- a/src/rs/lib.rs +++ b/src/rs/lib.rs @@ -1,8 +1,8 @@ /* SPDX-FileCopyrightText: © 2023 Decompollaborate */ /* SPDX-License-Identifier: MIT */ -pub mod cickinds; pub mod checksum; +pub mod cickinds; pub mod detect; mod utils; @@ -14,13 +14,18 @@ use pyo3::prelude::*; #[pymodule] fn ipl3checksum(_py: Python<'_>, m: &PyModule) -> PyResult<()> { m.add_class::()?; - m.add_function(wrap_pyfunction!(checksum::python_bindings::calculateChecksum, m)?)?; - m.add_function(wrap_pyfunction!(checksum::python_bindings::calculateChecksumAutodetect, m)?)?; + m.add_function(wrap_pyfunction!( + checksum::python_bindings::calculateChecksum, + m + )?)?; + m.add_function(wrap_pyfunction!( + checksum::python_bindings::calculateChecksumAutodetect, + m + )?)?; m.add_function(wrap_pyfunction!(detect::python_bindings::detectCICRaw, m)?)?; m.add_function(wrap_pyfunction!(detect::python_bindings::detectCIC, m)?)?; Ok(()) } #[cfg(test)] -mod tests { -} +mod tests {} diff --git a/src/rs/utils.rs b/src/rs/utils.rs index 48ef42d..8ef06e2 100644 --- a/src/rs/utils.rs +++ b/src/rs/utils.rs @@ -23,10 +23,10 @@ pub(crate) fn read_u32(bytes: &[u8], offset: usize) -> u32 { } pub(crate) fn read_u32_vec(bytes: &[u8], offset: usize, len: usize) -> Vec { - let mut ret = vec![0;len]; + let mut ret = vec![0; len]; for i in 0..len { - ret[i] = read_u32(bytes, offset + i*4); + ret[i] = read_u32(bytes, offset + i * 4); } ret From 4e2273b7024589504b26cad8501f611bb7c0cb51 Mon Sep 17 00:00:00 2001 From: angie Date: Fri, 15 Dec 2023 14:18:58 -0300 Subject: [PATCH 13/17] clippy --- src/rs/checksum.rs | 10 +++++----- src/rs/utils.rs | 6 ++---- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/rs/checksum.rs b/src/rs/checksum.rs index a7542dd..64c1077 100644 --- a/src/rs/checksum.rs +++ b/src/rs/checksum.rs @@ -104,7 +104,7 @@ pub fn calculate_checksum(rom_bytes: &[u8], kind: &CICKind) -> Option<(u32, u32) at = if a2 < v0 { 1 } else { 0 }; a3 = a1; - t3 = t3 ^ v0; + t3 ^= v0; //s0 = utils.u32(s0 + a0) s0 = s0.wrapping_add(a0); @@ -112,12 +112,12 @@ pub fn calculate_checksum(rom_bytes: &[u8], kind: &CICKind) -> Option<(u32, u32) if at != 0 { let t9 = a3 ^ v0; - a2 = t9 ^ a2; + a2 ^= t9; // goto LA4000640; // LA400063C: } else { - a2 = a2 ^ a0; + a2 ^= a0; } // LA4000640: @@ -130,7 +130,7 @@ pub fn calculate_checksum(rom_bytes: &[u8], kind: &CICKind) -> Option<(u32, u32) t0 = t0.wrapping_add(0x4); s6 = s6.wrapping_add(0x4); - t7 = v0 ^ t7; + t7 ^= v0; // t4 = utils.u32(t7 + t4); t4 = t7.wrapping_add(t4); @@ -141,7 +141,7 @@ pub fn calculate_checksum(rom_bytes: &[u8], kind: &CICKind) -> Option<(u32, u32) t1 = t1.wrapping_add(0x4); // s6 = utils.u32(s6 & t7); - s6 = s6 & t7; + s6 &= t7; } else { // t0 = utils.u32(t0 + 0x4); t0 = t0.wrapping_add(0x4); diff --git a/src/rs/utils.rs b/src/rs/utils.rs index 8ef06e2..6ab525a 100644 --- a/src/rs/utils.rs +++ b/src/rs/utils.rs @@ -1,8 +1,6 @@ /* SPDX-FileCopyrightText: © 2023 Decompollaborate */ /* SPDX-License-Identifier: MIT */ -use md5; - pub(crate) fn read_u32(bytes: &[u8], offset: usize) -> u32 { if offset % 4 != 0 { panic!("Unaligned read"); @@ -23,10 +21,10 @@ pub(crate) fn read_u32(bytes: &[u8], offset: usize) -> u32 { } pub(crate) fn read_u32_vec(bytes: &[u8], offset: usize, len: usize) -> Vec { - let mut ret = vec![0; len]; + let mut ret = Vec::with_capacity(len); for i in 0..len { - ret[i] = read_u32(bytes, offset + i * 4); + ret.push(read_u32(bytes, offset + i * 4)); } ret From aaf3d4c514bdae60f18e340f749768fa3c033934 Mon Sep 17 00:00:00 2001 From: angie Date: Fri, 15 Dec 2023 14:44:53 -0300 Subject: [PATCH 14/17] changelog and readme --- CHANGELOG.md | 13 +++++++++++ README.md | 56 +++++++++++++++++++++++++++++++++++++--------- src/rs/checksum.rs | 4 ---- 3 files changed, 58 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a837fc..1bf82d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add Rust support +- New static method `CICKind.fromHashMd5`. + - Returns a CIC kind based on the passed md5 hash. + +### Changed + +- Library was reimplemented in Rust, allowing faster runtime calculation. + - The Python API is still the same + +### Fixed + - Fix links in `CHANGELOG.md` ## [1.0.1] - 2023-09-21 diff --git a/README.md b/README.md index 127d633..d668f55 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,10 @@ -# ipl3checksum ![PyPI - Downloads] ![GitHub License] ![GitHub release (latest SemVer)] ![PyPI] ![GitHub contributors] +# ipl3checksum + +![PyPI - Downloads] +![GitHub License] +![GitHub release (latest SemVer)] +![PyPI] +![GitHub contributors] [PyPI - Downloads]: [GitHub License]: @@ -6,7 +12,7 @@ [PyPI]: [GitHub contributors]: -A Python library to calculate the IPL3 checksum for N64 ROMs. +A Python and Rust library to calculate the IPL3 checksum for N64 ROMs. ## How to use it? @@ -17,7 +23,10 @@ romBytes = # A big endian bytes-like object cickind = ipl3checksum.CICKind.CIC_6102_7101 checksum = ipl3checksum.calculateChecksum(romBytes, cickind) -assert checksum is not None # Not able to compute the checksum, probably because rom was too small + +# If this assert fails it is because the library was not able to compute the +# checksum, probably because the passed rom was too small +assert checksum is not None print(f"{checksum[0]:08X}") print(f"{checksum[1]:08X}") @@ -27,7 +36,8 @@ This library also contains a CIC detector: ```py cickind = ipl3checksum.detectCIC(romBytes) -print(cickind) # Either a `ipl3checksum.CICKind` or None if was not able to detect the CIC +# Either a `ipl3checksum.CICKind` or None if was not able to detect the CIC +print(cickind) ``` ## Features @@ -36,6 +46,7 @@ print(cickind) # Either a `ipl3checksum.CICKind` or None if was not able to dete - Can calculate the checksum of a ROM using the algorithm of any of the supported CIC variants. - Can detect any of the supported CIC variants. +- Fast calculation written in Rust. ### Restrictions/requirements @@ -44,10 +55,13 @@ supported CIC variants. - Since the checksum algorithm is calculated on the first MiB after IPL3 (from `0x1000` to `0x101000`), then the library expects the passed ROM to be at least `0x101000` bytes long, otherwise the library will reject the ROM. - - If it is not the case, then pad your ROM with zeroes to that size. + - If your ROM is not big enough then it is suggested then pad your ROM with + zeroes until it reaches that size. ## Installing +### Python version + First you need to install the library, one way of doing it is via `pip`. ```bash @@ -58,21 +72,25 @@ If you use a `requirements.txt` file in your repository, then you can add this library with the following line: ```txt -ipl3checksum>=1.0.0,<2.0.0 +ipl3checksum>=1.1.0,<2.0.0 `````` Now you can invoke the library from your script. -### Development version +#### Development version -The unstable development version is located at the [develop](https://github.com/Decompollaborate/ipl3checksum/tree/develop) +The unstable development version is located at the +[develop](https://github.com/Decompollaborate/ipl3checksum/tree/develop) branch. PRs should be made into that branch instead of the main one. -The recommended way to install a locally cloned repo is by passing the `-e` -(editable) flag to `pip`. +Since this library uses Rust code then you'll need a Rust compiler installed +on your system. To build the Python bindings you'll also need `maturin` +installed via `pip`. + +The recommended way to install a locally cloned repo the following. ```bash -python3 -m pip install -e . +python3 -m pip install . ``` In case you want to mess with the latest development version without wanting to @@ -86,6 +104,22 @@ python3 -m pip install git+https://github.com/Decompollaborate/ipl3checksum.git@ NOTE: Installing the development version is not recommended unless you know what you are doing. Proceed at your own risk. +### Rust version + +See this crate at . + +To add this library to your project using Cargo: + +```bash +cargo add ipl3checksum +``` + +Or add the following line manually to your `Cargo.toml` file: + +```toml +ipl3checksum = "1.1.0" +``` + ## Versioning and changelog This library follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). diff --git a/src/rs/checksum.rs b/src/rs/checksum.rs index 64c1077..daf39cd 100644 --- a/src/rs/checksum.rs +++ b/src/rs/checksum.rs @@ -212,11 +212,7 @@ pub fn calculate_checksum_autodetect(rom_bytes: &[u8]) -> Option<(u32, u32)> { #[cfg(test)] mod tests { - //use rstest::rstest; - //use std::path::PathBuf; - use std::fs; - use crate::{cickinds::CICKind, utils}; #[test] From a5cb2bdf3b47c8a8bda23e4467add2d08405251f Mon Sep 17 00:00:00 2001 From: angie Date: Fri, 15 Dec 2023 15:22:13 -0300 Subject: [PATCH 15/17] some docs --- src/rs/checksum.rs | 75 +++++++++++++++++++++++++++++----------------- src/rs/cickinds.rs | 1 + src/rs/detect.rs | 43 +++++++++++++------------- src/rs/lib.rs | 10 +++++-- 4 files changed, 77 insertions(+), 52 deletions(-) diff --git a/src/rs/checksum.rs b/src/rs/checksum.rs index daf39cd..f2387c1 100644 --- a/src/rs/checksum.rs +++ b/src/rs/checksum.rs @@ -8,20 +8,30 @@ fn read_word_from_ram(rom_words: &[u32], entrypoint_ram: u32, ram_addr: u32) -> rom_words[((ram_addr - entrypoint_ram + 0x1000) / 4) as usize] } +/// Calculates the checksum required by an official CIC of a N64 ROM. +/// +/// ## Arguments +/// +/// * `rom_bytes` - The bytes of the N64 ROM in big endian format. It must have a minimum size of 0x101000 bytes. +/// * `kind` - The CIC kind variation used to calculate the checksum. +/// +/// ## Return +/// +/// * 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: +/// - `rom_bytes` not being big enough +/// +/// ## Examples +/// +/// ``` +/// use ipl3checksum; +/// let bytes = vec![0; 0x101000]; +/// let kind = ipl3checksum::CICKind::CIC_6102_7101; +/// let checksum = ipl3checksum::calculate_checksum(&bytes, &kind).unwrap(); +/// println!("{:08X} {:08X}", checksum.0, checksum.1); +/// ``` pub fn calculate_checksum(rom_bytes: &[u8], kind: &CICKind) -> Option<(u32, u32)> { - /* - Calculates the checksum required by an official CIC of a N64 ROM. - - Args: - rom_bytes (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: - - `rom_bytes` not being big enough - */ - if rom_bytes.len() < 0x101000 { return None; } @@ -190,21 +200,32 @@ pub fn calculate_checksum(rom_bytes: &[u8], kind: &CICKind) -> Option<(u32, u32) Some((a3, s0)) } +/// 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`. +/// +/// ## Arguments +/// +/// * `rom_bytes` - The bytes of the N64 ROM in big endian format. It must have a minimum size of 0x101000 bytes. +/// +/// ## Return +/// +/// * 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: +/// - `rom_bytes` not being big enough +/// - Not able to detect the CIC kind +/// +/// ## Examples +/// +/// ``` +/// use ipl3checksum; +/// let bytes = vec![0; 0x101000]; +/// let checksum = ipl3checksum::calculate_checksum_autodetect(&bytes); +/// /* This will return `None` because there's no ipl3 binary on an array of zeroes */ +/// assert!(checksum.is_none()); +/// ``` pub fn calculate_checksum_autodetect(rom_bytes: &[u8]) -> Option<(u32, u32)> { - /*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: - rom_bytes (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: - - `rom_bytes` not being big enough - - Not able to detect the CIC kind - */ - let kind = detect::detect_cic(rom_bytes)?; calculate_checksum(rom_bytes, &kind) diff --git a/src/rs/cickinds.rs b/src/rs/cickinds.rs index 07ff445..b2f4648 100644 --- a/src/rs/cickinds.rs +++ b/src/rs/cickinds.rs @@ -7,6 +7,7 @@ use pyo3::prelude::*; #[cfg_attr(feature = "python_bindings", pyclass(module = "ipl3checksum"))] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] #[allow(non_camel_case_types)] +/// Enum that represents a CIC kind pub enum CICKind { CIC_6101, CIC_6102_7101, diff --git a/src/rs/detect.rs b/src/rs/detect.rs index 9c8adce..8dfeb01 100644 --- a/src/rs/detect.rs +++ b/src/rs/detect.rs @@ -4,18 +4,17 @@ use crate::cickinds::CICKind; use crate::utils; +/// Tries to detect an IPL3 binary. +/// +/// The argument to this function must be exactly the IPL3 binary, stripping the rest of the ROM. +/// +/// ## Arguments +/// +/// * `raw_bytes` - IPL3 binary in big endian format. +/// +/// ## Return +/// * The detected CIC kind, or `None` if was not able to detect the CIC kind. pub fn detect_cic_raw(raw_bytes: &[u8]) -> Option { - /*Tries to detect an IPL3 binary. - - The argument to this function must be exactly the IPL3 binary, stripping the rest of the ROM. - - Args: - raw_bytes (bytes): IPL3 binary in big endian format. - - Returns: - CICKind|None: The detected CIC kind, or `None` if was not able to detect the CIC kind. - */ - if raw_bytes.len() != 0xFC0 { return None; } @@ -25,18 +24,18 @@ pub fn detect_cic_raw(raw_bytes: &[u8]) -> Option { CICKind::from_hash_md5(&bytes_hash) } +/// Tries to detect an IPL3 in a ROM. +/// +/// The argument to this function must be a ROM in big endian format. +/// +/// ## Arguments +/// +/// * `rom_bytes` - ROM binary in big endian format. +/// +/// ## Return +/// +/// * The detected CIC kind, or `None` if was not able to detect the CIC kind. pub fn detect_cic(rom_bytes: &[u8]) -> Option { - /*Tries to detect an IPL3 in a ROM. - - The argument to this function must be a ROM in big endian format. - - Args: - rom_bytes (bytes): ROMbinary in big endian format. - - Returns: - CICKind|None: The detected CIC kind, or `None` if was not able to detect the CIC kind. - */ - detect_cic_raw(&rom_bytes[0x40..0x1000]) } diff --git a/src/rs/lib.rs b/src/rs/lib.rs index 3612687..223c953 100644 --- a/src/rs/lib.rs +++ b/src/rs/lib.rs @@ -1,12 +1,16 @@ /* SPDX-FileCopyrightText: © 2023 Decompollaborate */ /* SPDX-License-Identifier: MIT */ -pub mod checksum; -pub mod cickinds; -pub mod detect; +mod checksum; +mod cickinds; +mod detect; mod utils; +pub use checksum::*; +pub use cickinds::*; +pub use detect::*; + #[cfg(feature = "python_bindings")] use pyo3::prelude::*; From c5f207cbb2a04797195b0a9372fb348e1dc5e03d Mon Sep 17 00:00:00 2001 From: angie Date: Fri, 15 Dec 2023 15:22:30 -0300 Subject: [PATCH 16/17] fmt --- src/rs/checksum.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rs/checksum.rs b/src/rs/checksum.rs index f2387c1..8f68cd5 100644 --- a/src/rs/checksum.rs +++ b/src/rs/checksum.rs @@ -233,8 +233,8 @@ pub fn calculate_checksum_autodetect(rom_bytes: &[u8]) -> Option<(u32, u32)> { #[cfg(test)] mod tests { - use std::fs; use crate::{cickinds::CICKind, utils}; + use std::fs; #[test] fn test_dummy_files() -> Result<(), ()> { From a256411fdeb89af5b0bbaff7b4d90e960cd2962f Mon Sep 17 00:00:00 2001 From: angie Date: Fri, 15 Dec 2023 15:36:07 -0300 Subject: [PATCH 17/17] fix ci? --- .github/workflows/tests_other_repo.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/tests_other_repo.yml b/.github/workflows/tests_other_repo.yml index faf6470..3bf6d4f 100644 --- a/.github/workflows/tests_other_repo.yml +++ b/.github/workflows/tests_other_repo.yml @@ -1,7 +1,7 @@ name: Test other repo # Build on every branch push, tag push, and pull request change: -on: [push, pull_request_target] +on: [push, pull_request] jobs: test_other_repo_py: @@ -14,8 +14,6 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha }} - name: Install Dependencies run: |