├── .github └── workflows │ └── rust_build.yml ├── .gitignore ├── .vscode └── settings.json ├── Cargo.toml ├── LICENSE ├── README.md ├── link.ld ├── rust-toolchain └── src ├── acmd.rs ├── callbacks.rs ├── hooks.rs ├── lib.rs ├── loader.rs ├── nro_hook.rs ├── nx.rs ├── rtld.rs ├── scripts.rs ├── status.rs └── unwind.rs /.github/workflows/rust_build.yml: -------------------------------------------------------------------------------- 1 | name: Rust Build 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | jobs: 7 | build: 8 | 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Install cargo-skyline 14 | run: cargo install --git https://github.com/jam1garner/cargo-skyline 15 | - name: Install rust-std-skyline-squashed 16 | run: cd .. && git clone https://github.com/jam1garner/rust-std-skyline-squashed && cd - 17 | - name: Attempt to build 18 | run: PATH=$PATH:/usr/share/rust/.rustup/toolchains/nightly-2020-04-10-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/bin cargo skyline build --release 19 | # uncomment the build step below to build on every change to master. 20 | # - name: Update Release 21 | # env: 22 | # PLUGIN_NAME: 'libsmashline_hook.nro' # change this to the name of your plugin 23 | # uses: meeDamian/github-release@2.0 24 | # with: 25 | # token: ${{ secrets.GITHUB_TOKEN }} 26 | # prerelease: true 27 | # allow_override: true 28 | # gzip: false 29 | # tag: master 30 | # name: master 31 | # body: Build of ${{env.PLUGIN_NAME}} from master. 32 | # files: > 33 | # ./target/aarch64-skyline-switch/release/${{env.PLUGIN_NAME}} 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/target 2 | **/*.py 3 | **/*.pyc 4 | Cargo.lock 5 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.checkOnSave.overrideCommand": [ 3 | "xargo", 4 | "check", 5 | "--workspace", 6 | "--message-format=json", 7 | "--all-features" 8 | ], 9 | "rust-analyzer.updates.channel": "nightly", 10 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "smashline_hook" 3 | version = "1.0.0" 4 | authors = ["blu-dev "] 5 | edition = "2018" 6 | 7 | [package.metadata.skyline] 8 | titleid = "01006A800016E000" 9 | 10 | [lib] 11 | crate-type = ["cdylib"] 12 | 13 | [dependencies] 14 | skyline = { version = "0.2" } 15 | # skyline_smash = { path = "../skyline-smash", features = ["weak_l2cvalue"] } 16 | # skyline_smash = { git = "https://github.com/blu-dev/skyline-smash.git", branch = "l2c-values", features = ["weak_l2cvalue"] } 17 | skyline_smash = { git = "https://github.com/blu-dev/skyline-smash.git", features = ["weak_l2cvalue"] } 18 | nnsdk = { git = "https://github.com/ultimate-research/nnsdk-rs" } 19 | parking_lot = { version = "0.11.2" } 20 | lazy_static = "1.4.0" 21 | bitflags = "1.2.1" 22 | aarch64-decode = { git = "https://github.com/jam1garner/aarch64-decode.git", rev = "99475256e779677df456b3ec37de258d1697b97d" } 23 | paste = "1.0.5" 24 | 25 | [profile.dev] 26 | panic = "abort" 27 | 28 | [profile.release] 29 | panic = "abort" 30 | lto = true 31 | 32 | [features] 33 | standalone = [] 34 | development = ["standalone"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # smashline_hook 2 | This is the plugin associated with [smashline](https://github.com/blu-dev/smashline) 3 | 4 | If you would like to read about how to use it, look there. -------------------------------------------------------------------------------- /link.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_FORMAT(elf64-littleaarch64) 2 | OUTPUT_ARCH(aarch64) 3 | ENTRY(_start) 4 | 5 | PHDRS 6 | { 7 | text PT_LOAD FLAGS(5); 8 | rodata PT_LOAD FLAGS(4); 9 | data PT_LOAD FLAGS(6); 10 | bss PT_LOAD FLAGS(6); 11 | dynamic PT_DYNAMIC; 12 | } 13 | 14 | SECTIONS 15 | { 16 | . = 0; 17 | 18 | .text : ALIGN(0x1000) { 19 | HIDDEN(__text_start = .); 20 | *(.nro_header) 21 | KEEP(*(.text.jmp)) 22 | 23 | . = 0x80; 24 | 25 | *(.text .text.*) 26 | *(.plt .plt.*) 27 | HIDDEN(__text_end = .); 28 | } :text 29 | 30 | /* Read-only sections */ 31 | 32 | 33 | . = ALIGN(0x1000); 34 | .module_name : { KEEP (*(.rodata.module_name)) } :rodata 35 | 36 | .hooks : { 37 | __hook_array_start = .; 38 | *(.rodata.hooks) 39 | __hook_array_end = .; 40 | } :rodata 41 | 42 | .rodata : { 43 | *(.rodata .rodata.*) 44 | } :rodata 45 | 46 | .mod0 : { 47 | KEEP(crt0.nso.o(.data.mod0)) 48 | KEEP(crt0.nro.o(.data.mod0)) 49 | KEEP(crt0.lib.nro.o(.data.mod0)) 50 | } 51 | .hash : { *(.hash) } 52 | .dynsym : { *(.dynsym .dynsym.*) } 53 | .dynstr : { *(.dynstr .dynstr.*) } 54 | .rela.dyn : { *(.rela.dyn) } 55 | 56 | .eh_frame : ONLY_IF_RO { 57 | HIDDEN(__eh_frame_start = .); 58 | KEEP (*(.eh_frame)) *(.eh_frame.*) 59 | HIDDEN(__eh_frame_end = .); 60 | } 61 | 62 | .eh_frame_hdr : { 63 | HIDDEN(__eh_frame_hdr_start = .); 64 | *(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*) 65 | HIDDEN(__eh_frame_hdr_end = .); 66 | } 67 | .gcc_except_table : ONLY_IF_RO { *(.gcc_except_table .gcc_except_table.*) } 68 | .gnu_extab : ONLY_IF_RO { *(.gnu_extab*) } 69 | 70 | /* Read-write sections */ 71 | 72 | . = ALIGN(0x1000); 73 | 74 | .data : { 75 | *(.data .data.*) 76 | *(.got .got.*) 77 | *(.got.plt .got.plt.*) 78 | } :data 79 | 80 | .eh_frame : ONLY_IF_RW { 81 | HIDDEN(__eh_frame_start = .); 82 | KEEP (*(.eh_frame)) *(.eh_frame.*) 83 | HIDDEN(__eh_frame_end = .); 84 | } 85 | .gnu_extab : ONLY_IF_RW { *(.gnu_extab*) } 86 | .gcc_except_table : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) } 87 | 88 | .dynamic : { 89 | HIDDEN(__dynamic_start = .); 90 | *(.dynamic) 91 | } 92 | 93 | /* BSS section */ 94 | 95 | . = ALIGN(0x1000); 96 | 97 | .bss : { 98 | HIDDEN(__bss_start = .); 99 | *(.bss .bss.*) 100 | *(COMMON) 101 | HIDDEN(__nx_module_runtime = .); 102 | *(.bss.module_runtime) 103 | . = ALIGN(8); 104 | HIDDEN(__bss_end = .); 105 | } :bss 106 | } 107 | -------------------------------------------------------------------------------- /rust-toolchain: -------------------------------------------------------------------------------- 1 | stable -------------------------------------------------------------------------------- /src/acmd.rs: -------------------------------------------------------------------------------- 1 | use skyline::nro::NroInfo; 2 | use smash::phx::Hash40; 3 | use parking_lot::Mutex; 4 | use std::collections::HashMap; 5 | use std::hash::{Hash, Hasher}; 6 | 7 | #[derive(PartialEq, Clone, Copy)] 8 | pub enum Category { 9 | ACMD_GAME, 10 | ACMD_EFFECT, 11 | ACMD_SOUND, 12 | ACMD_EXPRESSION 13 | } 14 | 15 | pub struct ScriptInfo { 16 | pub script: Hash40, 17 | pub original: Option<&'static mut *const extern "C" fn()>, 18 | pub low_priority: bool, 19 | pub bind_fn: *const extern "C" fn(), 20 | pub backup: *const extern "C" fn() // serves same purpose as `original` except for guaranteeing something on uninstallation 21 | } 22 | 23 | impl ScriptInfo { 24 | pub fn transfer(&mut self) -> Self { 25 | Self { 26 | script: self.script, 27 | original: self.original.take(), 28 | low_priority: self.low_priority, 29 | bind_fn: self.bind_fn, 30 | backup: self.backup 31 | } 32 | } 33 | } 34 | 35 | impl PartialEq for ScriptInfo { 36 | fn eq(&self, other: &Hash40) -> bool { 37 | self.script == *other 38 | } 39 | } 40 | 41 | impl PartialEq for ScriptInfo { 42 | fn eq(&self, other: &ScriptInfo) -> bool { 43 | self.script == other.script 44 | } 45 | } 46 | 47 | impl Eq for ScriptInfo {} 48 | 49 | impl Hash for ScriptInfo { 50 | fn hash(&self, state: &mut H) { 51 | self.script.hash(state); 52 | } 53 | } 54 | 55 | unsafe impl Sync for ScriptInfo {} 56 | unsafe impl Send for ScriptInfo {} 57 | 58 | lazy_static! { 59 | pub static ref GAME_SCRIPTS: Mutex>> = Mutex::new(HashMap::new()); 60 | pub static ref EFFECT_SCRIPTS: Mutex>> = Mutex::new(HashMap::new()); 61 | pub static ref SOUND_SCRIPTS: Mutex>> = Mutex::new(HashMap::new()); 62 | pub static ref EXPRESSION_SCRIPTS: Mutex>> = Mutex::new(HashMap::new()); 63 | } 64 | 65 | pub fn nro_load(info: &NroInfo) { 66 | static CATEGORIES: &[Category] = &[ 67 | Category::ACMD_GAME, 68 | Category::ACMD_EFFECT, 69 | Category::ACMD_SOUND, 70 | Category::ACMD_EXPRESSION 71 | ]; 72 | match info.name { 73 | "common" | "item" | "" => {}, 74 | _ => { 75 | for cat in CATEGORIES.iter() { 76 | crate::scripts::patch_create_agent_animcmd(info, *cat); 77 | } 78 | } 79 | } 80 | } 81 | 82 | pub fn nro_unload(info: &NroInfo) { 83 | 84 | } 85 | 86 | pub unsafe fn remove_acmd_scripts(range: (usize, usize)) { 87 | crate::scripts::remove_live_acmd_scripts(range); 88 | 89 | let locks = &mut [ 90 | GAME_SCRIPTS.lock(), 91 | EFFECT_SCRIPTS.lock(), 92 | SOUND_SCRIPTS.lock(), 93 | EXPRESSION_SCRIPTS.lock() 94 | ]; 95 | 96 | let (begin, end) = range; 97 | 98 | for scripts in locks.iter_mut() { 99 | for (_, script_list) in scripts.iter_mut() { 100 | let mut new_vec = Vec::with_capacity(script_list.len()); 101 | for script in script_list.iter_mut() { 102 | let as_usize = script.bind_fn as *const () as usize; 103 | if !(begin <= as_usize && as_usize < end) { 104 | new_vec.push(script.transfer()); 105 | } 106 | } 107 | *script_list = new_vec; 108 | } 109 | } 110 | } 111 | 112 | #[no_mangle] 113 | pub extern "Rust" fn replace_acmd_script(agent: Hash40, script: Hash40, original: Option<&'static mut *const extern "C" fn()>, category: Category, low_priority: bool, bind_fn: *const extern "C" fn()) { 114 | crate::unwind::register_skyline_plugin(bind_fn as usize); 115 | 116 | let mut info = ScriptInfo { script, original, low_priority, bind_fn, backup: 0 as _ }; 117 | 118 | unsafe { 119 | crate::scripts::install_live_acmd_scripts(agent, category, &mut info); 120 | } 121 | 122 | let mut map = match category { 123 | Category::ACMD_GAME => GAME_SCRIPTS.lock(), 124 | Category::ACMD_EFFECT => EFFECT_SCRIPTS.lock(), 125 | Category::ACMD_SOUND => SOUND_SCRIPTS.lock(), 126 | Category::ACMD_EXPRESSION => EXPRESSION_SCRIPTS.lock() 127 | }; 128 | 129 | if let Some(script_list) = map.get_mut(&agent) { 130 | for script_info in script_list.iter_mut() { 131 | if script_info.script == info.script { 132 | if script_info.low_priority { 133 | *script_info = info; 134 | } else { 135 | println!("[smashline::acmd] ACMD script already replaced with high priority | Agent: {:#x}, Script: {:#x}", agent.hash, info.script.hash); 136 | } 137 | return; 138 | } 139 | } 140 | script_list.push(info); 141 | } else { 142 | map.insert(agent, vec![info]); 143 | } 144 | } -------------------------------------------------------------------------------- /src/callbacks.rs: -------------------------------------------------------------------------------- 1 | use smash::lua2cpp::*; 2 | use smash::lib::{LuaConst, L2CValue, lua_const::{BATTLE_OBJECT_CATEGORY_FIGHTER, BATTLE_OBJECT_CATEGORY_WEAPON}}; 3 | use smash::phx::Hash40; 4 | use smash::app::lua_bind::StatusModule; 5 | 6 | use skyline::nro::NroInfo; 7 | 8 | use crate::LuaConstant; 9 | use crate::c_str; 10 | 11 | use std::collections::HashMap; 12 | use parking_lot::Mutex; 13 | 14 | type FighterFrame = extern "C" fn(&mut L2CFighterCommon) -> L2CValue; 15 | type AgentFrame = extern "C" fn(&mut L2CFighterBase) -> L2CValue; 16 | type FighterFrameCallback = fn(&mut L2CFighterCommon); 17 | type AgentFrameCallback = fn(&mut L2CFighterBase); 18 | type FighterReset = fn(&mut L2CFighterCommon); 19 | type AgentReset = fn(&mut L2CFighterBase); 20 | type FighterInit = fn(&mut L2CFighterCommon); 21 | type AgentInit = fn(&mut L2CFighterBase); 22 | 23 | struct FighterFrameInfo { 24 | pub agent: LuaConstant, 25 | pub original: Option<&'static mut *const extern "C" fn()>, 26 | pub frame: FighterFrame 27 | } 28 | 29 | unsafe impl Sync for FighterFrameInfo {} 30 | unsafe impl Send for FighterFrameInfo {} 31 | 32 | struct AgentFrameInfo { 33 | pub agent: LuaConstant, 34 | pub original: Option<&'static mut *const extern "C" fn()>, 35 | pub frame: AgentFrame 36 | } 37 | 38 | unsafe impl Sync for AgentFrameInfo {} 39 | unsafe impl Send for AgentFrameInfo {} 40 | 41 | struct AgentFrameMainInfo { 42 | pub agent: LuaConstant, 43 | pub is_fighter: bool, 44 | pub original: Option<&'static mut *const extern "C" fn()>, 45 | pub frame: AgentFrame 46 | } 47 | 48 | unsafe impl Sync for AgentFrameMainInfo {} 49 | unsafe impl Send for AgentFrameMainInfo {} 50 | 51 | lazy_static! { 52 | static ref FIGHTER_FRAMES: Mutex> = Mutex::new(Vec::new()); 53 | static ref WEAPON_FRAMES: Mutex> = Mutex::new(Vec::new()); 54 | static ref AGENT_FRAMES_MAIN: Mutex> = Mutex::new(Vec::new()); 55 | 56 | static ref FIGHTER_RESETS: Mutex> = Mutex::new(Vec::new()); 57 | static ref AGENT_RESETS: Mutex> = Mutex::new(Vec::new()); 58 | 59 | static ref FIGHTER_FRAME_CALLBACKS: Mutex> = Mutex::new(Vec::new()); 60 | static ref WEAPON_FRAME_CALLBACKS: Mutex> = Mutex::new(Vec::new()); 61 | static ref AGENT_FRAME_MAIN_CALLBACKS: Mutex> = Mutex::new(Vec::new()); 62 | 63 | static ref FIGHTER_INIT_CALLBACKS: Mutex> = Mutex::new(Vec::new()); 64 | static ref AGENT_INIT_CALLBACKS: Mutex> = Mutex::new(Vec::new()); 65 | } 66 | 67 | static mut SHOULD_INSTALL_FIGHTER_CB: bool = false; 68 | static mut SHOULD_INSTALL_WEAPON_CB: bool = false; 69 | static mut SHOULD_INSTALL_AGENT_MAIN_CB: bool = false; 70 | 71 | // These symbols must be used since they are passed va_lists 72 | // call_check_attack, for example, does not take a va_list 73 | extern "C" { 74 | #[link_name = "\u{1}_ZN7lua2cpp16L2CFighterCommon32bind_hash_call_call_check_damageEPN3lib8L2CAgentERNS1_7utility8VariadicEPKcSt9__va_list"] 75 | fn call_check_damage(); 76 | #[link_name = "\u{1}_ZN7lua2cpp16L2CFighterCommon32bind_hash_call_call_check_attackEPN3lib8L2CAgentERNS1_7utility8VariadicEPKcSt9__va_list"] 77 | fn call_check_attack(); 78 | #[link_name = "\u{1}_ZN7lua2cpp16L2CFighterCommon32bind_hash_call_call_on_change_lrEPN3lib8L2CAgentERNS1_7utility8VariadicEPKcSt9__va_list"] 79 | fn call_on_change_lr(); 80 | #[link_name = "\u{1}_ZN7lua2cpp16L2CFighterCommon30bind_hash_call_call_leave_stopEPN3lib8L2CAgentERNS1_7utility8VariadicEPKcSt9__va_list"] 81 | fn call_leave_stop(); 82 | #[link_name = "\u{1}_ZN7lua2cpp16L2CFighterCommon40bind_hash_call_call_notify_event_gimmickEPN3lib8L2CAgentERNS1_7utility8VariadicEPKcSt9__va_list"] 83 | fn call_notify_event_gimmick(); 84 | #[link_name = "\u{1}_ZN7lua2cpp16L2CFighterCommon30bind_hash_call_call_calc_paramEPN3lib8L2CAgentERNS1_7utility8VariadicEPKcSt9__va_list"] 85 | fn call_calc_param(); 86 | } 87 | 88 | #[skyline::hook(replace = L2CFighterCommon_sys_line_system_init)] 89 | unsafe extern "C" fn sys_line_system_fighter_init_replace(fighter: &mut L2CFighterCommon) -> L2CValue { 90 | use std::mem::transmute; 91 | 92 | for callback in FIGHTER_INIT_CALLBACKS.lock().iter() { 93 | callback(fighter); 94 | } 95 | 96 | fighter.sv_set_function_hash(transmute(call_check_damage as *const ()), Hash40::new("call_check_damage")); 97 | fighter.sv_set_function_hash(transmute(call_check_attack as *const ()), Hash40::new("call_check_attack")); 98 | fighter.sv_set_function_hash(transmute(call_on_change_lr as *const ()), Hash40::new("call_on_change_lr")); 99 | fighter.sv_set_function_hash(transmute(call_leave_stop as *const ()), Hash40::new("call_leave_stop")); 100 | fighter.sv_set_function_hash(transmute(call_notify_event_gimmick as *const ()), Hash40::new("call_notify_event_gimmick")); 101 | fighter.sv_set_function_hash(transmute(call_calc_param as *const ()), Hash40::new("call_calc_param")); 102 | 103 | // already been resolved by this point, so we have to do it this way unfortunately :( 104 | let mut sys_line_system_control = 0usize; 105 | skyline::nn::ro::LookupSymbol(&mut sys_line_system_control, c_str!("_ZN7lua2cpp16L2CFighterCommon31sys_line_system_control_fighterEv")); 106 | 107 | let mut system_control = L2CValue::Ptr(transmute(sys_line_system_control as *const ())); 108 | let mut fighter_frames = FIGHTER_FRAMES.lock(); 109 | for frame_info in fighter_frames.iter_mut() { 110 | if frame_info.agent.get() == smash::app::utility::get_kind(&mut *fighter.module_accessor) { 111 | if let Some(original) = frame_info.original.as_mut() { 112 | let og = system_control.get_ptr() as *const extern "C" fn(); 113 | **original = og; 114 | } 115 | system_control = L2CValue::Ptr(transmute(frame_info.frame as *const ())); 116 | } 117 | } 118 | drop(fighter_frames); 119 | fighter.shift(system_control.clone()); 120 | let func: extern "C" fn(&mut L2CFighterCommon) -> L2CValue = transmute(system_control.get_ptr()); 121 | func(fighter) 122 | } 123 | 124 | #[skyline::hook(replace = L2CFighterBase_sys_line_system_init)] 125 | unsafe extern "C" fn sys_line_system_init_replace(agent: &mut L2CFighterBase) -> L2CValue { 126 | use std::mem::transmute; 127 | 128 | for callback in AGENT_INIT_CALLBACKS.lock().iter() { 129 | callback(agent); 130 | } 131 | 132 | let mut sys_line_system_control = 0usize; 133 | skyline::nn::ro::LookupSymbol(&mut sys_line_system_control, c_str!("_ZN7lua2cpp14L2CFighterBase23sys_line_system_controlEv")); 134 | let mut system_control = L2CValue::Ptr(transmute(sys_line_system_control as *const ())); 135 | let mut weapon_frames = WEAPON_FRAMES.lock(); 136 | for frame_info in weapon_frames.iter_mut() { 137 | if frame_info.agent.get() == smash::app::utility::get_kind(&mut *agent.module_accessor) { 138 | if let Some(original) = frame_info.original.as_mut() { 139 | let og = system_control.get_ptr() as *const extern "C" fn(); 140 | **original = og; 141 | } 142 | system_control = L2CValue::Ptr(transmute(frame_info.frame as *const ())); 143 | } 144 | } 145 | drop(weapon_frames); 146 | agent.shift(system_control.clone()); 147 | let func: extern "C" fn(&mut L2CFighterBase) -> L2CValue = transmute(system_control.get_ptr()); 148 | func(agent) 149 | } 150 | 151 | #[skyline::hook(replace = L2CFighterBase_sys_line_status_system_init)] 152 | unsafe extern "C" fn sys_line_status_system_init_replace(agent: &mut L2CFighterBase) -> L2CValue { 153 | use std::mem::transmute; 154 | 155 | for callback in AGENT_INIT_CALLBACKS.lock().iter() { 156 | callback(agent); 157 | } 158 | 159 | let mut sys_line_status_system_control = 0usize; 160 | skyline::nn::ro::LookupSymbol(&mut sys_line_status_system_control, c_str!("_ZN7lua2cpp14L2CFighterBase30sys_line_status_system_controlEv")); 161 | let mut status_system_control = L2CValue::Ptr(transmute(sys_line_status_system_control as *const ())); 162 | let mut agent_frames_main = AGENT_FRAMES_MAIN.lock(); 163 | for frame_info in agent_frames_main.iter_mut() { 164 | if (frame_info.is_fighter && smash::app::utility::get_category(&mut *agent.module_accessor) == *BATTLE_OBJECT_CATEGORY_FIGHTER) 165 | || (!frame_info.is_fighter && smash::app::utility::get_category(&mut *agent.module_accessor) == *BATTLE_OBJECT_CATEGORY_WEAPON) 166 | { 167 | if frame_info.agent.get() == smash::app::utility::get_kind(&mut *agent.module_accessor) { 168 | if let Some(original) = frame_info.original.as_mut() { 169 | let og = status_system_control.get_ptr() as *const extern "C" fn(); 170 | **original = og; 171 | } 172 | status_system_control = L2CValue::Ptr(transmute(frame_info.frame as *const ())); 173 | } 174 | } 175 | } 176 | drop(agent_frames_main); 177 | agent.shift(status_system_control.clone()); 178 | let func: extern "C" fn(&mut L2CFighterBase) -> L2CValue = transmute(status_system_control.get_ptr()); 179 | func(agent) 180 | } 181 | 182 | #[skyline::hook(replace = L2CFighterCommon_RESET)] 183 | fn fighter_reset(fighter: &mut L2CFighterCommon) { 184 | for callback in FIGHTER_RESETS.lock().iter() { 185 | callback(fighter) 186 | } 187 | original!()(fighter) 188 | } 189 | 190 | #[skyline::hook(replace = L2CFighterBase_RESET)] 191 | fn agent_reset(agent: &mut L2CFighterBase) { 192 | for callback in AGENT_RESETS.lock().iter() { 193 | callback(agent) 194 | } 195 | original!()(agent) 196 | } 197 | 198 | #[skyline::hook(replace = L2CFighterCommon_sys_line_system_control_fighter)] 199 | fn fighter_frame_callbacks(fighter: &mut L2CFighterCommon) -> L2CValue { 200 | let ret = call_original!(fighter); 201 | for cb in FIGHTER_FRAME_CALLBACKS.lock().iter() { 202 | cb(fighter); 203 | } 204 | ret 205 | } 206 | 207 | #[skyline::hook(replace = L2CFighterBase_sys_line_system_control)] 208 | fn weapon_frame_callbacks(weapon: &mut L2CFighterBase) -> L2CValue { 209 | let ret = call_original!(weapon); 210 | for cb in WEAPON_FRAME_CALLBACKS.lock().iter() { 211 | cb(weapon); 212 | } 213 | ret 214 | } 215 | 216 | #[skyline::hook(replace = L2CFighterBase_sys_line_status_system_control)] 217 | fn agent_frame_main_callbacks(agent: &mut L2CFighterBase) -> L2CValue { 218 | let ret = call_original!(agent); 219 | unsafe { 220 | if !StatusModule::is_changing(agent.module_accessor) { 221 | for cb in AGENT_FRAME_MAIN_CALLBACKS.lock().iter() { 222 | cb(agent); 223 | } 224 | } 225 | } 226 | ret 227 | } 228 | 229 | pub fn remove_fighter_resets(range: (usize, usize)) { 230 | let (begin, end) = range; 231 | let mut resets = FIGHTER_RESETS.lock(); 232 | let mut new_resets = Vec::with_capacity(resets.len()); 233 | for callback in resets.iter() { 234 | let as_usize = *callback as *const () as usize; 235 | if !(begin <= as_usize && as_usize < end) { 236 | new_resets.push(*callback); 237 | } 238 | } 239 | *resets = new_resets; 240 | } 241 | 242 | pub fn remove_agent_resets(range: (usize, usize)) { 243 | let (begin, end) = range; 244 | let mut resets = AGENT_RESETS.lock(); 245 | let mut new_resets = Vec::with_capacity(resets.len()); 246 | for callback in resets.iter() { 247 | let as_usize = *callback as *const () as usize; 248 | if !(begin <= as_usize && as_usize < end) { 249 | new_resets.push(*callback); 250 | } 251 | } 252 | *resets = new_resets; 253 | } 254 | 255 | pub fn remove_fighter_frame_callbacks(range: (usize, usize)) { 256 | let range = range.0..range.1; 257 | let mut callbacks = FIGHTER_FRAME_CALLBACKS.lock(); 258 | let mut new_callbacks = Vec::with_capacity(callbacks.len()); 259 | for callback in callbacks.iter() { 260 | if !range.contains(&(*callback as *const () as usize)) { 261 | new_callbacks.push(*callback); 262 | } 263 | } 264 | *callbacks = new_callbacks; 265 | } 266 | 267 | pub fn remove_weapon_frame_callbacks(range: (usize, usize)) { 268 | let range = range.0..range.1; 269 | let mut callbacks = WEAPON_FRAME_CALLBACKS.lock(); 270 | let mut new_callbacks = Vec::with_capacity(callbacks.len()); 271 | for callback in callbacks.iter() { 272 | if !range.contains(&(*callback as *const () as usize)) { 273 | new_callbacks.push(*callback); 274 | } 275 | } 276 | *callbacks = new_callbacks; 277 | } 278 | 279 | pub fn remove_agent_frame_main_callbacks(range: (usize, usize)) { 280 | let range = range.0..range.1; 281 | let mut callbacks = AGENT_FRAME_MAIN_CALLBACKS.lock(); 282 | let mut new_callbacks = Vec::with_capacity(callbacks.len()); 283 | for callback in callbacks.iter() { 284 | if !range.contains(&(*callback as *const () as usize)) { 285 | new_callbacks.push(*callback); 286 | } 287 | } 288 | *callbacks = new_callbacks; 289 | } 290 | 291 | pub fn remove_fighter_init_callbacks(range: (usize, usize)) { 292 | let range = range.0..range.1; 293 | let mut callbacks = FIGHTER_INIT_CALLBACKS.lock(); 294 | let mut new_callbacks = Vec::with_capacity(callbacks.len()); 295 | for callback in callbacks.iter() { 296 | if !range.contains(&(*callback as *const () as usize)) { 297 | new_callbacks.push(*callback); 298 | } 299 | } 300 | *callbacks = new_callbacks; 301 | } 302 | 303 | pub fn remove_agent_init_callbacks(range: (usize, usize)) { 304 | let range = range.0..range.1; 305 | let mut callbacks = AGENT_INIT_CALLBACKS.lock(); 306 | let mut new_callbacks = Vec::with_capacity(callbacks.len()); 307 | for callback in callbacks.iter() { 308 | if !range.contains(&(*callback as *const () as usize)) { 309 | new_callbacks.push(*callback); 310 | } 311 | } 312 | *callbacks = new_callbacks; 313 | } 314 | 315 | #[no_mangle] 316 | pub extern "Rust" fn replace_fighter_frame(agent: LuaConstant, original: Option<&'static mut *const extern "C" fn()>, replacement: FighterFrame) { 317 | let info = FighterFrameInfo { 318 | agent, 319 | original, 320 | frame: replacement 321 | }; 322 | let mut fighter_frames = FIGHTER_FRAMES.lock(); 323 | fighter_frames.push(info); 324 | } 325 | 326 | #[no_mangle] 327 | pub extern "Rust" fn replace_weapon_frame(agent: LuaConstant, original: Option<&'static mut *const extern "C" fn()>, replacement: AgentFrame) { 328 | let info = AgentFrameInfo { 329 | agent, 330 | original, 331 | frame: replacement 332 | }; 333 | let mut weapon_frames = WEAPON_FRAMES.lock(); 334 | weapon_frames.push(info); 335 | } 336 | 337 | #[no_mangle] 338 | pub extern "Rust" fn replace_agent_frame_main(agent: LuaConstant, is_fighter: bool, original: Option<&'static mut *const extern "C" fn()>, replacement: AgentFrame) { 339 | let info = AgentFrameMainInfo { 340 | agent, 341 | is_fighter, 342 | original, 343 | frame: replacement 344 | }; 345 | let mut agent_frames_main = AGENT_FRAMES_MAIN.lock(); 346 | agent_frames_main.push(info); 347 | } 348 | 349 | #[no_mangle] 350 | pub extern "Rust" fn add_fighter_reset_callback(callback: FighterReset) { 351 | FIGHTER_RESETS.lock().push(callback); 352 | } 353 | 354 | #[no_mangle] 355 | pub extern "Rust" fn add_agent_reset_callback(callback: AgentReset) { 356 | AGENT_RESETS.lock().push(callback); 357 | } 358 | 359 | #[no_mangle] 360 | pub extern "Rust" fn add_fighter_frame_callback(callback: FighterFrameCallback) { 361 | static SHOULD_INSTALL: std::sync::Once = std::sync::Once::new(); 362 | SHOULD_INSTALL.call_once(|| { 363 | unsafe { 364 | SHOULD_INSTALL_FIGHTER_CB = true; 365 | if let Some(_) = crate::COMMON_MEMORY_INFO.as_ref() { 366 | skyline::install_hook!(fighter_frame_callbacks); 367 | } 368 | } 369 | }); 370 | FIGHTER_FRAME_CALLBACKS.lock().push(callback); 371 | } 372 | 373 | #[no_mangle] 374 | pub extern "Rust" fn add_weapon_frame_callback(callback: AgentFrameCallback) { 375 | static SHOULD_INSTALL: std::sync::Once = std::sync::Once::new(); 376 | SHOULD_INSTALL.call_once(|| { 377 | unsafe { 378 | SHOULD_INSTALL_WEAPON_CB = true; 379 | if let Some(_) = crate::COMMON_MEMORY_INFO.as_ref() { 380 | skyline::install_hook!(weapon_frame_callbacks); 381 | } 382 | } 383 | }); 384 | WEAPON_FRAME_CALLBACKS.lock().push(callback); 385 | } 386 | 387 | #[no_mangle] 388 | pub extern "Rust" fn add_agent_frame_main_callback(callback: AgentFrameCallback) { 389 | static SHOULD_INSTALL: std::sync::Once = std::sync::Once::new(); 390 | SHOULD_INSTALL.call_once(|| { 391 | unsafe { 392 | SHOULD_INSTALL_AGENT_MAIN_CB = true; 393 | if let Some(_) = crate::COMMON_MEMORY_INFO.as_ref() { 394 | skyline::install_hook!(agent_frame_main_callbacks); 395 | } 396 | } 397 | }); 398 | AGENT_FRAME_MAIN_CALLBACKS.lock().push(callback); 399 | } 400 | 401 | #[no_mangle] 402 | pub extern "Rust" fn add_fighter_init_callback(callback: FighterInit) { 403 | FIGHTER_INIT_CALLBACKS.lock().push(callback); 404 | } 405 | 406 | #[no_mangle] 407 | pub extern "Rust" fn add_agent_init_callback(callback: AgentInit) { 408 | AGENT_INIT_CALLBACKS.lock().push(callback); 409 | } 410 | 411 | fn install() { 412 | skyline::install_hooks!( 413 | sys_line_system_fighter_init_replace, 414 | sys_line_system_init_replace, 415 | sys_line_status_system_init_replace, 416 | fighter_reset, 417 | agent_reset 418 | ); 419 | 420 | unsafe { 421 | if SHOULD_INSTALL_FIGHTER_CB { 422 | skyline::install_hook!(fighter_frame_callbacks); 423 | } 424 | if SHOULD_INSTALL_WEAPON_CB { 425 | skyline::install_hook!(weapon_frame_callbacks); 426 | } 427 | if SHOULD_INSTALL_AGENT_MAIN_CB { 428 | skyline::install_hook!(agent_frame_main_callbacks); 429 | } 430 | } 431 | } 432 | 433 | pub fn nro_load(info: &NroInfo) { 434 | if info.name == "common" { 435 | install(); 436 | } 437 | } 438 | 439 | pub fn nro_unload(_: &NroInfo) { 440 | 441 | } -------------------------------------------------------------------------------- /src/hooks.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use parking_lot::Mutex; 4 | 5 | use skyline::nro::NroInfo; 6 | use nnsdk::root::{Elf64_Sym, rtld::ModuleObject}; 7 | 8 | use crate::c_str; 9 | use crate::rtld; 10 | 11 | struct HookCtx { 12 | pub symbol: String, 13 | pub replace: *const extern "C" fn(), 14 | pub original: Option<&'static mut *const extern "C" fn()> 15 | } 16 | 17 | pub enum StaticSymbol { 18 | Resolved(usize), 19 | Unresolved(&'static str) 20 | } 21 | 22 | unsafe impl Send for HookCtx {} 23 | unsafe impl Sync for HookCtx {} 24 | 25 | lazy_static! { 26 | static ref SYMBOL_HOOKS: Mutex>> = Mutex::new(HashMap::new()); 27 | } 28 | 29 | pub fn nro_load(nro_info: &NroInfo) { 30 | let mut map = SYMBOL_HOOKS.lock(); 31 | if let Some(hooks) = map.get_mut(nro_info.name) { 32 | for hook in hooks.iter_mut() { 33 | unsafe { 34 | lazy_symbol_replace(nro_info.module.ModuleObject as *mut ModuleObject, hook.symbol.as_str(), hook.replace, hook.original.as_mut()); 35 | } 36 | } 37 | } 38 | } 39 | 40 | pub fn nro_unload(nro_info: &NroInfo) { 41 | let mut map = SYMBOL_HOOKS.lock(); 42 | if let Some(hooks) = map.get_mut(nro_info.name) { 43 | for hook in hooks.iter_mut() { 44 | if let Some(original) = hook.original.as_mut() { 45 | // change the original function to nullptr, leave it to the 46 | // smashline-macro implementation to check when calling original 47 | **original = 0 as _; 48 | } 49 | } 50 | } 51 | } 52 | 53 | pub unsafe fn lazy_symbol_replace(module_object: *mut ModuleObject, symbol: &str, replace: *const extern "C" fn(), original: Option<&mut &'static mut *const extern "C" fn()>) { 54 | let sym = rtld::get_symbol_by_name(module_object, symbol); 55 | if sym.is_null() { 56 | println!("[smashline::hooks] Unable to find symbol {} to replace", symbol); 57 | } else { 58 | symbol_replace(module_object, sym, replace, original); 59 | } 60 | } 61 | 62 | unsafe fn symbol_replace(module_object: *mut ModuleObject, symbol: *const Elf64_Sym, replace: *const extern "C" fn(), original: Option<&mut &'static mut *const extern "C" fn()>) { 63 | if !symbol.is_null() { 64 | let base = (*module_object).module_base; 65 | let difference = (replace as u64) - base; 66 | if let Some(original) = original { 67 | **original = ((*symbol).st_value + base) as *const extern "C" fn(); 68 | } 69 | skyline::patching::sky_memcpy(&(*symbol).st_value as *const u64 as *const _, &difference as *const u64 as *const _, 8); 70 | } 71 | } 72 | 73 | #[no_mangle] 74 | pub extern "Rust" fn replace_symbol(module: &str, symbol: &str, replace: *const extern "C" fn(), original: Option<&'static mut *const extern "C" fn()>) { 75 | let mut map = SYMBOL_HOOKS.lock(); 76 | let hook_ctx = HookCtx { 77 | symbol: String::from(symbol), 78 | replace, 79 | original 80 | }; 81 | if let Some(hooks) = map.get_mut(module) { 82 | hooks.push(hook_ctx); 83 | } else { 84 | map.insert(String::from(module), vec![hook_ctx]); 85 | } 86 | } 87 | 88 | #[no_mangle] 89 | pub extern "Rust" fn replace_static_symbol(symbol: StaticSymbol, replace: *const extern "C" fn(), mut original: Option<&'static mut *const extern "C" fn()>) { 90 | unsafe { 91 | match symbol { 92 | StaticSymbol::Unresolved(sym) => { 93 | let mut symbol_addr = 0usize; 94 | let result = skyline::nn::ro::LookupSymbol(&mut symbol_addr, c_str!(sym)); 95 | if result != 0 || symbol_addr == 0 { 96 | panic!("Failed to lookup symbol \"{}\", is it really static?", sym); 97 | } 98 | let module_object = rtld::get_module_object_from_address(symbol_addr).expect("Failed to get module object from static symbol, is it really static?"); 99 | lazy_symbol_replace(module_object, sym, replace, original.as_mut()); 100 | }, 101 | StaticSymbol::Resolved(addr) => { 102 | let module_object = rtld::get_module_object_from_address(addr).expect("Failed to get module object from static symbol, is it really static?"); 103 | let sym = rtld::get_symbol_by_resolved_address(module_object, addr); 104 | if sym.is_null() { 105 | println!("[smashline::hooks] Unable to replace static symbol with resolved address {:#x}", addr); 106 | } else { 107 | symbol_replace(module_object, sym, replace, original.as_mut()); 108 | } 109 | } 110 | } 111 | } 112 | } -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(proc_macro_hygiene)] 2 | #![feature(asm)] 3 | #![allow(unused_imports)] 4 | #![feature(const_if_match)] 5 | 6 | #[macro_use] 7 | extern crate lazy_static; 8 | #[macro_use] 9 | extern crate bitflags; 10 | #[macro_use] 11 | extern crate paste; 12 | 13 | use std::sync::atomic::{AtomicUsize, Ordering}; 14 | 15 | use skyline::{hook, install_hook}; 16 | use skyline::nro::NroInfo; 17 | use smash::lib::LuaConst; 18 | 19 | mod acmd; 20 | mod callbacks; 21 | mod hooks; 22 | mod loader; 23 | mod nro_hook; 24 | mod nx; 25 | mod rtld; 26 | mod scripts; 27 | mod status; 28 | mod unwind; 29 | 30 | #[derive(Clone)] 31 | pub enum LuaConstant { 32 | Symbolic(LuaConst), 33 | Evaluated(i32) 34 | } 35 | 36 | impl LuaConstant { 37 | pub fn get(&mut self) -> i32 { 38 | match self { 39 | LuaConstant::Symbolic(symbolic) => { 40 | let val = **symbolic; 41 | *self = LuaConstant::Evaluated(val); 42 | val 43 | }, 44 | LuaConstant::Evaluated(evaluated) => *evaluated 45 | } 46 | } 47 | } 48 | 49 | // I've copy pasted this from jugeeya so much 50 | #[macro_export] 51 | macro_rules! c_str { 52 | ($l:tt) => { 53 | [$l.as_bytes(), "\u{0}".as_bytes()].concat().as_ptr(); 54 | } 55 | } 56 | 57 | pub static mut COMMON_MEMORY_INFO: Option = None; 58 | 59 | fn nro_load(info: &NroInfo) { 60 | callbacks::nro_load(info); 61 | hooks::nro_load(info); 62 | acmd::nro_load(info); 63 | status::nro_load(info); 64 | if info.name == "common" { 65 | unsafe { 66 | COMMON_MEMORY_INFO = Some(nx::svc::query_memory((*info.module.ModuleObject).module_base as usize).expect("Unable to query common memory info.")); 67 | } 68 | } 69 | } 70 | 71 | fn nro_unload(info: &NroInfo) { 72 | scripts::clear_loaded_agent(info); 73 | callbacks::nro_unload(info); 74 | hooks::nro_unload(info); 75 | acmd::nro_unload(info); 76 | status::nro_unload(info); 77 | } 78 | 79 | extern "C" { 80 | #[link_name = "add_nn_hid_hook"] 81 | fn add_nn_hid_hook(callback: extern "C" fn(&mut skyline::nn::hid::NpadHandheldState, &u32)); 82 | } 83 | 84 | extern "C" fn hid_hook(state: &mut skyline::nn::hid::NpadHandheldState, id: &u32) { 85 | static TIMER: AtomicUsize = AtomicUsize::new(0); 86 | const KEY_L: u32 = 1 << 6; 87 | const KEY_R: u32 = 1 << 7; 88 | const KEY_DUP: u32 = 1 << 13; 89 | const BUTTON_COMBO: u64 = (KEY_L | KEY_R | KEY_DUP) as u64; 90 | if TIMER.load(Ordering::SeqCst) != 0 { 91 | TIMER.fetch_sub(1, Ordering::SeqCst); 92 | return; 93 | } 94 | if (state.Buttons & BUTTON_COMBO) == BUTTON_COMBO { 95 | unsafe { 96 | loader::load_development_plugin(); 97 | } 98 | TIMER.store(500, Ordering::SeqCst); 99 | } 100 | } 101 | 102 | #[skyline::main(name = "smashline_hook")] 103 | pub fn main() { 104 | nro_hook::install(); 105 | nro_hook::add_nro_load_hook(nro_load); 106 | nro_hook::add_nro_unload_hook(nro_unload); 107 | 108 | status::install(); 109 | unwind::install(); 110 | if cfg!(feature = "development") { 111 | unsafe { 112 | let mut symbol = 0usize; 113 | skyline::nn::ro::LookupSymbol(&mut symbol, "add_nn_hid_hook\0".as_ptr()); 114 | if symbol != 0 { 115 | add_nn_hid_hook(hid_hook); 116 | return; 117 | } 118 | } 119 | unsafe { 120 | loader::load_development_plugin(); 121 | std::thread::spawn(|| { 122 | let mut load_flag = false; 123 | skyline::nn::hid::InitializeNpad(); 124 | loop { 125 | const KEY_L: u32 = 1 << 6; 126 | const KEY_R: u32 = 1 << 7; 127 | const KEY_DUP: u32 = 1 << 13; 128 | const BUTTON_COMBO: u64 = (KEY_L | KEY_R | KEY_DUP) as u64; 129 | use skyline::nn::hid::*; 130 | if load_flag { 131 | std::thread::sleep(std::time::Duration::from_secs(5)); 132 | load_flag = false; 133 | } 134 | std::thread::sleep(std::time::Duration::from_millis(100)); 135 | let mut npad_state = NpadHandheldState::default(); 136 | GetNpadHandheldState(&mut npad_state, &0x20); 137 | if (npad_state.Buttons & BUTTON_COMBO) == BUTTON_COMBO { 138 | loader::load_development_plugin(); 139 | load_flag = true; 140 | continue; 141 | } 142 | for x in 0..8 { 143 | GetNpadFullKeyState(&mut npad_state, &x); 144 | if (npad_state.Buttons & BUTTON_COMBO) == BUTTON_COMBO { 145 | loader::load_development_plugin(); 146 | load_flag = true; 147 | break; 148 | } 149 | GetNpadJoyDualState(&mut npad_state, &x); 150 | if (npad_state.Buttons & BUTTON_COMBO) == BUTTON_COMBO { 151 | loader::load_development_plugin(); 152 | load_flag = true; 153 | break; 154 | } 155 | let mut npad_state = NpadGcState::default(); 156 | GetNpadGcState(&mut npad_state, &x); 157 | if (npad_state.Buttons & BUTTON_COMBO) == BUTTON_COMBO { 158 | loader::load_development_plugin(); 159 | load_flag = true; 160 | break; 161 | } 162 | } 163 | } 164 | }); 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/loader.rs: -------------------------------------------------------------------------------- 1 | use std::{mem::MaybeUninit, path::{Path, PathBuf}}; 2 | use skyline::{nn, libc}; 3 | use nn::ro::{self, NroHeader, NrrHeader, RegistrationInfo, Module}; 4 | use parking_lot::Mutex; 5 | 6 | use crate::c_str; 7 | 8 | macro_rules! align_up { 9 | ($x:expr, $a:expr) => { 10 | ((($x) + (($a)-1)) & !(($a)-1)) 11 | } 12 | } 13 | 14 | struct DevelopmentPlugin { 15 | // pub path: PathBuf, 16 | pub nro_module: Module, 17 | pub nrr_info: RegistrationInfo, 18 | // pub bss_section: *mut u8, 19 | // pub bss_size: usize 20 | } 21 | 22 | unsafe impl Send for DevelopmentPlugin {} 23 | unsafe impl Sync for DevelopmentPlugin {} 24 | 25 | extern "C" { 26 | #[allow(non_snake_case)] 27 | #[link_name = "\u{1}_ZN2nn2ro20UnregisterModuleInfoEPNS0_16RegistrationInfoE"] 28 | fn UnregisterModuleInfo(info: *mut RegistrationInfo); 29 | } 30 | 31 | lazy_static! { 32 | static ref LOADED_DEVELOPMENT_PLUGIN: Mutex> = Mutex::new(None); 33 | } 34 | 35 | const NRR_SIZE: usize = std::mem::size_of::(); 36 | 37 | impl DevelopmentPlugin { 38 | pub unsafe fn new(path: &str) -> Option { 39 | let path = PathBuf::from(path); 40 | let file_path = path.as_path(); 41 | if !file_path.exists() { 42 | println!("[smashline::loader] Development plugin file not found"); 43 | return None; 44 | } 45 | 46 | let nro_image = match std::fs::read(file_path) { 47 | Ok(data) => data, 48 | Err(_) => { 49 | println!("[smashline::loader] Failed to read development plugin"); 50 | return None; 51 | } 52 | }; 53 | 54 | // TEMP 55 | let nro_image_size = nro_image.len(); 56 | 57 | let nro_image = { 58 | let new_mem = libc::memalign(0x1000, nro_image.len()) as *mut u8; 59 | std::ptr::copy_nonoverlapping(nro_image.as_ptr(), new_mem, nro_image.len()); 60 | new_mem as *const libc::c_void 61 | }; 62 | 63 | let mut bss_size = 0u64; 64 | let rc = nn::ro::GetBufferSize(&mut bss_size, nro_image); 65 | if rc != 0 { 66 | println!("[smashline::loader] Failed to read buffer size from development plugin ({:#x}). Is it a valid NRO?", rc); 67 | libc::free(nro_image as *mut libc::c_void); 68 | return None; 69 | } 70 | let bss_size = bss_size as usize; 71 | 72 | let nro_header = nro_image as *const NroHeader; 73 | let mut hash = [0u8; 0x20]; 74 | nn::crypto::GenerateSha256Hash(hash.as_mut_ptr() as _, 0x20, nro_header as _, (*nro_header).size as u64); 75 | 76 | let nrr_size = align_up!(NRR_SIZE + 0x20, 0x1000); 77 | let nrr_image = libc::memalign(0x1000, nrr_size) as *mut u8; 78 | libc::memset(nrr_image as _, 0x0, nrr_size); 79 | 80 | let program_id = skyline::info::get_program_id(); 81 | 82 | { 83 | let nrr_header = &mut *(nrr_image as *mut NrrHeader); 84 | nrr_header.magic = 0x3052524E; 85 | nrr_header.program_id = ro::ProgramId { value: program_id }; 86 | nrr_header.size = nrr_size as u32; 87 | nrr_header.type_ = 0; 88 | nrr_header.hashes_offset = NRR_SIZE as u32; 89 | nrr_header.num_hashes = 1; 90 | } 91 | 92 | libc::memcpy(nrr_image.add(NRR_SIZE) as _, hash.as_ptr() as _, 0x20); 93 | let mut nrr_info = MaybeUninit::uninit(); 94 | let rc = ro::RegisterModuleInfo(nrr_info.as_mut_ptr(), nrr_image as _); 95 | if rc != 0 { 96 | println!("[smashline::loader] Failed to register NRR ({:#x})", rc); 97 | libc::free(nro_image as _); 98 | libc::free(nrr_image as _); 99 | return None; 100 | } else { 101 | println!("[smashline::loader] Loading development plugin..."); 102 | } 103 | let nrr_info = nrr_info.assume_init(); 104 | 105 | let bss_section = libc::memalign(0x1000, bss_size); 106 | let mut nro_module = MaybeUninit::uninit(); 107 | let rc = ro::LoadModule(nro_module.as_mut_ptr(), nro_image, bss_section, bss_size as u64, ro::BindFlag_BindFlag_Now as i32); 108 | if rc == 0 { 109 | println!("[smashline::loader] Successfuly loaded development plugin in range ({:#x} - {:#x})", nro_image as u64, nro_image_size + nro_image as usize); 110 | } else { 111 | println!("[smashline::loader] Failed to load development plugin ({:#x})", rc); 112 | } 113 | let nro_module = nro_module.assume_init(); 114 | 115 | if rc == 0 { 116 | let bss_section = bss_section as *mut u8; 117 | 118 | Some(Self { 119 | // path, 120 | nro_module, 121 | nrr_info, 122 | // bss_section, 123 | // bss_size 124 | }) 125 | } else { 126 | // we don't free on success since it's owned by RO now 127 | libc::free(bss_section as _); 128 | libc::free(nro_image as _); 129 | libc::free(nrr_image as _); 130 | None 131 | } 132 | 133 | } 134 | 135 | pub unsafe fn install(&self) { 136 | let mut install_fn = 0usize; 137 | let rc = ro::LookupModuleSymbol(&mut install_fn, &self.nro_module, c_str!("smashline_install")); 138 | if rc != 0 { 139 | panic!("Smashline development plugin does not export 'smashline_install'"); 140 | } else { 141 | let callable: extern "Rust" fn() = std::mem::transmute(install_fn); 142 | callable() 143 | } 144 | } 145 | 146 | pub unsafe fn uninstall(&mut self) { 147 | let mut uninstall_fn = 0usize; 148 | let rc = ro::LookupModuleSymbol(&mut uninstall_fn, &self.nro_module, c_str!("smashline_uninstall")); 149 | 150 | if rc != 0 || uninstall_fn == 0 { 151 | println!("[smashline::loader] Development plugin does not export 'smashline_uninstall', continuing with default uninstallation."); 152 | } else { 153 | let callable: extern "Rust" fn() = std::mem::transmute(uninstall_fn); 154 | callable(); 155 | println!("[smashline::loader] Development plugin's uninstall routine called, continuing with default uninstallation."); 156 | } 157 | 158 | let mem_info = crate::nx::svc::query_memory((*self.nro_module.ModuleObject).module_base as usize).expect("Smashline unable to query memory for development plugin on uninstall."); 159 | let range = (mem_info.mem_info.base_address, mem_info.mem_info.base_address + mem_info.mem_info.size); 160 | 161 | crate::callbacks::remove_fighter_resets(range); 162 | crate::callbacks::remove_agent_resets(range); 163 | crate::callbacks::remove_fighter_frame_callbacks(range); 164 | crate::callbacks::remove_weapon_frame_callbacks(range); 165 | crate::callbacks::remove_agent_frame_main_callbacks(range); 166 | crate::callbacks::remove_fighter_init_callbacks(range); 167 | crate::callbacks::remove_agent_init_callbacks(range); 168 | crate::acmd::remove_acmd_scripts(range); 169 | crate::status::remove_status_scripts(range); 170 | crate::unwind::unregister_skyline_plugin(range.0); 171 | 172 | println!("[smashline::loader] Unloading the development plugin..."); 173 | ro::UnloadModule(&mut self.nro_module); 174 | UnregisterModuleInfo(&mut self.nrr_info); 175 | } 176 | } 177 | 178 | // The following code has been adapted by Skyline (https://github.com/skyline-dev/skyline/blob/master/source/skyline/plugin/PluginManager.cpp) 179 | // Only one development plugin is allowed at a time 180 | pub unsafe fn load_development_plugin() { 181 | let mut loaded = LOADED_DEVELOPMENT_PLUGIN.lock(); 182 | if let Some(mut plugin) = loaded.take() { 183 | plugin.uninstall(); 184 | std::mem::forget(plugin); 185 | } 186 | // hardcode the path here. would like to use rom but nnsdk caches the rom contents when it's mounted ig 187 | if let Some(plugin) = DevelopmentPlugin::new("sd:/atmosphere/contents/01006A800016E000/romfs/smashline/development.nro") { 188 | plugin.install(); 189 | *loaded = Some(plugin); 190 | } 191 | } -------------------------------------------------------------------------------- /src/nro_hook.rs: -------------------------------------------------------------------------------- 1 | use skyline::nro::{self, Callback, NroInfo}; 2 | use skyline::{nn, libc}; 3 | 4 | pub fn add_nro_load_hook(callback: Callback) { 5 | match nro::add_hook(callback) { 6 | Err(_) => { 7 | panic!("smashline failed to add NRO callback because libnro_hook.nro is missing!"); 8 | }, 9 | _ => {} 10 | } 11 | } 12 | 13 | pub fn add_nro_unload_hook(callback: Callback) { 14 | match nro::add_unload_hook(callback) { 15 | Err(_) => { 16 | panic!("smashline failed to add NRO callback because libnro_hook.nro is missing!"); 17 | } 18 | _ => {} 19 | } 20 | } 21 | 22 | extern "C" { 23 | fn nro_request_bind_now(); 24 | } 25 | 26 | pub fn install() { 27 | unsafe { 28 | nro_request_bind_now(); 29 | } 30 | } -------------------------------------------------------------------------------- /src/nx.rs: -------------------------------------------------------------------------------- 1 | use std::arch::asm; 2 | // The following code is taken from the WIP skyline rust-rewrite to assist in symbol hooking 3 | // the static modules 4 | 5 | #[derive(Debug)] 6 | #[repr(transparent)] 7 | pub struct NxResult(u32); 8 | 9 | #[repr(u32)] 10 | #[derive(Copy, Clone)] 11 | pub enum MemoryState { 12 | Free = 0x00, 13 | Io = 0x01, 14 | Static = 0x02, 15 | Code = 0x03, 16 | CodeData = 0x04, 17 | Normal = 0x05, 18 | Shared = 0x06, 19 | Alias = 0x07, 20 | AliasCode = 0x08, 21 | AliasCodeData = 0x09, 22 | Ipc = 0x0A, 23 | Stack = 0x0B, 24 | ThreadLocal = 0x0C, 25 | Transfered = 0x0D, 26 | SharedTransfered = 0x0E, 27 | SharedCode = 0x0F, 28 | Inaccessible = 0x10, 29 | NonSecureIpc = 0x11, 30 | NonDeviceIpc = 0x12, 31 | Kernel = 0x13, 32 | GeneratedCode = 0x14, 33 | CodeOut = 0x15, 34 | } 35 | 36 | bitflags! { 37 | #[repr(C)] 38 | pub struct MemoryPermission : u32 { 39 | const NONE = 0; 40 | const READ = 1 << 0; 41 | const WRITE = 1 << 1; 42 | const EXECUTE = 1 << 2; 43 | const DONT_CARE = 1 << 28; 44 | 45 | const READ_WRITE = Self::READ.bits | Self::WRITE.bits; 46 | const READ_EXECUTE = Self::READ.bits | Self::EXECUTE.bits; 47 | } 48 | } 49 | 50 | bitflags! { 51 | #[repr(C)] 52 | pub struct MemoryAttribute : u32 { 53 | const LOCKED = 1 << 0; 54 | const IPC_LOCKED = 1 << 1; 55 | const DEVICE_SHARED = 1 << 2; 56 | const UNCACHED = 1 << 3; 57 | } 58 | } 59 | #[repr(C)] 60 | #[derive(Copy, Clone)] 61 | pub struct MemoryInfo { 62 | pub base_address: usize, 63 | pub size: usize, 64 | pub state: MemoryState, 65 | pub attribute: MemoryAttribute, 66 | pub permission: MemoryPermission, 67 | pub device_refcount: u32, 68 | pub ipc_refcount: u32, 69 | pub padding: u32 70 | } 71 | 72 | #[repr(transparent)] 73 | #[derive(Copy, Clone)] 74 | pub struct PageInfo { 75 | pub flags: u32 76 | } 77 | 78 | #[repr(C)] 79 | #[derive(Copy, Clone)] 80 | pub struct QueryMemoryResult { 81 | pub mem_info: MemoryInfo, 82 | pub page_info: PageInfo 83 | } 84 | 85 | use std::mem::MaybeUninit; 86 | 87 | pub mod svc { 88 | use super::*; 89 | 90 | #[inline(always)] 91 | pub extern "C" fn query_memory(address: usize) -> Result { 92 | let mut res: NxResult = NxResult(0); 93 | let mut mem_info: MemoryInfo = unsafe { std::mem::zeroed() }; 94 | let svc_result = unsafe { 95 | let mem_info_ptr = &mut mem_info as *mut MemoryInfo;; 96 | let mut page_info: PageInfo = PageInfo { flags: 0 }; 97 | asm!("svc 0x6", lateout("w0") res.0, out("w1") page_info.flags, in("x0") mem_info_ptr, in("x2") address, options(nostack)); 98 | // asm!("svc 0x6" : "={w0}" (res), "={w1}" (page_info) : "{x0}" (mem_info_ptr), "{x2}" (address) : : "volatile"); 99 | QueryMemoryResult { 100 | mem_info: mem_info, 101 | page_info 102 | } 103 | }; 104 | match res.0 { 105 | 0 => Ok(svc_result), 106 | _ => Err(res) 107 | } 108 | } 109 | } -------------------------------------------------------------------------------- /src/rtld.rs: -------------------------------------------------------------------------------- 1 | // This code has been adapted from Thog's oss-rtld implementation 2 | // https://github.com/Thog/oss-rtld/ 3 | 4 | use skyline::{nn, libc}; 5 | use nnsdk::root::Elf64_Sym; 6 | use nnsdk::root::rtld::ModuleObject; 7 | 8 | use crate::c_str; 9 | use crate::nx::{self, svc}; 10 | 11 | unsafe fn gen_rtld_elf_hash(mut name: *const libc::c_char) -> u32 { 12 | let mut elf_hash = 0u32; 13 | let mut g; 14 | loop { 15 | if *name == 0 { 16 | break; 17 | } 18 | 19 | elf_hash = (elf_hash << 4) + (*name as u32); 20 | name = name.add(1); 21 | g = elf_hash & 0xF0000000; 22 | if g != 0 { 23 | elf_hash ^= g >> 24; 24 | } 25 | 26 | elf_hash &= !g; 27 | } 28 | 29 | elf_hash 30 | } 31 | 32 | pub unsafe fn get_symbol_by_name(module_object: *const ModuleObject, name: &str) -> *const Elf64_Sym { 33 | let module_object = &*module_object; 34 | let name = c_str!(name); 35 | let hash = gen_rtld_elf_hash(name) as u32; 36 | let mut i = *module_object.hash_bucket.offset((hash % (module_object.hash_nbucket_value as u32)) as isize); 37 | while i != 0 { 38 | let sym = &*module_object.dynsym.offset(i as isize); 39 | let mut is_common = true; 40 | if sym.st_shndx != 0 { 41 | is_common = sym.st_shndx == 0xFFF2; 42 | } 43 | if !is_common && libc::strcmp(name, module_object.dynstr.offset(sym.st_name as isize)) == 0 { 44 | return module_object.dynsym.offset(i as isize); 45 | } 46 | i = *module_object.hash_chain.offset(i as isize); 47 | } 48 | 0 as _ 49 | } 50 | 51 | pub unsafe fn get_symbol_by_resolved_address(module_object: *const ModuleObject, address: usize) -> *const Elf64_Sym { 52 | // this is slow and should be avoided at all costs 53 | let module_object = &*module_object; 54 | let difference = (address as u64) - module_object.module_base; 55 | for i in (0..module_object.hash_nbucket_value) { 56 | let mut j = *module_object.hash_bucket.offset(i as isize); 57 | while j != 0 { 58 | let sym = &*module_object.dynsym.offset(j as isize); 59 | let mut is_common = true; 60 | if sym.st_shndx != 0 { 61 | is_common = sym.st_shndx == 0xFFF2; 62 | } 63 | if !is_common && difference == sym.st_value { 64 | return sym as *const Elf64_Sym; 65 | } 66 | j = *module_object.hash_chain.offset(j as isize); 67 | } 68 | } 69 | 0 as _ 70 | } 71 | 72 | #[derive(Clone, Copy)] 73 | struct Mod0Header { 74 | pub reserved: u32, 75 | pub mod0_offset: u32 76 | } 77 | #[derive(Clone, Copy)] 78 | struct Mod0 { 79 | pub magic: u32, 80 | pub dynamic_offset: u32, 81 | pub bss_start_offset: u32, 82 | pub bss_end_offset: u32, 83 | pub unwind_start_offset: u32, 84 | pub unwind_end_offset: u32, 85 | pub module_object_offset: u32 86 | } 87 | 88 | pub unsafe fn get_module_object_from_address(address: usize) -> Result<*mut ModuleObject, nx::NxResult> { 89 | let queried_mem = svc::query_memory(address)?; 90 | let header = *(queried_mem.mem_info.base_address as *const Mod0Header); 91 | let mod0_addr = queried_mem.mem_info.base_address + header.mod0_offset as usize; 92 | let mod0 = *(mod0_addr as *const Mod0); 93 | assert!(mod0.magic == 0x30444f4d); 94 | let module_object = (mod0_addr + mod0.module_object_offset as usize) as *mut ModuleObject; 95 | Ok(module_object) 96 | } -------------------------------------------------------------------------------- /src/scripts.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, HashSet}; 2 | use nnsdk::root::{Elf64_Sym, rtld::ModuleObject}; 3 | 4 | use aarch64_decode::*; 5 | use skyline::nro::NroInfo; 6 | use smash::phx::Hash40; 7 | use smash::app::{BattleObject, BattleObjectModuleAccessor}; 8 | use smash::lua_State; 9 | use smash::lua2cpp::L2CAgentBase; 10 | use smash::lib::L2CValue; 11 | 12 | use parking_lot::Mutex; 13 | 14 | use crate::hooks::lazy_symbol_replace; 15 | use crate::acmd::{Category, GAME_SCRIPTS, EFFECT_SCRIPTS, SOUND_SCRIPTS, EXPRESSION_SCRIPTS}; 16 | use crate::status::{COMMON_STATUS_SCRIPTS, STATUS_SCRIPTS, STATUS_CUSTOMIZERS}; 17 | use crate::COMMON_MEMORY_INFO; 18 | use Category::*; 19 | 20 | macro_rules! generate_new_create_agent { 21 | ($(($func_name:ident, $agent_infos:ident, $script_list:ident, $cat:ident, $share:expr)),* $(,)?) => { 22 | $( 23 | generate_new_create_agent!($func_name, $agent_infos, $script_list, $cat, $share); 24 | )* 25 | }; 26 | ($func_name:ident, $agent_infos:ident, $script_list:ident, $cat:ident, $share:expr) => { 27 | paste! { 28 | pub unsafe extern "C" fn [] ( 29 | hash: Hash40, 30 | bobj: *mut BattleObject, 31 | boma: *mut BattleObjectModuleAccessor, 32 | state: *mut lua_State 33 | ) -> *mut L2CAgentBase { 34 | let infos = $agent_infos.lock(); 35 | for (_, info) in infos.iter() { 36 | if info.hashes.contains(&hash) { 37 | let agent = (info.original)(hash, bobj, boma, state); 38 | LOADED_ACMD_AGENTS.lock().push(LoadedAcmdAgentInfo { agent: agent, hash: hash, category: $cat, is_share: $share }); 39 | let mut script_list = $script_list.lock(); 40 | if let Some(scripts) = script_list.get_mut(&Hash40::new("common")) { 41 | for script_info in scripts.iter_mut() { 42 | if let Some(original) = script_info.original.as_mut() { 43 | **original = 0 as _; 44 | } 45 | (*agent).sv_set_function_hash(std::mem::transmute(script_info.bind_fn), script_info.script); 46 | } 47 | } 48 | if let Some(scripts) = script_list.get_mut(&hash) { 49 | for script_info in scripts.iter_mut() { 50 | let og_func = *(*agent).functions.get(&script_info.script).unwrap_or(&(0 as _)); 51 | script_info.backup = std::mem::transmute(og_func); 52 | if let Some(original) = script_info.original.as_mut() { 53 | **original = std::mem::transmute(og_func); 54 | } 55 | (*agent).sv_set_function_hash(std::mem::transmute(script_info.bind_fn), script_info.script); 56 | } 57 | } 58 | return agent; 59 | } 60 | } 61 | return 0 as _; 62 | } 63 | } 64 | } 65 | } 66 | 67 | generate_new_create_agent!( 68 | (animcmd_game, GAME_CREATE_AGENTS, GAME_SCRIPTS, ACMD_GAME, false), 69 | (animcmd_game_share, GAME_SHARE_CREATE_AGENTS, GAME_SCRIPTS, ACMD_GAME, true), 70 | (animcmd_effect, EFFECT_CREATE_AGENTS, EFFECT_SCRIPTS, ACMD_EFFECT, false), 71 | (animcmd_effect_share, EFFECT_SHARE_CREATE_AGENTS, EFFECT_SCRIPTS, ACMD_EFFECT, true), 72 | (animcmd_sound, SOUND_CREATE_AGENTS, SOUND_SCRIPTS, ACMD_SOUND, false), 73 | (animcmd_sound_share, SOUND_SHARE_CREATE_AGENTS, SOUND_SCRIPTS, ACMD_SOUND, true), 74 | (animcmd_expression, EXPRESSION_CREATE_AGENTS, EXPRESSION_SCRIPTS, ACMD_EXPRESSION, false), 75 | (animcmd_expression_share, EXPRESSION_SHARE_CREATE_AGENTS, EXPRESSION_SCRIPTS, ACMD_EXPRESSION, true) 76 | ); 77 | 78 | const STATUS_DTOR: usize = 15; 79 | const STATUS_DEL_DTOR: usize = 16; 80 | const STATUS_SET: usize = 17; 81 | const STATUS_AGENT_HASH: usize = 18; 82 | 83 | unsafe fn recreate_status_vtable(vtable: *const u64, hash: Hash40) -> *const u64 { 84 | let new_vtable = std::alloc::alloc(std::alloc::Layout::from_size_align(19 * 0x8, 0x8).unwrap()) as *mut u64; 85 | std::ptr::copy_nonoverlapping(vtable, new_vtable, 15); 86 | *new_vtable.add(STATUS_DTOR) = *vtable.add(0); 87 | *new_vtable.add(STATUS_DEL_DTOR) = *vtable.add(1); 88 | *new_vtable.add(STATUS_SET) = *vtable.add(9); 89 | *new_vtable.add(STATUS_AGENT_HASH) = hash.hash; 90 | *new_vtable.add(0) = status_agent_dtor as *const () as u64; 91 | *new_vtable.add(1) = status_agent_del_dtor as *const () as u64; 92 | *new_vtable.add(9) = set_status_scripts as *const () as u64; 93 | new_vtable 94 | } 95 | 96 | unsafe extern "C" fn status_agent_dtor(agent: *mut L2CAgentBase) { 97 | let callable: extern "C" fn(*mut L2CAgentBase) = std::mem::transmute(*((*agent).vtable as *const u64).add(STATUS_DTOR)); 98 | callable(agent) 99 | } 100 | 101 | unsafe extern "C" fn status_agent_del_dtor(agent: *mut L2CAgentBase) { 102 | let callable: extern "C" fn(*mut L2CAgentBase) = std::mem::transmute(*((*agent).vtable as *const u64).add(STATUS_DEL_DTOR)); 103 | callable(agent) 104 | } 105 | 106 | unsafe extern "C" fn set_status_scripts(agent: *mut L2CAgentBase) { 107 | let agent_hash = Hash40::new_raw(*((*agent).vtable as *const u64).add(STATUS_AGENT_HASH)); 108 | let callable: extern "C" fn(*mut L2CAgentBase) = std::mem::transmute(*((*agent).vtable as *const u64).add(STATUS_SET)); 109 | callable(agent); 110 | let mut scripts = STATUS_SCRIPTS.lock(); 111 | if let Some(script_list) = scripts.get_mut(&agent_hash) { 112 | for script in script_list.iter_mut() { 113 | let og_func = (*agent).sv_get_status_func( 114 | &L2CValue::I32(script.status.get()), 115 | &L2CValue::I32(script.condition.get()) 116 | ).get_ptr(); 117 | if let Some(original) = script.original.as_mut() { 118 | **original = std::mem::transmute(og_func); 119 | } 120 | script.backup = std::mem::transmute(og_func); 121 | let og = (*agent).sv_get_status_func( 122 | &L2CValue::I32(script.status.get()), 123 | &L2CValue::I32(script.condition.get()) 124 | ).get_ptr(); 125 | (*agent).sv_set_status_func( 126 | L2CValue::I32(script.status.get()), 127 | L2CValue::I32(script.condition.get()), 128 | std::mem::transmute(script.replacement) 129 | ); 130 | } 131 | } 132 | drop(scripts); 133 | let mut customizers = STATUS_CUSTOMIZERS.lock(); 134 | if let Some(waza_info) = customizers.get_mut(&agent_hash) { 135 | println!("here"); 136 | let og_func = (*(agent as *mut smash::lua2cpp::L2CFighterCommon)).global_table[0x3D].get_ptr(); 137 | if let Some(original) = waza_info.original.as_mut() { 138 | **original = std::mem::transmute(og_func); 139 | } 140 | waza_info.backup = std::mem::transmute(og_func); 141 | (*(agent as *mut smash::lua2cpp::L2CFighterCommon)).global_table[0x3D].assign(&L2CValue::Ptr(waza_info.replacement as _)); 142 | } 143 | } 144 | 145 | unsafe extern "C" fn create_agent_fighter_status_script( 146 | hash: Hash40, 147 | bobj: *mut BattleObject, 148 | boma: *mut BattleObjectModuleAccessor, 149 | state: *mut lua_State 150 | ) -> *mut L2CAgentBase { 151 | let agents = STATUS_CREATE_AGENTS.lock(); 152 | for (module, info) in agents.iter() { 153 | if info.hashes.contains(&hash) { 154 | let agent = (info.original)(hash, bobj, boma, state); 155 | let new_vtable = recreate_status_vtable((*agent).vtable as *const u64, hash); 156 | (*agent).vtable = new_vtable as u64; 157 | let mut vtables = STATUS_VTABLES.lock(); 158 | if let Some(vtables) = vtables.get_mut(module) { 159 | vtables.push(new_vtable as u64); 160 | } else { 161 | vtables.insert(module.clone(), vec![new_vtable as u64]); 162 | } 163 | let mut loaded_agents = LOADED_STATUS_AGENTS.lock(); 164 | loaded_agents.push(LoadedStatusAgentInfo { agent, hash }); 165 | return agent; 166 | } 167 | } 168 | 0 as _ 169 | } 170 | 171 | type StatusFunc = unsafe extern "C" fn(*mut L2CAgentBase); 172 | type CreateAgentFunc = unsafe extern "C" fn(Hash40, *mut BattleObject, *mut BattleObjectModuleAccessor, *mut lua_State) -> *mut L2CAgentBase; 173 | 174 | struct CreateAgentInfo { 175 | pub original: CreateAgentFunc, 176 | pub hashes: Vec 177 | } 178 | 179 | #[derive(Copy, Clone)] 180 | struct LoadedAcmdAgentInfo { 181 | pub agent: *mut L2CAgentBase, 182 | pub hash: Hash40, 183 | pub category: Category, 184 | pub is_share: bool 185 | } 186 | 187 | unsafe impl Sync for LoadedAcmdAgentInfo {} 188 | unsafe impl Send for LoadedAcmdAgentInfo {} 189 | 190 | struct StatusCreateAgentInfo { 191 | pub agent: Hash40, 192 | pub set_status_func: StatusFunc, 193 | pub status_dtor: StatusFunc, 194 | pub status_del_dtor: StatusFunc 195 | } 196 | 197 | #[derive(Copy, Clone)] 198 | struct LoadedStatusAgentInfo { 199 | pub agent: *mut L2CAgentBase, 200 | pub hash: Hash40 201 | } 202 | 203 | unsafe impl Sync for LoadedStatusAgentInfo {} 204 | unsafe impl Send for LoadedStatusAgentInfo {} 205 | 206 | lazy_static! { 207 | static ref GAME_CREATE_AGENTS: Mutex> = Mutex::new(HashMap::new()); 208 | static ref GAME_SHARE_CREATE_AGENTS: Mutex> = Mutex::new(HashMap::new()); 209 | 210 | static ref EFFECT_CREATE_AGENTS: Mutex> = Mutex::new(HashMap::new()); 211 | static ref EFFECT_SHARE_CREATE_AGENTS: Mutex> = Mutex::new(HashMap::new()); 212 | 213 | static ref SOUND_CREATE_AGENTS: Mutex> = Mutex::new(HashMap::new()); 214 | static ref SOUND_SHARE_CREATE_AGENTS: Mutex> = Mutex::new(HashMap::new()); 215 | 216 | static ref EXPRESSION_CREATE_AGENTS: Mutex> = Mutex::new(HashMap::new()); 217 | static ref EXPRESSION_SHARE_CREATE_AGENTS: Mutex> = Mutex::new(HashMap::new()); 218 | 219 | static ref STATUS_CREATE_AGENTS: Mutex> = Mutex::new(HashMap::new()); 220 | static ref STATUS_VTABLES: Mutex>> = Mutex::new(HashMap::new()); 221 | 222 | static ref LOADED_ACMD_AGENTS: Mutex> = Mutex::new(Vec::new()); 223 | static ref LOADED_STATUS_AGENTS: Mutex> = Mutex::new(Vec::new()); 224 | } 225 | 226 | fn get_movk_offset(instr: u32) -> u32 { 227 | (instr & 0x0060_0000) >> 21 228 | } 229 | 230 | pub fn read_hashes_from_executable(mut address: *const u32) -> Vec { 231 | let mut hashes = Vec::new(); 232 | let mut cpu = [0; 31]; 233 | 234 | unsafe { 235 | while let Some(instr) = aarch64_decode::decode_a64(*address) { 236 | match instr { 237 | Instr::Ret64RBranchReg => { 238 | break; 239 | }, 240 | Instr::Movz64Movewide { imm16: imm, Rd: dst, .. } => { 241 | cpu[dst as usize] = imm as u64; 242 | }, 243 | Instr::Movk64Movewide { imm16: part, Rd: dst, .. } => { 244 | let offset = get_movk_offset(*address) * 16; 245 | cpu[dst as usize] |= (part as u64) << offset; 246 | }, 247 | Instr::Subs64AddsubShift{ Rm: src, Rn: _, Rd: _, .. } => { 248 | hashes.push(Hash40::new_raw(cpu[src as usize])); 249 | }, 250 | _ => {} 251 | } 252 | address = address.add(1); 253 | } 254 | } 255 | 256 | let mut set = HashSet::new(); 257 | for hash in hashes.iter() { 258 | set.insert(hash.hash); 259 | } 260 | 261 | let hashes: Vec = set.iter().map(|x| Hash40::new_raw(*x)).collect(); 262 | for hash in hashes.iter() { 263 | // println!("{:#x}", hash.hash); 264 | } 265 | hashes 266 | } 267 | 268 | fn format_create_agent_symbol(fighter: &str, func: &str) -> String { 269 | let func_name = format!("create_agent_fighter_{}_{}", func, fighter); 270 | format!("_ZN7lua2cpp{}{}EN3phx6Hash40EPN3app12BattleObjectEPNS2_26BattleObjectModuleAccessorEP9lua_State", func_name.len(), func_name) 271 | } 272 | 273 | pub fn patch_create_agent_animcmd(info: &NroInfo, category: Category) { 274 | static mut ORIGINAL: *const extern "C" fn() = 0 as _; // to fulfill requirements of lazy_symbol_replace 275 | let mut original_opt: Option<&'static mut *const extern "C" fn()> = unsafe { Some(&mut ORIGINAL) }; 276 | macro_rules! patch_category { 277 | ($cat:ident, $unshared_create_agents:ident, $shared_create_agents:ident, $unshared:expr, $shared:expr) => { 278 | paste! { 279 | let $cat = format_create_agent_symbol(info.name, $unshared); 280 | let [<$cat _share>] = format_create_agent_symbol(info.name, $shared); 281 | unsafe { 282 | ORIGINAL = 0 as _; 283 | lazy_symbol_replace(info.module.ModuleObject as *mut ModuleObject, $cat.as_str(), [] as *const _, original_opt.as_mut()); 284 | if !ORIGINAL.is_null() { 285 | let mut map = $unshared_create_agents.lock(); 286 | map.insert( 287 | String::from(info.name), 288 | CreateAgentInfo { 289 | original: std::mem::transmute(ORIGINAL), 290 | hashes: read_hashes_from_executable(ORIGINAL as *const u32) 291 | } 292 | ); 293 | } 294 | ORIGINAL = 0 as _; 295 | lazy_symbol_replace(info.module.ModuleObject as *mut ModuleObject, [<$cat _share>].as_str(), [] as *const _, original_opt.as_mut()); 296 | if !ORIGINAL.is_null() { 297 | let mut map = $shared_create_agents.lock(); 298 | map.insert( 299 | String::from(info.name), 300 | CreateAgentInfo { 301 | original: std::mem::transmute(ORIGINAL), 302 | hashes: read_hashes_from_executable(ORIGINAL as *const u32) 303 | } 304 | ); 305 | } 306 | } 307 | } 308 | } 309 | } 310 | 311 | match category { 312 | ACMD_GAME => { 313 | patch_category!(game, GAME_CREATE_AGENTS, GAME_SHARE_CREATE_AGENTS, "animcmd_game", "animcmd_game_share"); 314 | }, 315 | ACMD_EFFECT => { 316 | patch_category!(effect, EFFECT_CREATE_AGENTS, EFFECT_SHARE_CREATE_AGENTS, "animcmd_effect", "animcmd_effect_share"); 317 | }, 318 | ACMD_SOUND => { 319 | patch_category!(sound, SOUND_CREATE_AGENTS, SOUND_SHARE_CREATE_AGENTS, "animcmd_sound", "animcmd_sound_share"); 320 | }, 321 | ACMD_EXPRESSION => { 322 | patch_category!(expression, EXPRESSION_CREATE_AGENTS, EXPRESSION_SHARE_CREATE_AGENTS, "animcmd_expression", "animcmd_expression_share"); 323 | } 324 | } 325 | } 326 | 327 | pub fn patch_create_agent_status(info: &NroInfo) { 328 | static mut ORIGINAL: *const extern "C" fn() = 0 as _; // to fulfill requirements of lazy_symbol_replace 329 | let mut original_opt: Option<&'static mut *const extern "C" fn()> = unsafe { Some(&mut ORIGINAL) }; 330 | let status = format_create_agent_symbol(info.name, "status_script"); 331 | unsafe { 332 | ORIGINAL = 0 as _; 333 | lazy_symbol_replace(info.module.ModuleObject as *mut ModuleObject, status.as_str(), create_agent_fighter_status_script as *const extern "C" fn(), original_opt.as_mut()); 334 | if !ORIGINAL.is_null() { 335 | let mut agents = STATUS_CREATE_AGENTS.lock(); 336 | agents.insert( 337 | String::from(info.name), 338 | CreateAgentInfo { 339 | original: std::mem::transmute(ORIGINAL), 340 | hashes: read_hashes_from_executable(ORIGINAL as *const u32) 341 | } 342 | ); 343 | } 344 | } 345 | } 346 | 347 | pub fn release_status_vtables(info: &NroInfo) { 348 | let name = String::from(info.name); 349 | let mut vtables = STATUS_VTABLES.lock(); 350 | if let Some(vtable_list) = vtables.remove(&name) { 351 | unsafe { 352 | let layout = std::alloc::Layout::from_size_align_unchecked(19 * 0x8, 0x8); 353 | for vtable in vtable_list.into_iter() { 354 | let vtable = vtable as *mut u8; 355 | std::alloc::dealloc(vtable, layout); 356 | } 357 | } 358 | } 359 | } 360 | 361 | pub fn install_live_acmd_scripts(agent_hash: Hash40, category: Category, info: &mut crate::acmd::ScriptInfo) { 362 | let agents = LOADED_ACMD_AGENTS.lock(); 363 | for agent in agents.iter() { 364 | if agent.hash == agent_hash && agent.category == category { 365 | unsafe { 366 | let test_func = *((*agent.agent).vtable as *const usize).add(1); 367 | let original_module = crate::nx::svc::query_memory(test_func).expect("Smashline unable to query mem info from live agent."); 368 | let og_begin = original_module.mem_info.base_address; 369 | let og_end = og_begin + original_module.mem_info.size; 370 | let current = *(*agent.agent).functions.get(&info.script).unwrap_or(&(0 as _)); 371 | if current == 0 as _ || (og_begin <= (current as usize) && (current as usize) < og_end) { 372 | if let Some(original) = info.original.as_mut() { 373 | **original = std::mem::transmute(current); 374 | } 375 | info.backup = std::mem::transmute(current); 376 | (*agent.agent).sv_set_function_hash(std::mem::transmute(info.bind_fn), info.script); 377 | } 378 | } 379 | } 380 | } 381 | } 382 | 383 | pub unsafe fn install_live_status_waza(agent_hash: Hash40, info: &mut crate::status::StatusWazaInfo) { 384 | let agents = LOADED_STATUS_AGENTS.lock(); 385 | for agent in agents.iter() { 386 | if agent.hash == agent_hash { 387 | let test_func = *((*agent.agent).vtable as *const usize).add(STATUS_DTOR); 388 | let original_module = crate::nx::svc::query_memory(test_func).expect("Smashline unable to query mem info from live agent."); 389 | let original = original_module.mem_info.base_address..(original_module.mem_info.base_address + original_module.mem_info.size); 390 | let current = (*(agent.agent as *mut smash::lua2cpp::L2CFighterCommon)).global_table[0x3D].get_ptr() as usize; 391 | if current == 0 || original.contains(¤t) { 392 | if let Some(original) = info.original.as_mut() { 393 | **original = std::mem::transmute(current); 394 | } 395 | info.backup = std::mem::transmute(current); 396 | (*(agent.agent as *mut smash::lua2cpp::L2CFighterCommon)).global_table[0x3D].assign(&L2CValue::Ptr(info.replacement as _)); 397 | } 398 | } 399 | } 400 | } 401 | 402 | pub unsafe fn install_live_status_scripts(agent_hash: Hash40, info: &mut crate::status::StatusInfo, common_module: &crate::nx::QueryMemoryResult, is_common: bool) { 403 | let agents = LOADED_STATUS_AGENTS.lock(); 404 | for agent in agents.iter() { 405 | if agent.hash == agent_hash || is_common { 406 | let test_func = *((*agent.agent).vtable as *const usize).add(STATUS_DTOR); 407 | let original_module = crate::nx::svc::query_memory(test_func).expect("Smashline unable to query mem info from live agent."); 408 | let common = common_module.mem_info.base_address..common_module.mem_info.base_address + common_module.mem_info.size; 409 | let original = original_module.mem_info.base_address..(original_module.mem_info.base_address + original_module.mem_info.size); 410 | let current = (*agent.agent).sv_get_status_func( 411 | &L2CValue::I32(info.status.get()), 412 | &L2CValue::I32(info.condition.get()) 413 | ).get_ptr() as usize; 414 | // println!("{:#x} {:#x?} {:#x?}", current, common, original); 415 | if current == 0 || common.contains(¤t) || (original.contains(¤t) && !is_common) { 416 | if let Some(original) = info.original.as_mut() { 417 | **original = std::mem::transmute(current); 418 | } 419 | info.backup = std::mem::transmute(current); 420 | (*agent.agent).sv_set_status_func( 421 | L2CValue::I32(info.status.get()), 422 | L2CValue::I32(info.condition.get()), 423 | std::mem::transmute(info.replacement) 424 | ); 425 | } 426 | } 427 | } 428 | } 429 | 430 | pub unsafe fn remove_live_acmd_scripts(range: (usize, usize)) { 431 | macro_rules! remove_scripts { 432 | ($agent:ident, $scripts:ident, $begin:ident, $end:ident) => { 433 | let scripts = $scripts.lock(); 434 | if let Some(script_list) = scripts.get(&$agent.hash) { 435 | for script in script_list.iter() { 436 | let as_usize = script.bind_fn as *const () as usize; 437 | if $begin <= as_usize && as_usize < $end { 438 | (*$agent.agent).sv_set_function_hash( 439 | std::mem::transmute(script.backup), 440 | script.script 441 | ); 442 | } 443 | } 444 | } 445 | } 446 | } 447 | 448 | let (begin, end) = range; 449 | let agents = LOADED_ACMD_AGENTS.lock(); 450 | for agent in agents.iter() { 451 | match agent.category { 452 | ACMD_GAME => { 453 | remove_scripts!(agent, GAME_SCRIPTS, begin, end); 454 | }, 455 | ACMD_EFFECT => { 456 | remove_scripts!(agent, EFFECT_SCRIPTS, begin, end); 457 | }, 458 | ACMD_SOUND => { 459 | remove_scripts!(agent, SOUND_SCRIPTS, begin, end); 460 | }, 461 | ACMD_EXPRESSION => { 462 | remove_scripts!(agent, EXPRESSION_SCRIPTS, begin, end); 463 | } 464 | } 465 | } 466 | } 467 | 468 | pub unsafe fn remove_live_status_waza(range: (usize, usize)) { 469 | let (begin, end) = range; 470 | let agents = LOADED_STATUS_AGENTS.lock(); 471 | let wazas = STATUS_CUSTOMIZERS.lock(); 472 | for agent in agents.iter() { 473 | if let Some(waza) = wazas.get(&agent.hash) { 474 | (*(agent.agent as *mut smash::lua2cpp::L2CFighterCommon)).global_table[0x3D].assign(&L2CValue::Ptr(waza.backup as _)); 475 | } 476 | } 477 | } 478 | 479 | pub unsafe fn remove_live_status_scripts(range: (usize, usize)) { 480 | let (begin, end) = range; 481 | let agents = LOADED_STATUS_AGENTS.lock(); 482 | let mut scripts = STATUS_SCRIPTS.lock(); 483 | let mut common_scripts = COMMON_STATUS_SCRIPTS.lock(); 484 | for agent in agents.iter() { 485 | if let Some(script_list) = scripts.get_mut(&agent.hash) { 486 | for script in script_list.iter_mut() { 487 | let as_usize = script.replacement as *const () as usize; 488 | if begin <= as_usize && as_usize < end { 489 | (*agent.agent).sv_set_status_func( 490 | L2CValue::I32(script.status.get()), 491 | L2CValue::I32(script.condition.get()), 492 | std::mem::transmute(script.backup) 493 | ); 494 | } 495 | } 496 | } 497 | if let Some(script_list) = common_scripts.get_mut(&Hash40::new("common")) { 498 | for script in script_list.iter_mut () { 499 | let as_usize = script.replacement as *const () as usize; 500 | if begin <= as_usize && as_usize < end { 501 | (*agent.agent).sv_set_status_func( 502 | L2CValue::I32(script.status.get()), 503 | L2CValue::I32(script.condition.get()), 504 | std::mem::transmute(script.backup) 505 | ); 506 | } 507 | } 508 | } 509 | } 510 | } 511 | 512 | pub fn clear_loaded_agent(info: &NroInfo) { 513 | let possible_agent_hashes = [ 514 | GAME_CREATE_AGENTS.lock(), 515 | GAME_SHARE_CREATE_AGENTS.lock(), 516 | EFFECT_CREATE_AGENTS.lock(), 517 | EFFECT_SHARE_CREATE_AGENTS.lock(), 518 | SOUND_CREATE_AGENTS.lock(), 519 | SOUND_SHARE_CREATE_AGENTS.lock(), 520 | EXPRESSION_CREATE_AGENTS.lock(), 521 | EXPRESSION_SHARE_CREATE_AGENTS.lock() 522 | ]; 523 | let module_name = String::from(info.name); 524 | for hashes in possible_agent_hashes.iter() { 525 | if let Some(agent_hashes) = hashes.get(&module_name) { 526 | for hash in agent_hashes.hashes.iter() { 527 | let mut loaded_agents = LOADED_ACMD_AGENTS.lock(); 528 | let mut new_agents = Vec::new(); 529 | for agent in loaded_agents.iter() { 530 | if agent.hash != *hash { 531 | new_agents.push(*agent); 532 | } 533 | } 534 | *loaded_agents = new_agents; 535 | let mut loaded_agents = LOADED_STATUS_AGENTS.lock(); 536 | let mut new_agents = Vec::new(); 537 | for agent in loaded_agents.iter() { 538 | if agent.hash != *hash { 539 | new_agents.push(*agent); 540 | } 541 | } 542 | *loaded_agents = new_agents; 543 | } 544 | } 545 | } 546 | } -------------------------------------------------------------------------------- /src/status.rs: -------------------------------------------------------------------------------- 1 | use smash::lib::{lua_const::*, LuaConst, L2CValue}; 2 | use smash::phx::Hash40; 3 | use smash::lua2cpp::*; 4 | use std::collections::HashMap; 5 | use parking_lot::Mutex; 6 | use skyline::nro::NroInfo; 7 | use crate::LuaConstant; 8 | 9 | lazy_static! { 10 | pub static ref STATUS_SCRIPTS: Mutex>> = Mutex::new(HashMap::new()); 11 | pub static ref COMMON_STATUS_SCRIPTS: Mutex>> = Mutex::new(HashMap::new()); 12 | pub static ref STATUS_CUSTOMIZERS: Mutex> = Mutex::new(HashMap::new()); 13 | } 14 | 15 | pub struct StatusInfo { 16 | pub status: LuaConstant, 17 | pub condition: LuaConstant, 18 | pub original: Option<&'static mut *const extern "C" fn()>, 19 | pub low_priority: bool, 20 | pub replacement: *const extern "C" fn(), 21 | pub backup: *const extern "C" fn() 22 | } 23 | 24 | pub struct StatusWazaInfo { 25 | pub replacement: *const extern "C" fn(), 26 | pub original: Option<&'static mut *const extern "C" fn()>, 27 | pub backup: *const extern "C" fn(), 28 | pub low_priority: bool 29 | } 30 | 31 | impl StatusInfo { 32 | pub fn transfer(&mut self) -> Self { 33 | Self { 34 | status: self.status.clone(), 35 | condition: self.condition.clone(), 36 | original: self.original.take(), 37 | low_priority: self.low_priority, 38 | replacement: self.replacement, 39 | backup: self.backup 40 | } 41 | } 42 | } 43 | 44 | impl StatusWazaInfo { 45 | pub fn transfer(&mut self) -> Self { 46 | Self { 47 | replacement: self.replacement, 48 | original: self.original.take(), 49 | low_priority: self.low_priority, 50 | backup: self.backup 51 | } 52 | } 53 | } 54 | 55 | unsafe impl Sync for StatusInfo {} 56 | unsafe impl Send for StatusInfo {} 57 | 58 | unsafe impl Sync for StatusWazaInfo {} 59 | unsafe impl Send for StatusWazaInfo {} 60 | 61 | static mut CONSTANT_RESOLVER: Option bool> = None; 62 | 63 | fn const_resolver(this: &mut LuaConstant, that: &mut LuaConstant) -> bool { 64 | let this = this.get(); 65 | let that = that.get(); 66 | this == that 67 | } 68 | 69 | static mut ORIGINAL: *const extern "C" fn() = 0 as _; 70 | 71 | extern "C" fn sub_set_fighter_common_table_replace(fighter: &mut L2CFighterCommon) { 72 | let original: extern "C" fn(&mut L2CFighterCommon) = unsafe { std::mem::transmute(ORIGINAL) }; 73 | original(fighter); 74 | let mut scripts = COMMON_STATUS_SCRIPTS.lock(); 75 | if let Some(script_list) = scripts.get_mut(&Hash40::new("common")) { 76 | for script in script_list.iter_mut() { 77 | if script.status.get() != FIGHTER_STATUS_KIND_NONE { 78 | unsafe { 79 | let og = fighter.sv_get_status_func( 80 | &L2CValue::I32(script.status.get()), 81 | &L2CValue::I32(script.condition.get()), 82 | ).get_ptr() as *const extern "C" fn(); 83 | if let Some(original) = script.original.as_mut() { 84 | **original = og; 85 | } 86 | script.backup = og; 87 | fighter.sv_set_status_func( 88 | L2CValue::I32(script.status.get()), 89 | L2CValue::I32(script.condition.get()), 90 | std::mem::transmute(script.replacement) 91 | ); 92 | } 93 | } 94 | } 95 | } 96 | } 97 | 98 | pub fn install() { 99 | unsafe { 100 | crate::hooks::replace_symbol("common", "_ZN7lua2cpp16L2CFighterCommon28sub_set_fighter_common_tableEv", sub_set_fighter_common_table_replace as *const extern "C" fn(), Some(&mut ORIGINAL)); 101 | } 102 | } 103 | 104 | pub fn nro_load(info: &NroInfo) { 105 | match info.name { 106 | "common" => { 107 | // On common load we need to resolve all of the statuses added before the const table was filled in 108 | // this way people can just do a "one and done" approach like they can with status scripts 109 | let script_locks = &mut [ 110 | STATUS_SCRIPTS.lock(), 111 | COMMON_STATUS_SCRIPTS.lock() 112 | ]; 113 | unsafe { 114 | CONSTANT_RESOLVER = Some(const_resolver); 115 | } 116 | for lock in script_locks.iter_mut() { 117 | for (agent, info) in lock.iter_mut() { 118 | let mut high_priority: Vec = Vec::new(); 119 | let mut low_priority = Vec::new(); 120 | for status_info in info.iter_mut() { 121 | if status_info.low_priority { 122 | low_priority.push(status_info.transfer()); 123 | } else { 124 | let mut is_unique = true; 125 | for high_info in high_priority.iter_mut() { 126 | if const_resolver(&mut high_info.status, &mut status_info.status) && const_resolver(&mut high_info.condition, &mut status_info.condition) { 127 | println!("[smashline::status] Status script already replaced with high priority | Status: {:#x}, condition: {:#x}", high_info.status.get(), high_info.condition.get()); 128 | is_unique = false; 129 | break; 130 | } 131 | } 132 | if is_unique { 133 | high_priority.push(status_info.transfer()); 134 | } 135 | } 136 | } 137 | 138 | // I'm sorry y'all, it has to be done 139 | let mut output: Vec = Vec::new(); 140 | 141 | for mut low_info in low_priority.into_iter() { 142 | let mut is_unique = true; 143 | for high_info in high_priority.iter_mut() { 144 | if const_resolver(&mut high_info.status, &mut low_info.status) && const_resolver(&mut high_info.condition, &mut low_info.condition) { 145 | println!("[smashline::status] Status script already replaced with high priority | Status: {:#x}, condition: {:#x}", high_info.status.get(), high_info.condition.get()); 146 | is_unique = false; 147 | break; 148 | } 149 | } 150 | if is_unique { 151 | for output_info in output.iter_mut() { 152 | if const_resolver(&mut low_info.status, &mut output_info.status) && const_resolver(&mut low_info.condition, &mut output_info.status) { 153 | *output_info = low_info; 154 | break; 155 | } 156 | } 157 | } 158 | } 159 | output.reserve(high_priority.len()); 160 | for info in high_priority.into_iter() { 161 | output.push(info); 162 | } 163 | *info = output; 164 | } 165 | } 166 | }, 167 | "item" | "" => {}, 168 | _ => { 169 | crate::scripts::patch_create_agent_status(info); 170 | } 171 | } 172 | } 173 | 174 | pub fn nro_unload(info: &NroInfo) { 175 | crate::scripts::release_status_vtables(info); 176 | } 177 | 178 | pub unsafe fn remove_status_scripts(range: (usize, usize)) { 179 | crate::scripts::remove_live_status_scripts(range); 180 | crate::scripts::remove_live_status_waza(range); 181 | 182 | let range = range.0..range.1; 183 | let mut scripts = STATUS_SCRIPTS.lock(); 184 | let mut common_scripts = COMMON_STATUS_SCRIPTS.lock(); 185 | for (_, script_list) in scripts.iter_mut() { 186 | let mut new_script_list = Vec::with_capacity(script_list.len()); 187 | for script in script_list.iter_mut() { 188 | if !range.contains(&(script.replacement as usize)) { 189 | new_script_list.push(script.transfer()); 190 | } 191 | } 192 | *script_list = new_script_list; 193 | } 194 | for (_, script_list) in common_scripts.iter_mut() { 195 | let mut new_script_list = Vec::with_capacity(script_list.len()); 196 | for script in script_list.iter_mut() { 197 | if !range.contains(&(script.replacement as usize)) { 198 | new_script_list.push(script.transfer()); 199 | } 200 | } 201 | *script_list = new_script_list; 202 | } 203 | } 204 | 205 | #[no_mangle] 206 | pub extern "Rust" fn replace_move_customizer(agent: Hash40, original: Option<&'static mut *const extern "C" fn()>, low_priority: bool, replacement: *const extern "C" fn()) { 207 | let mut info = StatusWazaInfo { 208 | original, 209 | replacement, 210 | low_priority, 211 | backup: 0 as _ 212 | }; 213 | 214 | let mut customizers = STATUS_CUSTOMIZERS.lock(); 215 | 216 | unsafe { 217 | crate::scripts::install_live_status_waza(agent, &mut info); 218 | } 219 | 220 | if let Some(waza_info) = customizers.get_mut(&agent) { 221 | if (waza_info.low_priority) { 222 | *waza_info = info.transfer(); 223 | } else { 224 | println!("[smashline::status] Status specializer (WAZA Customizer) has already been replaced and is not low priority | Agent: {:#x}", agent.hash); 225 | } 226 | } else { 227 | customizers.insert(agent, info); 228 | } 229 | } 230 | 231 | #[no_mangle] 232 | pub extern "Rust" fn replace_status_script(agent: Hash40, status: LuaConstant, condition: LuaConstant, original: Option<&'static mut *const extern "C" fn()>, low_priority: bool, replacement: *const extern "C" fn()) { 233 | let mut info = StatusInfo { 234 | status, 235 | condition, 236 | original, 237 | low_priority, 238 | replacement, 239 | backup: 0 as _ 240 | }; 241 | 242 | let mut scripts = STATUS_SCRIPTS.lock(); 243 | unsafe { 244 | if let Some(common_module) = crate::COMMON_MEMORY_INFO.as_ref() { 245 | crate::scripts::install_live_status_scripts(agent, &mut info, common_module, false); 246 | } 247 | } 248 | 249 | if let Some(script_list) = scripts.get_mut(&agent) { 250 | unsafe { 251 | if let Some(resolver) = CONSTANT_RESOLVER.as_ref() { 252 | for script in script_list.iter_mut() { 253 | if (resolver)(&mut script.status, &mut info.status) && (resolver)(&mut script.condition, &mut info.condition) { 254 | if script.low_priority { 255 | *script = info; 256 | } else { 257 | println!("[smashline::status] Status script already replaced with high priority | Status: {:#x}, condition: {:#x}", script.status.get(), script.condition.get()); 258 | } 259 | return; 260 | } 261 | } 262 | } 263 | script_list.push(info); 264 | } 265 | } else { 266 | scripts.insert(agent, vec![info]); 267 | } 268 | } 269 | 270 | #[no_mangle] 271 | pub extern "Rust" fn replace_common_status_script(status: LuaConstant, condition: LuaConstant, original: Option<&'static mut *const extern "C" fn()>, replacement: *const extern "C" fn()) { 272 | let mut info = StatusInfo { 273 | status, 274 | condition, 275 | original, 276 | low_priority: false, 277 | replacement, 278 | backup: 0 as _ 279 | }; 280 | 281 | let mut scripts = COMMON_STATUS_SCRIPTS.lock(); 282 | unsafe { 283 | if let Some(common_module) = crate::COMMON_MEMORY_INFO.as_ref() { 284 | crate::scripts::install_live_status_scripts(Hash40::new("common"), &mut info, common_module, true); 285 | } 286 | } 287 | if let Some(script_list) = scripts.get_mut(&Hash40::new("common")) { 288 | unsafe { 289 | if let Some(resolver) = CONSTANT_RESOLVER.as_ref() { 290 | for script in script_list.iter_mut() { 291 | if (resolver)(&mut script.status, &mut info.status) && (resolver)(&mut script.condition, &mut info.condition) { 292 | if script.low_priority { 293 | *script = info; 294 | } else { 295 | println!("[smashline::status] Common status script already replaced with high priority | Status: {:#x}, condition: {:#x}", script.status.get(), script.condition.get()); 296 | } 297 | return; 298 | } 299 | } 300 | } 301 | script_list.push(info); 302 | } 303 | } else { 304 | scripts.insert(Hash40::new("common"), vec![info]); 305 | } 306 | } -------------------------------------------------------------------------------- /src/unwind.rs: -------------------------------------------------------------------------------- 1 | use skyline::{nn, libc}; 2 | use skyline::hooks::InlineCtx; 3 | 4 | use parking_lot::Mutex; 5 | 6 | use crate::c_str; 7 | use crate::nx::{self, svc}; 8 | 9 | const UNW_STEP_END: u64 = 0; 10 | const UNW_STEP_SUCCESS: u64 = 1; 11 | 12 | const _UA_SEARCH_PHASE: u64 = 1; 13 | 14 | const _URC_FATAL_PHASE2_ERROR: u64 = 2; 15 | const _URC_FATAL_PHASE1_ERROR: u64 = 3; 16 | const _URC_HANDLER_FOUND: u64 = 6; 17 | const _URC_INSTALL_CONTEXT: u64 = 7; 18 | 19 | extern "C" { 20 | #[link_name = "\u{1}_Unwind_GetIP"] 21 | fn _Unwind_GetIP(context: *const u64) -> u64; 22 | #[link_name = "\u{1}_Unwind_SetIP"] 23 | fn _Unwind_SetIP(context: *const u64, ip: u64); 24 | } 25 | 26 | static STEP_WITH_DWARF_SEARCH_CODE: &[u8] = &[ 27 | 0xfc, 0x0f, 0x1a, 0xf8, 0xfa, 0x67, 0x01, 0xa9, 28 | 0xf8, 0x5f, 0x02, 0xa9, 0xf6, 0x57, 0x03, 0xa9, 29 | 0xf4, 0x4f, 0x04, 0xa9, 0xfd, 0x7b, 0x05, 0xa9, 30 | 0xfd, 0x43, 0x01, 0x91, 0xff, 0x83, 0x22, 0xd1, 31 | 0xe8, 0x03, 0x02, 0xaa, 0xf3, 0x03, 0x03, 0xaa, 32 | 0xf5, 0x03, 0x01, 0xaa, 0xa2, 0x03, 0x02, 0xd1 33 | ]; 34 | 35 | static SET_INFO_BASED_ON_IP_SEARCH_CODE: &[u8] = &[ 36 | 0xfc, 0x57, 0xbd, 0xa9, 0xf4, 0x4f, 0x01, 0xa9, 37 | 0xfd, 0x7b, 0x02, 0xa9, 0xfd, 0x83, 0x00, 0x91, 38 | 0xff, 0x03, 0x1b, 0xd1, 0x08, 0x00, 0x40, 0xf9, 39 | 0xf4, 0x03, 0x01, 0x2a 40 | ]; 41 | 42 | static UNWIND_CURSOR_STEP_SEARCH_CODE: &[u8] = &[ 43 | 0xf4, 0x4f, 0xbe, 0xa9, 0xfd, 0x7b, 0x01, 0xa9, 44 | 0xfd, 0x43, 0x00, 0x91, 0x08, 0xa0, 0x49, 0x39, 45 | 0xc8, 0x00, 0x00, 0x34 46 | ]; 47 | 48 | static BAD_INFO_CHECK_SEARCH_CODE: &[u8] = &[ 49 | 0x00, 0x01, 0x3f, 0xd6, 0x68, 0x02, 0x40, 0xf9, 50 | 0xe0, 0x03, 0x13, 0xaa, 0xe1, 0x03, 0x1f, 0x2a, 51 | 0x08, 0x35, 0x40, 0xf9, 0x00, 0x01, 0x3f, 0xd6 52 | ]; 53 | 54 | static mut STEP_WITH_DWARF: *const extern "C" fn(*mut u64, u64, *mut u64, *mut u64) -> u64 = 0 as _; 55 | static mut SET_INFO_BASED_ON_IP_REGISTER: *const extern "C" fn(*mut u64, bool) = 0 as _; 56 | static mut UNWIND_CURSOR_STEP_ADDRESS: usize = 0; 57 | static mut BAD_INFO_CHECK_ADDRESS: usize = 0; 58 | 59 | static OFFSET_INIT: std::sync::Once = std::sync::Once::new(); 60 | 61 | lazy_static! { 62 | static ref CUSTOM_EH_MEM: Mutex> = Mutex::new(Vec::new()); 63 | } 64 | 65 | #[skyline::hook(replace = libc::abort)] 66 | fn abort_hook() -> ! { 67 | println!("[smashline::unwind | Fatal Error] abort() has been called. Flushing logger."); 68 | std::thread::sleep(std::time::Duration::from_millis(500)); 69 | 70 | call_original!() 71 | } 72 | 73 | #[skyline::hook(replace = libc::fwrite)] 74 | fn fwrite_hook(c_str: *const libc::c_char, size: libc::size_t, count: libc::size_t, file: *mut libc::c_void) -> libc::size_t { 75 | unsafe { 76 | print!("{}", skyline::from_c_str(c_str)); 77 | } 78 | 79 | call_original!(c_str, size, count, file) 80 | } 81 | 82 | unsafe fn step_with_dwarf(address_space: *mut u64, ip: u64, unwind_info: *mut u64, registers: *mut u64) -> u64 { 83 | if STEP_WITH_DWARF.is_null() { 84 | panic!("step_with_dwarf is null."); 85 | } 86 | let callable: extern "C" fn(*mut u64, u64, *mut u64, *mut u64) -> u64 = std::mem::transmute(STEP_WITH_DWARF); 87 | callable(address_space, ip, unwind_info, registers) 88 | } 89 | 90 | unsafe fn set_info_based_on_ip_register(arg1: *mut u64, arg2: bool) { 91 | if SET_INFO_BASED_ON_IP_REGISTER.is_null() { 92 | panic!("set_info_based_on_ip_register is null."); 93 | } 94 | let callable: extern "C" fn(*mut u64, bool) = std::mem::transmute(SET_INFO_BASED_ON_IP_REGISTER); 95 | callable(arg1, arg2) 96 | } 97 | 98 | fn is_custom_eh_mem(ip: usize) -> Option<(usize, usize)> { 99 | let custom_mems = CUSTOM_EH_MEM.lock(); 100 | for mem in custom_mems.iter() { 101 | if mem.mem_info.base_address <= ip && ip < (mem.mem_info.base_address + mem.mem_info.size) { 102 | return Some((mem.mem_info.base_address, mem.mem_info.base_address + mem.mem_info.size)); 103 | } 104 | } 105 | None 106 | } 107 | 108 | unsafe fn byte_search(start: *const u32, want: u32, distance: usize) -> Option<*const u32> { 109 | for x in 0..distance { 110 | let cur = start.add(x); 111 | if *cur == want { 112 | return Some(cur); 113 | } 114 | } 115 | None 116 | } 117 | 118 | #[allow(unused_assignments)] 119 | unsafe extern "C" fn custom_eh_personality(version: i32, actions: u64, _: u64, _: *mut u64, context: *mut u64) -> u64 { 120 | let mut ret = _URC_FATAL_PHASE1_ERROR; 121 | if version != 1 { 122 | panic!("Custom EH personality routine called in the wrong context."); 123 | } 124 | if actions & _UA_SEARCH_PHASE != 0 { 125 | ret = _URC_HANDLER_FOUND; 126 | } else { 127 | let ip = _Unwind_GetIP(context); 128 | if is_custom_eh_mem(ip as usize).is_none() { 129 | panic!("Custom EH personality routine said it found an exception handler, but it is not inside a skyline plugin."); 130 | } 131 | let unwind_info_ptr = context.add(0x44); 132 | let landing_pad = *unwind_info_ptr.add(1) + 4; 133 | if is_custom_eh_mem(landing_pad as usize).is_none() { 134 | panic!("Custom EH personality routine found landing pad, but it is not inside a skyline plugin."); 135 | } 136 | _Unwind_SetIP(context, landing_pad); 137 | ret = _URC_INSTALL_CONTEXT; 138 | } 139 | ret 140 | } 141 | 142 | #[skyline::hook(replace = UNWIND_CURSOR_STEP_ADDRESS)] 143 | unsafe fn step_replace(this: *mut u64) -> u64 { 144 | if *(this as *const bool).offset(0x268) { 145 | return UNW_STEP_END; 146 | } 147 | 148 | let address_space = *this.add(1) as *mut u64; 149 | let ip = _Unwind_GetIP(this); 150 | let unwind_info = *this.add(0x4B) as *mut u64; 151 | let registers = this.add(2); 152 | 153 | let result = step_with_dwarf(address_space, ip, unwind_info, registers); 154 | if result == UNW_STEP_SUCCESS { 155 | let ip = _Unwind_GetIP(this) as usize; 156 | if let Some(range) = is_custom_eh_mem(ip) { 157 | let pc_end = match byte_search(ip as *const u32, 0xB000B1E5, 0x2000) { 158 | Some(val) => val, 159 | None => { 160 | panic!("Stack unwinding passing through skyline plugin with no eh marker. Address range: ({:#x} - {:#x})", range.0, range.1); 161 | } 162 | }; 163 | let unwind_info_ptr = this.add(0x44); 164 | *unwind_info_ptr.add(0) = ip as u64; 165 | *unwind_info_ptr.add(1) = pc_end as u64; 166 | *unwind_info_ptr.add(3) = std::mem::transmute(custom_eh_personality as *const ()); 167 | } else { 168 | set_info_based_on_ip_register(this, true); 169 | if *(this as *const bool).offset(0x268) { 170 | return UNW_STEP_END; 171 | } 172 | } 173 | } 174 | 175 | result 176 | } 177 | 178 | #[skyline::hook(replace = BAD_INFO_CHECK_ADDRESS, inline)] 179 | unsafe fn prevent_bad_info_check(ctx: &mut InlineCtx) { 180 | fn stub() {} 181 | let ip = _Unwind_GetIP(*ctx.registers[0].x.as_ref() as *const u64) as usize; 182 | if let Some(_) = is_custom_eh_mem(ip) { 183 | *ctx.registers[8].x.as_mut() = std::mem::transmute(stub as *const ()); 184 | } 185 | } 186 | 187 | fn find_subsequence(haystack: &[u8], needle: &[u8]) -> Option { 188 | haystack 189 | .windows(needle.len()) 190 | .position(|window| window == needle) 191 | } 192 | 193 | #[inline(never)] 194 | pub fn register_skyline_plugin(addr: usize) { 195 | let mem_info = svc::query_memory(addr).expect("Unable to query memory for skyline plugin."); 196 | let mut custom_mem = CUSTOM_EH_MEM.lock(); 197 | for plugin in custom_mem.iter() { 198 | if plugin.mem_info.base_address == mem_info.mem_info.base_address { 199 | return; 200 | } 201 | } 202 | custom_mem.push(mem_info); 203 | } 204 | 205 | #[inline(never)] 206 | pub fn unregister_skyline_plugin(base_addr: usize) { 207 | let mut custom_mem = CUSTOM_EH_MEM.lock(); 208 | let mut new_mems = Vec::with_capacity(custom_mem.len()); 209 | for plugin in custom_mem.iter() { 210 | if plugin.mem_info.base_address != base_addr { 211 | new_mems.push(*plugin); 212 | } 213 | } 214 | *custom_mem = new_mems; 215 | } 216 | 217 | pub fn install() { 218 | OFFSET_INIT.call_once(|| { 219 | unsafe { 220 | let mut unwind_resume = 0usize; 221 | let result = nn::ro::LookupSymbol(&mut unwind_resume, c_str!("_Unwind_Resume")); 222 | if result != 0 || unwind_resume == 0 { 223 | panic!("Failed to lookup symbol for \"_Unwind_Resume\""); 224 | } 225 | 226 | let nnsdk_memory = svc::query_memory(unwind_resume).expect("Failed to locate the start of nnSdk in memory."); 227 | let nnsdk_text_range = std::slice::from_raw_parts(nnsdk_memory.mem_info.base_address as *const u8, nnsdk_memory.mem_info.size); 228 | 229 | STEP_WITH_DWARF = (find_subsequence(nnsdk_text_range, STEP_WITH_DWARF_SEARCH_CODE).expect("Unable to locate stepWithDwarf in nnSdk.") + nnsdk_memory.mem_info.base_address) as _; 230 | SET_INFO_BASED_ON_IP_REGISTER = (find_subsequence(nnsdk_text_range, SET_INFO_BASED_ON_IP_SEARCH_CODE).expect("Unable to locate setInfoBasedOnIPRegister in nnSdk.") + nnsdk_memory.mem_info.base_address) as _; 231 | UNWIND_CURSOR_STEP_ADDRESS = find_subsequence(nnsdk_text_range, UNWIND_CURSOR_STEP_SEARCH_CODE).expect("Unable to locate UnwindCursor::Step in nnSdk.") + nnsdk_memory.mem_info.base_address; 232 | BAD_INFO_CHECK_ADDRESS = find_subsequence(nnsdk_text_range, BAD_INFO_CHECK_SEARCH_CODE).expect("Unable to locate badInfoCheck in nnSdk.") + nnsdk_memory.mem_info.base_address + 0x14; 233 | } 234 | }); 235 | 236 | skyline::install_hooks!( 237 | abort_hook, 238 | fwrite_hook, 239 | step_replace, 240 | prevent_bad_info_check 241 | ); 242 | } --------------------------------------------------------------------------------