Initial commit

This commit is contained in:
Léo Lam
2019-06-27 23:12:50 +02:00
commit c6859d7e01
45 changed files with 6237 additions and 0 deletions

76
.clang-format Normal file
View File

@@ -0,0 +1,76 @@
---
Language: Cpp
AccessModifierOffset: -2
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlinesLeft: false
AlignOperands: true
AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Inline
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: true
BinPackArguments: true
BinPackParameters: true
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Attach
BreakBeforeTernaryOperators: false
BreakConstructorInitializersBeforeComma: false
ColumnLimit: 100
CommentPragmas: '^ (IWYU pragma:|NOLINT)'
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: false
DisableFormat: false
ForEachMacros: []
IncludeCategories:
- Regex: '^<[Ww]indows\.h>$'
Priority: 1
- Regex: '^<'
Priority: 2
- Regex: '^"'
Priority: 3
IndentCaseLabels: false
IndentWidth: 2
IndentWrappedFunctionNames: false
KeepEmptyLinesAtTheStartOfBlocks: false
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBlockIndentWidth: 2
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Left
ReflowComments: true
SortIncludes: true
SpaceAfterCStyleCast: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeParens: ControlStatements
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 2
SpacesInAngles: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Cpp11
TabWidth: 2
UseTab: Never
...

20
.gitignore vendored Normal file
View File

@@ -0,0 +1,20 @@
# Build files
*.bin
*.o
*.x
*.elf
*.sym
*.d
*.map
newcodeinfo.h
build/
hooks/
bak/
/source/Version.cmake
# IDA
*.id*
*.nam
*.til
release/

66
DevkitArm3DS.cmake Normal file
View File

@@ -0,0 +1,66 @@
# https://github.com/Lectem/3ds-cmake
# Licensed under MIT
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR armv6k)
set(3DS TRUE) # To be used for multiplatform projects
# DevkitPro Paths are broken on windows, so we have to fix those
macro(msys_to_cmake_path MsysPath ResultingPath)
if(WIN32)
string(REGEX REPLACE "^/([a-zA-Z])/" "\\1:/" ${ResultingPath} "${MsysPath}")
else()
set(${ResultingPath} "${MsysPath}")
endif()
endmacro()
msys_to_cmake_path("$ENV{DEVKITPRO}" DEVKITPRO)
if(NOT IS_DIRECTORY ${DEVKITPRO})
message(FATAL_ERROR "Please set DEVKITPRO in your environment")
endif()
msys_to_cmake_path("$ENV{DEVKITARM}" DEVKITARM)
if(NOT IS_DIRECTORY ${DEVKITARM})
message(FATAL_ERROR "Please set DEVKITARM in your environment")
endif()
# Prefix detection only works with compiler id "GNU"
# CMake will look for prefixed g++, cpp, ld, etc. automatically
if(WIN32)
set(CMAKE_C_COMPILER "${DEVKITARM}/bin/arm-none-eabi-gcc.exe")
set(CMAKE_CXX_COMPILER "${DEVKITARM}/bin/arm-none-eabi-g++.exe")
set(CMAKE_AR "${DEVKITARM}/bin/arm-none-eabi-gcc-ar.exe" CACHE STRING "")
set(CMAKE_RANLIB "${DEVKITARM}/bin/arm-none-eabi-gcc-ranlib.exe" CACHE STRING "")
else()
set(CMAKE_C_COMPILER "${DEVKITARM}/bin/arm-none-eabi-gcc")
set(CMAKE_CXX_COMPILER "${DEVKITARM}/bin/arm-none-eabi-g++")
set(CMAKE_AR "${DEVKITARM}/bin/arm-none-eabi-gcc-ar" CACHE STRING "")
set(CMAKE_RANLIB "${DEVKITARM}/bin/arm-none-eabi-gcc-ranlib" CACHE STRING "")
endif()
set(WITH_PORTLIBS ON CACHE BOOL "use portlibs ?")
if(WITH_PORTLIBS)
set(CMAKE_FIND_ROOT_PATH ${DEVKITARM} ${DEVKITPRO} ${DEVKITPRO}/portlibs/3ds ${DEVKITPRO}/portlibs/armv6k)
else()
set(CMAKE_FIND_ROOT_PATH ${DEVKITARM} ${DEVKITPRO})
endif()
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
SET(BUILD_SHARED_LIBS OFF CACHE INTERNAL "Shared libs not available" )
add_definitions(-DARM11 -D_3DS)
set(ARCH "-march=armv6k -mtune=mpcore -mfloat-abi=hard -mtp=soft ")
set(CMAKE_C_FLAGS " -mword-relocations ${ARCH}" CACHE STRING "C flags")
set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS}" CACHE STRING "C++ flags")
set(DKA_SUGGESTED_C_FLAGS "-fomit-frame-pointer")
set(DKA_SUGGESTED_CXX_FLAGS "${DKA_SUGGESTED_C_FLAGS} -fno-rtti -fno-exceptions -std=gnu++11")
set(CMAKE_INSTALL_PREFIX ${DEVKITPRO}/portlibs/3ds
CACHE PATH "Install libraries in the portlibs dir")

17
Makefile Normal file
View File

@@ -0,0 +1,17 @@
export SHELL:=/bin/bash
export SHELLOPTS:=$(if $(SHELLOPTS),$(SHELLOPTS):)pipefail:errexit
.PHONY: build
.ONESHELL:
build:
cd source
mkdir build || true
cd build
cmake .. -GNinja -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_TOOLCHAIN_FILE=../../DevkitArm3DS.cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCODEADDR=$(CODEADDR)
ninja
cp *.sym *.bin ../../
clean:
cd source
rm -rf build

336
license.md Normal file
View File

@@ -0,0 +1,336 @@
GNU General Public License
==========================
_Version 2, June 1991_
_Copyright © 1989, 1991 Free Software Foundation, Inc.,_
_51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA_
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
### Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: **(1)** copyright the software, and
**(2)** offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
### TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
**0.** This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The “Program”, below,
refers to any such program or work, and a “work based on the Program”
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term “modification”.) Each licensee is addressed as “you”.
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
**1.** You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
**2.** You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
* **a)** You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
* **b)** You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
* **c)** If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
**3.** You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
* **a)** Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
* **b)** Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
* **c)** Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
**4.** You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
**5.** You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
**6.** Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
**7.** If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
**8.** If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
**9.** The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and “any
later version”, you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
**10.** If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
### NO WARRANTY
**11.** BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
**12.** IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
### How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the “copyright” line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w` and `show c` should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w` and `show c`; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a “copyright disclaimer” for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

24
loader/CMakeLists.txt Normal file
View File

@@ -0,0 +1,24 @@
cmake_minimum_required(VERSION 3.13)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
project(loader C ASM)
add_executable(loader
source/loader.c
source/svc.s
)
set_target_properties(loader PROPERTIES SUFFIX ".elf")
target_compile_options(loader PRIVATE -Os -fomit-frame-pointer -ffunction-sections)
if (NOT DEFINED CODEADDR OR NOT DEFINED DATAADDR)
message(FATAL_ERROR "Set CODEADDR and DATAADDR")
endif()
configure_file(linker_template.ld linker.ld)
target_link_options(loader PRIVATE "-Wl,-Tlinker.ld")
add_custom_target(loader_product ALL
DEPENDS loader
COMMAND ${CMAKE_OBJCOPY} -O binary loader.elf loader.bin
COMMAND sh -c "${CMAKE_OBJDUMP} -t loader.elf>loader.sym"
BYPRODUCTS loader.bin loader.sym
)

15
loader/Makefile Normal file
View File

@@ -0,0 +1,15 @@
export SHELL:=/bin/bash
export SHELLOPTS:=$(if $(SHELLOPTS),$(SHELLOPTS):)pipefail:errexit
.PHONY: build
.ONESHELL:
build:
mkdir build || true
cd build
cmake .. -DCMAKE_TOOLCHAIN_FILE=../../DevkitArm3DS.cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCODEADDR=$(CODEADDR) -DDATAADDR=$(DATAADDR)
make
cp *.sym *.bin ../
clean:
rm -rf build

22
loader/linker_template.ld Normal file
View File

@@ -0,0 +1,22 @@
OUTPUT_ARCH(arm)
SECTIONS
{
. = ${CODEADDR};
.text : {
__text_start = . ;
*(.text)
*(.text.*)
__text_end = . ;
}
. = ${DATAADDR};
.data : {
__data_start = . ;
*(.rodata)
*(.data)
*(.bss)
*(COMMON)
__data_end = . ;
}
}

5
loader/source/hooks.hks Normal file
View File

@@ -0,0 +1,5 @@
LoaderEntryPoint:
type: softbranch
opcode: post
func: LoaderMain
addr: 0x00100000

43
loader/source/loader.c Normal file
View File

@@ -0,0 +1,43 @@
#include "newcodeinfo.h" // Automatically generated
typedef signed int s32;
typedef unsigned int u32;
typedef s32 Result;
typedef u32 Handle;
Result svcOpenProcess(Handle* process, u32 processId);
Result svcGetProcessId(u32* out, Handle handle);
void svcBreak(u32 breakReason);
Result svcControlProcessMemory(Handle process, u32 addr0, u32 addr1, u32 size, u32 type, u32 perm);
Handle getCurrentProcessHandle();
void LoaderMain()
{
Result res;
u32 address = NEWCODE_OFFSET;
u32 neededMemory = (NEWCODE_SIZE + 0xFFF) & ~0xFFF; // Dunno if rounding this up is needed
res = svcControlProcessMemory(getCurrentProcessHandle(), address, address, neededMemory, 6, 7);
if (res < 0)
svcBreak(1);
}
Handle getCurrentProcessHandle()
{
Handle handle = 0;
u32 currentPid = 0;
Result res;
svcGetProcessId(&currentPid, 0xffff8001);
res = svcOpenProcess(&handle, currentPid);
if (res != 0)
return 0;
return handle;
}

45
loader/source/svc.s Normal file
View File

@@ -0,0 +1,45 @@
.arm
.align 4
.macro SVC_BEGIN name
.section .text.\name, "ax", %progbits
.global \name
.type \name, %function
.align 2
.cfi_startproc
\name:
.endm
.macro SVC_END
.cfi_endproc
.endm
SVC_BEGIN svcOpenProcess
push {r0}
svc 0x33
pop {r2}
str r1, [r2]
bx lr
SVC_END
SVC_BEGIN svcGetProcessId
str r0, [sp, #-0x4]!
svc 0x35
ldr r3, [sp], #4
str r1, [r3]
bx lr
SVC_END
SVC_BEGIN svcBreak
svc 0x3C
bx lr
SVC_END
SVC_BEGIN svcControlProcessMemory
push {r4-r5}
ldr r4, [sp, #0x8]
ldr r5, [sp, #0xC]
svc 0x70
pop {r4-r5}
bx lr
SVC_END

57
make_release.sh Executable file
View File

@@ -0,0 +1,57 @@
#!/bin/bash
set -eu
RST_ROOT=$(dirname "$0")
RELEASE_DIR=$RST_ROOT/release
VERSION=$(git describe --tags --dirty --always --long --match '*')
print_status () {
MSG=$1
BLUE='\033[0;34m'
NC='\033[0m'
echo -e "${BLUE}${MSG}${NC}"
}
# Clean up the release directory
rm -r $RELEASE_DIR || true
mkdir $RELEASE_DIR
build () {
TARGET_VERSION=$1
print_status "building for $TARGET_VERSION"
# Copy the version-specific hooks
rm -r $RST_ROOT/hooks/ || true
mkdir $RST_ROOT/hooks
cp $RST_ROOT/$TARGET_VERSION/hooks.hks $RST_ROOT/hooks/
# Copy the version-specific build files
cp $RST_ROOT/$TARGET_VERSION/*.bin $RST_ROOT/
cp $RST_ROOT/$TARGET_VERSION/Version.cmake $RST_ROOT/source/
# Run the patcher
Magikoopa --build --workdir $RST_ROOT/
# Copy build output
mkdir $RELEASE_DIR/$TARGET_VERSION
flips -i $RST_ROOT/bak/code.bin $RST_ROOT/code.bin $RELEASE_DIR/$TARGET_VERSION/code.ips
cp $RST_ROOT/exheader*.bin $RELEASE_DIR/$TARGET_VERSION/
# Clean up
rm -r $RST_ROOT/loader/*.bin $RST_ROOT/loader/*.sym || true
rm -r $RST_ROOT/*.bin $RST_ROOT/*.sym || true
rm -r $RST_ROOT/bak $RST_ROOT/hooks || true
}
build v100
if [ -z ${RST_SKIP_110+x} ]; then
build v110
fi
print_status "packing"
pushd $RELEASE_DIR
7z a mm3d_project_restoration_${VERSION}.7z .
popd

114
readme.md Normal file
View File

@@ -0,0 +1,114 @@
# Project Restoration
A *Majora's Mask 3D* patch that restores some mechanics from the original *Majora's Mask*
and fixes some issues to make the game even more enjoyable.
**Note**: Some features make use of the new ZL/ZR buttons,
so playing on a New 3DS/2DS or Citra is recommended for a better experience.
## Changes
* **Zora Fast Swim**: Swim gracefully as a Zora without having to use magic
* Fast swim is now the default way of swimming and no longer requires magic.
* Slow swim is still available. Hold ZL to slow swim.
* **Fixed Deku Hopping**: Hop on water as fast as in MM
* Deku Link's walk acceleration value was nerfed in MM3D (2.0 -> 0.6). As a possibly unintended consequence, this made hopping on lilypads very slow compared to the original even if you spin at the optimal time.
* **Fast Transform**: Transform without having to equip items for a more streamlined gameplay
* Three of the four D-Pad buttons are now used to fast transform (*Left*: Zora, *Up*: Goron, *Down*: Deku)
* This frees up as many as 3 buttons!
* **Fast Ocarina**: Dedicated physical button for the Ocarina of Time
* Press ZR to play the instrument.
* **More Effective Inverted Song of Time**: The ISoT makes time go at 1/3 speed (rather than 1/2).
* Makes some glitchless challenge runs possible again.
* Gives the player more time in a three-day cycle.
* **Improved Twinmold Battle**: Less tedious, less confusing
* Reduced the number of cycles to make it less repetitive.
* Red Twinmold no longer resets its hit counter every time it burrows back into the sand. As a result, the battle is much less confusing for new players.
### Planned changes
In a roughly increasing order of difficulty. Most tasks below require reverse engineering actors.
* **Ice Arrows Everywhere**: There is no reason ice arrows should only work on a few sparkling spots and in Gyorg's boss room. The player should have the freedom to experiment with ice arrows. It's a frankly surprising limitation and worse, an inconsistent one because the water in the GBT boss room isn't even sparkling. I'm also considering removing the sparkling effects.
* **More Fluid Bomber's Notebook**: MM3D has annoying long popups and UI animations. It should be possible to decrease the transition durations and reduce pauses.
* **Faster Captain Keeta**: In MM3D, Captain Keeta walks a lot slower and the battle ends a lot faster. This was probably another attempt at making the game more accessible, but IMO it wasn't even that difficult in the first place. I don't particularly care about this but someone has suggested reverting this change.
### Considered changes
* **Optional Saving via Song of Time**: This was suggested to me and I have no strong opinion on this, though adding a brand new option to save the game is probably a bit difficult to implement, especially considering it will likely require text edits, when Citra still doesn't support LayeredFS-style patching functionality.
## Setup
First, download the [latest release](https://github.com/leoetlino/project-restoration/releases) that **corresponds to your game version**. You can determine the version by checking whether "v1.1" is shown on the title screen.
### Console
On console, [Luma3DS](https://github.com/AuroraWright/Luma3DS) (or a loader that supports IPS patching and exheader replacement) is required.
* Open the release archive.
* Create /luma/titles/0004000000125600/ (SD) on your SD card (if it doesn't already exist).
* Copy **code.ips** to /luma/titles/0004000000125600/**code.ips** (SD).
* Copy **exheader_legacy.bin** to /luma/titles/0004000000125600/**exheader.bin** (SD).
### Citra
A recent version of Citra is required. More specifically, pull requests citra-emu/citra#4812, citra-emu/citra#4813, citra-emu/citra#4817 are required to use code patching functionality. **Note**: As of 2019-07-06, the last two PRs have not been merged yet, so building Citra youself with those patches **or using a canary build** is **required**.
Let *GAME_FILE* be the path to the game file (3ds/cia/app). If you've installed the game, GAME_FILE is "sdmc/Nintendo 3DS/00000000000000000000000000000000/00000000000000000000000000000000/title/00040000/00125600/content/xxxxxxxx.app" (where xxxxxxxx.app is the largest file in that directory).
* Open the release archive.
* Create ***GAME_FILE*.exefsdir** (if it doesn't already exist).
* Copy **code.ips** to *GAME_FILE*.exefsdir/**code.ips**.
* Copy **exheader.bin** to ***GAME_FILE*.exheader**.
## Rationale
### Zora swim
In MM3D, swimming is a bit slower. It is possible to fast swim; however it now requires and consumes magic at a fast rate. Chateau Romani isn't a satisfactory workaround: it only becomes available after a bunch of quests and requires wasting most of the First Day, and even then it's still impossible to get rid of the constant buzzing sound that comes from using the barrier.
Besides, why would Zora Link need magic to swim like a Zora?
### Inverted Song of Time potency
The ISoT nerf might have been another unintended change.
The in-game time is updated by adding a speed value and another value I'll call the extra speed to the time variable every frame. In MM, the time speed is usually 3 (units/frame) and the ISoT sets the extra speed to -2, resulting in a +1 effective speed (which means 1/3 speed). Because the time is updated every frame, in MM3D, the developers reduced the speed to 2 to compensate for the increased framerate. The ISoT was updated to set the speed to -1 instead of -2. However, that only gives players 1/2 speed.
I couldn't see any good reason to keep this change, so I reverted it.
### Twinmold
The new Twinmold battle drags on for way too long. Spinning the main stick makes it faster, but that's not an obvious mechanic. Even with that trick, killing Red Twinmold still takes 3 long identical cycles!
Another issue is the addition of a hidden hit counter. 10 hits are required to stun Red or Blue Twinmold. This would have been acceptable if it weren't for the fact that Red Twinmold regularly burrows back into sand during phase 2
and the hit counter is silently reset every time that happens.
This makes for a confusing experience the first time the player fights Twinmold,
as there is nothing in the game that indicates that the hit counter resets every time,
and it's still frustrating on subsequent playthroughs.
## Project information
* `source/` *Project Restoration*'s source code.
* `addresses.h`: Version-specific memory addresses.
* `v100` and `v110`: Version-specific data.
* `hooks.hks`: configuration for patches and hooks (for Magikoopa).
* `Version.cmake`: defines for our own code.
* `loader/`: Code loader (from [Magikoopa](https://github.com/RicBent/Magikoopa)).
To build the project:
* Put the original code.bin and exheader.bin in v100 and v110.
* Run make_release.sh. You need git and Magikoopa in your PATH.
* Generated code patches (code.ips) and patched exheaders can be found in `release/`.
PRs and help are welcome!

51
source/CMakeLists.txt Normal file
View File

@@ -0,0 +1,51 @@
cmake_minimum_required(VERSION 3.13)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
project(restoration C CXX ASM)
add_executable(newcode
common/context.cpp
common/context.h
common/debug.cpp
common/debug.h
common/flags.h
common/svc.s
common/types.h
common/utils.h
game/actor.h
game/actors/boss_twinmold.h
game/common_data.cpp
game/common_data.h
game/context.cpp
game/context.h
game/items.cpp
game/items.h
game/pad.h
game/player.cpp
game/player.h
game/static_context.cpp
game/static_context.h
rst/fixes.cpp
rst/fixes.h
rst/link.cpp
rst/link.h
rst/main.cpp
rst/trampolines.s
)
set_target_properties(newcode PROPERTIES SUFFIX ".elf")
target_compile_options(newcode PRIVATE -fdiagnostics-color=always -Wall -Wextra -Werror -std=c++17 -fno-exceptions -fomit-frame-pointer -ffunction-sections)
target_include_directories(newcode PRIVATE ./)
if (NOT DEFINED CODEADDR)
message(FATAL_ERROR "Set CODEADDR")
endif()
target_link_options(newcode PRIVATE "-Wl,-Ttext-segment=${CODEADDR}")
add_custom_target(newcode_product ALL
DEPENDS newcode
COMMAND ${CMAKE_OBJCOPY} -O binary newcode.elf newcode.bin
COMMAND sh -c "${CMAKE_OBJDUMP} -t newcode.elf>newcode.sym"
BYPRODUCTS newcode.bin newcode.sym
)
include(Version.cmake)

10
source/common/context.cpp Normal file
View File

@@ -0,0 +1,10 @@
#include "common/context.h"
namespace rst {
Context& GetContext() {
static Context s_context{};
return s_context;
}
} // namespace rst

18
source/common/context.h Normal file
View File

@@ -0,0 +1,18 @@
#pragma once
#include "common/types.h"
namespace game {
struct GlobalContext;
}
namespace rst {
struct Context {
game::GlobalContext* gctx;
bool has_initialised = false;
};
Context& GetContext();
} // namespace rst

40
source/common/debug.cpp Normal file
View File

@@ -0,0 +1,40 @@
#include "common/debug.h"
#include <cstdarg>
#include <cstdio>
#include "common/types.h"
#include "game/common_data.h"
namespace rst::util {
extern "C" {
int svcOutputDebugString(const char* string, int length);
}
void Print(std::string_view string) {
svcOutputDebugString(string.data(), string.size());
}
void Print(const char* format, ...) {
char buffer[0x200];
va_list arg;
va_start(arg, format);
const int written = std::vsnprintf(buffer, sizeof(buffer), format, arg);
va_end(arg);
if (written >= 0)
Print(std::string_view{buffer, size_t(written)});
}
std::array<char, 6> TimeToString() {
const u16 game_time = game::GetCommonData().save.time;
const float day_percentage = float(game_time) / 0x10000;
const int hours = 24 * day_percentage;
const int minutes = 60 * ((24 * day_percentage) - hours);
std::array<char, 6> output;
std::snprintf(output.data(), output.size(), "%02d:%02d", hours, minutes);
return output;
}
}

16
source/common/debug.h Normal file
View File

@@ -0,0 +1,16 @@
#pragma once
#include <array>
#include <string_view>
namespace rst::util {
/// Prints a debug message using svcOutputDebugString.
void Print(std::string_view string);
/// Prints a debug message using svcOutputDebugString.
__attribute__((format(printf, 1, 2))) void Print(const char* format, ...);
/// Returns the in-game time as a null-terminated HH:MM string.
std::array<char, 6> TimeToString();
} // namespace rst::util

39
source/common/flags.h Normal file
View File

@@ -0,0 +1,39 @@
#pragma once
#include <type_traits>
namespace rst {
// Helper class that makes it easy to manipulate bit flags in a typesafe way.
template <typename FlagType, typename = typename std::enable_if_t<std::is_enum_v<FlagType>, void>>
class Flags {
public:
constexpr auto Set(FlagType v) {
flags |= std::underlying_type_t<FlagType>(v);
return *this;
}
constexpr auto Clear(FlagType v) {
flags &= ~std::underlying_type_t<FlagType>(v);
return *this;
}
constexpr bool IsSet(FlagType v) const {
return (flags & std::underlying_type_t<FlagType>(v)) != 0;
}
constexpr bool AreAllSet(FlagType v) const { return IsSet(v); }
template <typename... Rest>
constexpr bool AreAllSet(FlagType v, Rest... rest) const {
return IsSet(v) && AreAllSet(rest...);
}
constexpr bool IsOneSet(FlagType v) const { return IsSet(v); }
template <typename... Rest>
constexpr bool IsOneSet(FlagType v, Rest... rest) const {
return IsSet(v) || IsOneSet(rest...);
}
std::underlying_type_t<FlagType> flags = 0;
};
} // namespace rst

8
source/common/svc.s Normal file
View File

@@ -0,0 +1,8 @@
.global svcOutputDebugString
.type svcOutputDebugString, %function
svcOutputDebugString:
str r0, [sp,#-0x4]!
svc 0x3D
ldr r2, [sp], #4
str r1, [r2]
bx lr

22
source/common/types.h Normal file
View File

@@ -0,0 +1,22 @@
#pragma once
#include <cstdint>
using u8 = std::uint8_t;
using u16 = std::uint16_t;
using u32 = std::uint32_t;
using u64 = std::uint64_t;
using s8 = std::int8_t;
using s16 = std::int16_t;
using s32 = std::int32_t;
using s64 = std::int64_t;
using size_t = std::size_t;
static_assert(sizeof(u16) == sizeof(short));
static_assert(sizeof(u32) == sizeof(int));
struct Vec3 {
float x;
float y;
float z;
};

52
source/common/utils.h Normal file
View File

@@ -0,0 +1,52 @@
#pragma once
#include <cstring>
#include <tuple>
#include <type_traits>
#include "common/types.h"
namespace rst::util {
namespace detail {
#if defined(RST_VER)
constexpr u32 Version = RST_VER;
#else
constexpr u32 Version = 0;
#endif
static_assert(Version == 0 || Version == 1, "Unknown version");
template <class... Ts, typename std::enable_if_t<(Version < sizeof...(Ts))>* = nullptr>
constexpr uintptr_t GetAddr(Ts... addresses) {
return std::get<Version>(std::forward_as_tuple(addresses...));
}
template <class... Ts>
constexpr uintptr_t GetAddr(Ts...) {
return 0;
}
} // namespace detail
/// Returns a version-specific address from a list of addresses and casts it to Type*.
template <typename Type, class... Ts>
constexpr auto GetPointer(Ts... addresses) {
static_assert(detail::Version < sizeof...(Ts), "Missing address!");
return reinterpret_cast<Type*>(detail::GetAddr(addresses...));
}
/// Returns the offset in bytes of a member.
/// Unlike offsetof, this works for derived classes as well.
template <typename T1, typename T2>
inline size_t constexpr OffsetOf(T1 T2::*member) {
constexpr T2 object{};
return size_t(&(object.*member)) - size_t(&object);
}
template <typename Dest, typename T>
Dest BitCastPtr(const T* ptr, size_t offset = 0) {
Dest dest;
std::memcpy(&dest, reinterpret_cast<const u8*>(ptr) + offset, sizeof(dest));
return dest;
}
} // namespace rst::util

177
source/game/actor.h Normal file
View File

@@ -0,0 +1,177 @@
#pragma once
#include <array>
#include "common/flags.h"
#include "common/types.h"
namespace game {
struct GlobalContext;
}
namespace game::act {
class Actor;
enum class Id : u16 {
Player = 0,
BossTwinmold = 0xcc,
};
// https://wiki.cloudmodding.com/oot/Actors#Categories
enum class Type : u8 {
Switch = 0,
Background = 1,
Player = 2,
Bomb = 3,
Npc = 4,
Enemy = 5,
Prop = 6,
Item = 7,
Misc = 8,
Boss = 9,
Door = 10,
Chest = 11,
};
struct ActorInfo {
Id id;
Type type;
u8 room;
u32 flags;
u16 object_id;
u8 anonymous_3[2];
size_t inst_size;
void (*init_fn)(Actor*, GlobalContext*);
void (*deinit_fn)(Actor*, GlobalContext*);
void (*calc_fn)(Actor*, GlobalContext*);
void (*draw_fn)(Actor*, GlobalContext*);
};
// Actor overlay info. Same structure as Majora's Mask, though most fields are now unused.
struct ActorOverlayInfo {
int field_0;
int field_4;
int increment_loaded_count;
int field_C;
int field_10;
ActorInfo* info;
const char* name;
u16 allocation_type;
u8 loaded_count;
};
struct Actor {
enum class Flag94 : u16 {
Grounded = 1,
};
Id id;
Type actor_type;
u8 room_number;
u32 flags;
Vec3 pos;
float field_14;
u16 field_18;
u16 field_1A;
u16 state;
u8 field_1E;
u8 field_1F;
u16 field_20;
u16 field_22;
Vec3 position;
float field_34;
u8 gap_36[6];
Vec3 pos_copy;
u16 field_48;
__attribute__((packed)) __attribute__((aligned(1))) u32 field_4A;
u8 gap_4E[2];
u32 field_50;
u8 gap_54[4];
Vec3 model_scale;
Vec3 vel;
float vel_xz;
float field_74;
u8 gap_78[8];
u32 field_80;
u8 gap_84;
u8 field_85;
u8 gap86[3];
u8 gap_89[3];
float field_8C;
u8 gap_90[4];
rst::Flags<Flag94> flags_94;
float field_98;
float field_9C;
float field_A0;
u8 field_A4[22];
u8 field_BA;
/// Used by Twinmold at least. Unused for player?
s8 life;
u8 field_BC;
u8 field_BD;
u8 field_BE;
u8 field_BF;
u16 field_C0;
u16 angle;
u16 field_C4;
u8 gap_C6[2];
float field_C8;
u32 field_CC;
float field_D0;
u8 gap_D4[37];
u8 field_F9;
u8 gap_FA[6];
u32 field_100;
u8 gap_104[8];
u16 field_10C;
u8 gap_10E[2];
float field_110;
u8 gap_114[4];
u8 field_118;
u8 gap119;
u16 field_11A;
u16 field_11C;
u16 field_11E;
u8 gap_120[5];
u8 field_125;
u8 gap_126[2];
Actor* child_actor;
Actor* parent_actor;
/// Previous actor of the same type in the linked list.
Actor* prev;
/// Next actor of the same type in the linked list.
Actor* next;
void (*init_fn)(Actor*, GlobalContext*);
void (*deinit_fn)(Actor*, GlobalContext*);
void (*calc_fn)(Actor*, GlobalContext*);
void (*draw_fn)(Actor*, GlobalContext*);
ActorOverlayInfo* overlay_info;
int field_14C;
int field_150;
int field_154;
int field_158;
int field_15C;
int field_160;
int field_164;
int field_168;
int field_16C;
int field_170;
int field_174;
int field_178;
void* field_17C;
char field_180[80];
int field_1D0;
u8 field_1D4;
int field_1D8;
int field_1DC;
int field_1E0;
int field_1E4;
int field_1E8;
u16 field_1EC;
int field_1F0;
float field_1F4;
};
static_assert(sizeof(Actor) == 0x1F8);
} // namespace game::act

View File

@@ -0,0 +1,167 @@
#pragma once
#include "common/types.h"
#include "common/utils.h"
#include "game/actor.h"
namespace game {
class GlobalContext;
}
namespace game::act {
struct BossTwinmold : Actor {
// Probably incomplete.
enum class Status : u16 {
Buried = 0,
BlueRisingOutOfSand = 1,
// Also resets the hit counter.
RedBurrowingIntoSand = 3,
Flying = 4,
Unk6 = 6,
// The most commonly seen state for Blue Twinmold.
FlyingAimlessly = 7,
Unk8 = 8,
Unk9 = 9,
// These states are entered after receiving enough hits.
BlueStunnedByShootingEyes = 11,
BlueStunnedEyeOut = 12,
BlueStunnedBurrowingIntoSand = 13,
Stunned = 15,
StunnedAndOnGround = 16,
TauntingLink = 18,
AfterTaunting = 19,
// Only for Red Twinmold?
TauntingAndAttacking = 21,
AfterTauntingAndAttacking = 22,
FlyingAndAttacking = 23,
BeingGrabbedByLink = 24,
BeingChokedByLink = 25,
PreparingToRiseOutOfSand = 26,
// ?
AfterTaunting2 = 98,
Inactive = 100,
FirstTimeRisingOutOfSand = 102,
DyingStart = 200,
DyingExploding = 201,
DyingFallingToGround = 202,
DyingTouchedGround = 203,
};
void* resource;
Status status;
u16 some_status_change_countdown;
u16 field_200;
u16 field_202;
u16 frame_counter;
u16 field_206;
u16 field_208;
u16 field_20A;
u16 field_20C;
u8 gap_20E[14];
u16 field_21C;
u8 gap_21E[14];
Vec3 field_22C;
Vec3 field_238;
u8 gap244[1];
u8 field_245;
u8 gap246[3];
u8 gap_249[27];
float field_264;
float field_268;
signed int field_26C;
Vec3 field_270;
u8 gap27C[15748];
u32 field_4000;
u8 gap_4004[3448];
u32 field_4D7C;
u32 field_4D80;
u8 gap_4D84[56];
u32 status_anim;
u8 gap_4DC0[8];
float field_4DC8;
signed int field_4DCC;
u8 gap_4DD0[560];
u32 field_5000;
u8 gap_5004[3448];
Vec3 field_5D7C;
Vec3 field_5D88;
Vec3 field_5D94;
Vec3 field_5DA0;
u8 gap5DAC[120];
void (*field_5E24)(BossTwinmold*, GlobalContext*);
/// Points to Blue Twinmold for Red Twinmold, and vice versa.
BossTwinmold* other_twinmold_actor;
signed int field_5E2C;
u8 gap_5E30[464];
u32 field_6000;
u8 gap_6004[1944];
u32 field_679C;
u16 field_67A0;
u8 gap_67A2[26];
float field_67BC;
u16 field_67C0;
signed int field_67C4;
u8 gap_67C8[44];
float field_67F4;
float field_67F8;
u8 gap_67FC[8];
int (*field_6804)(BossTwinmold*, GlobalContext*, act::Actor*);
u8 gap_6808[6136];
u32 field_8000;
u8 gap_8004[784];
char field_8314;
char field_8315;
char field_8316;
char field_8317;
u8 gap_8318[3304];
u32 field_9000;
u8 gap_9004[208];
char field_90D4;
__attribute__((aligned(4))) u8 field_90D8;
u8 gap_90D9[11];
u16 hit_counter;
u16 field_90E6;
u8 gap_90E8[12];
u32 field_90F4;
int field_90F8;
u8 gap_90FC[16];
u32 field_910C;
u32 field_9110;
u8 gap_9114[4];
u32 field_9118;
u8 gap_911C[8];
u32 field_9124;
u8 gap_9128[4];
u32 field_912C;
float field_9130;
u8 gap_9134[8];
float field_913C;
u8 gap_9140[24];
Vec3 field_9158;
Vec3 field_9164;
u8 gap9170[1108];
Vec3 field_95C4;
u8 gap_95D0[8];
int field_95D8;
int field_95DC;
int field_95E0;
u8 gap_95E4[60];
int field_9620;
int field_9624;
};
static_assert(sizeof(BossTwinmold) == 0x9628);
static_assert(rst::util::OffsetOf(&BossTwinmold::other_twinmold_actor) == 0x5E28);
} // namespace game::act

View File

@@ -0,0 +1,12 @@
#include "game/common_data.h"
#include "common/utils.h"
namespace game {
CommonData& GetCommonData() {
// Right before the static context in .bss.
return *rst::util::GetPointer<CommonData>(0x7751D8, 0x7761D8);
}
} // namespace game

582
source/game/common_data.h Normal file
View File

@@ -0,0 +1,582 @@
#pragma once
#include <array>
#include "common/types.h"
#include "game/items.h"
#include "game/player.h"
namespace game {
struct __attribute__((packed)) __attribute__((aligned(2))) PlayerData {
u32 field_11C;
u8 gap_120[2];
u8 save_count_maybe;
int anonymous_d;
int anonymous_e;
int anonymous_f;
int anonymous_g;
u8 anonymous_h[2];
u16 anonymous_i;
u16 anonymous_j;
char player_magic_size_type;
char player_magic;
u16 player_rupee_count;
u16 player_razor_sword_hp;
u16 anonymous_k;
char player_magic_stuff;
char anonymous_l;
char anonymous_17;
char anonymous_18;
char anonymous_19;
char anonymous_20;
u16 anonymous_21;
char field_2E;
char field_2F;
char field_30;
char field_31;
};
static_assert(sizeof(PlayerData) == 0x32);
struct FormEquipmentData {
ItemId item_btn_b;
ItemId item_btn_y;
ItemId item_btn_x;
ItemId item_btn_i;
ItemId item_btn_ii;
};
struct EquipmentData {
FormEquipmentData data[4];
char field_14;
char anonymous_24;
char anonymous_25;
char anonymous_26;
char anonymous_27;
char field_19;
char field_1A;
char field_1B;
char field_1C;
char field_1D;
char field_1E;
char field_1F;
char field_20;
char field_21;
char field_22;
char field_23;
char field_24;
char field_25;
char field_26;
char field_27;
u16 anonymous_28;
};
struct InventoryData {
std::array<ItemId, 24> items;
std::array<ItemId, 24> masks;
std::array<u8, 24> item_counts;
u8 field_48[24];
u8 field_60[24];
int anonymous_31;
int anonymous_32;
char anonymous_33[1];
char anonymous_34[3];
u8 gap200[6];
char anonymous_35[1];
char anonymous_36;
char anonymous_37;
char anonymous_38;
u8 gap20A[5];
char anonymous_39;
char anonymous_40;
char anonymous_41;
char anonymous_42;
char anonymous_43;
char gap98[60];
};
struct SaveData {
MaskId mask;
bool has_completed_intro;
char unused;
char anonymous_0;
bool is_night;
/// Number of extra time units to add per game tick (0 normally; -1 with ISoT)
/// In Majora's Mask, ISoT used to set this to -2.
int extra_time_speed;
/// In-game day
int day;
int total_day;
/// Legacy time speed (?)
///
/// Strangely enough, this is still set to -3 on the title screen. But setting this
/// during gameplay breaks the game as it tries to dereference a null pointer.
s16 legacy_time_speed, legacy_time_speed_padding;
/// In-game time.
/// 0x0000 is midnight, 0x4000 is 6am, 0x8000 is noon, 0xc000 is 6pm.
u16 time;
u16 anonymous_3;
u16 anonymous_4;
act::Player::Form player_form;
char anonymous_5;
char field_20;
char anonymous_7;
char anonymous_8;
char anonymous_9;
char anonymous_10;
char anonymous_11;
char anonymous_12;
char anonymous_13;
char anonymous_14;
char anonymous_15;
char anonymous_16;
char gap33[205];
char anonymous_a[24];
char anonymous_b;
u8 gap_115[7];
PlayerData player;
EquipmentData equipment;
InventoryData inventory;
char field_24C;
u8 gap249[1235];
int anonymous_44;
u8 gap728[384];
char anonymous_45;
u8 gap8A9[1023];
int anonymous_46;
u8 gapCAC[1284];
int anonymous_47;
int anonymous_48;
int anonymous_49;
int anonymous_50;
int anonymous_51;
u8 gap11C4[12];
int anonymous_52;
int anonymous_53;
int anonymous_54;
int anonymous_55;
int anonymous_56;
int anonymous_57;
int anonymous_58;
u8 gap11EC[36];
int anonymous_59;
int anonymous_60;
u8 gap1218[4];
int anonymous_61;
int anonymous_62;
int anonymous_63;
u8 gap1228[8];
int anonymous_64;
u8 gap1234[8];
int anonymous_65;
int anonymous_66;
int anonymous_67;
int anonymous_68;
u8 gap124C[5];
char anonymous_69;
char anonymous_70;
u8 gap1253[4];
char anonymous_71;
char anonymous_72;
char anonymous_73;
char anonymous_74;
char anonymous_75;
char anonymous_76;
char anonymous_77;
u8 flag_8_for_no_magic_use;
char anonymous_78;
char anonymous_79;
char anonymous_80;
char anonymous_81;
char anonymous_82;
char anonymous_83;
char anonymous_84;
char anonymous_85;
char anonymous_86;
char anonymous_87;
char anonymous_88;
char anonymous_89;
char anonymous_90;
char anonymous_91;
char anonymous_92;
char anonymous_93;
char anonymous_94;
char anonymous_95;
char anonymous_96;
char anonymous_97;
char anonymous_98;
char anonymous_99;
char anonymous_100;
char anonymous_101;
char anonymous_102;
char anonymous_103;
char anonymous_104;
u8 gap127A[8];
char anonymous_105;
char anonymous_106;
char anonymous_107;
char anonymous_108;
char anonymous_109;
char anonymous_110;
char anonymous_111;
char anonymous_112;
char anonymous_113;
char anonymous_114;
char anonymous_115;
char anonymous_116;
char anonymous_117;
char anonymous_118;
char anonymous_119;
char anonymous_120;
u8 gap1292[7];
char anonymous_121;
char anonymous_122;
char anonymous_123;
char anonymous_124;
char anonymous_125;
char anonymous_126;
char anonymous_127;
char anonymous_128;
char anonymous_129;
char anonymous_130;
char anonymous_131;
char anonymous_132;
char anonymous_133;
char anonymous_134;
char anonymous_135;
char anonymous_136;
char anonymous_137;
char anonymous_138;
char anonymous_139;
char anonymous_140;
char anonymous_141;
u8 gap12AE[6];
char anonymous_142;
char anonymous_143;
char anonymous_144;
u8 gap12B7[3];
char anonymous_145;
char anonymous_146;
u8 gap12BC[13];
char anonymous_147;
char anonymous_148[6];
char anonymous_149;
char anonymous_150;
char anonymous_151;
char anonymous_152;
char anonymous_153;
char anonymous_154;
char anonymous_155;
char anonymous_156;
char anonymous_157;
char anonymous_158;
char anonymous_159;
char anonymous_160;
u8 gap12DC[20];
int anonymous_161;
int anonymous_162;
u8 gap12F8;
char anonymous_163;
u8 gap12FA[128];
char anonymous_164;
char anonymous_165;
char anonymous_166;
char anonymous_167;
char anonymous_168;
char anonymous_169;
char anonymous_170;
char anonymous_171;
char anonymous_172;
char anonymous_173;
char anonymous_174;
char anonymous_175;
char anonymous_176;
char anonymous_177;
char anonymous_178;
char anonymous_179;
u8 gap138A[5];
char anonymous_180;
char anonymous_181;
char anonymous_182;
char anonymous_183;
char anonymous_184;
char anonymous_185;
char anonymous_186[3];
u16 anonymous_187;
u16 anonymous_188;
u16 anonymous_189;
u16 anonymous_190;
u16 anonymous_191;
u8 gap13A2[326];
char anonymous_192;
char anonymous_193;
char anonymous_194;
char anonymous_195;
char anonymous_196;
char anonymous_197;
u16 anonymous_198;
u8 gap_14E8[136];
u32 field_1570;
u8 gap_1574[1020];
u32 field_1970;
u8 gap_1974[176];
char anonymous_199;
char anonymous_200;
u16 anonymous_201;
u8 gap1A30[20];
char anonymous_202;
char anonymous_203;
char anonymous_204;
char anonymous_205;
char anonymous_206;
char anonymous_207;
char anonymous_208;
char anonymous_209;
char anonymous_210;
char anonymous_211;
char anonymous_212;
char anonymous_213;
char anonymous_214;
char anonymous_215;
char anonymous_216;
char anonymous_217;
char anonymous_218;
char anonymous_219;
char anonymous_220;
char anonymous_221;
char anonymous_222;
char anonymous_223;
char anonymous_224;
char anonymous_225;
u8 gap1A5C[8];
u16 anonymous_226;
char anonymous_227[2];
u8 gap_1A60[16];
u32 field_1A70;
u8 gap_1A74[12];
u16 anonymous_228;
u16 anonymous_229;
int field_1A84;
};
static_assert(sizeof(SaveData) == 0x1A88);
struct CommonDataSub1 {
int field_0;
int field_4;
int field_8;
int field_C;
int field_10;
int field_14;
int field_18;
int field_1C;
};
struct CommonDataSub3 {
u32 field_0;
int field_4;
int field_8;
int field_C;
int field_10;
int field_14;
int field_18;
int field_1C;
};
struct CommonDataSub4 {
u32 field_0;
int field_4;
int field_8;
int field_C;
int field_10;
int field_14;
};
struct CommonDataSub5 {
u32 field_0;
int field_4;
int field_8;
int field_C;
int field_10;
int field_14;
int field_18;
int field_1C;
};
struct CommonDataSub6 {
u32 field_0;
int field_4;
int field_8;
int field_C;
int field_10;
int field_14;
};
struct CommonDataSub7 {
u64 field_0;
int field_8;
int field_C;
int field_10;
int field_14;
int field_18;
int field_1C;
};
struct CommonDataSub8 {
u32 field_0;
int field_4;
int field_8;
int field_C;
int field_10;
int field_14;
};
struct CommonDataSub9 {
u32 field_0;
int field_4;
int field_8;
int field_C;
int field_10;
int field_14;
int field_18;
int field_1C;
};
struct CommonDataSub10 {
u32 field_0;
int field_4;
int field_8;
int field_C;
int field_10;
int field_14;
};
struct CommonDataSub11 {
u64 field_0;
int field_8;
int field_C;
int field_10;
int field_14;
int field_18;
int field_1C;
};
struct CommonDataSub12 {
u32 field_0;
int field_4;
int field_8;
int field_C;
int field_10;
int field_14;
};
struct CommonDataSub13 {
u32 field_C;
u32 field_10;
u32 field_14;
u16 field_18;
u16 field_1A;
u16 field_1C;
char field_1E;
char field_1F;
u32 field_20;
u32 field_24;
u32 field_28;
};
/// Common gameplay data, also known as the Save Context (unofficially).
struct CommonData {
int start;
int scene;
SaveData save;
SaveData save_backup;
CommonDataSub1 sub1;
u64 unknown_1;
u64 unknown_2;
u64 unknown_3;
CommonDataSub3 sub3;
CommonDataSub4 sub4;
CommonDataSub5 sub5;
CommonDataSub6 sub6;
CommonDataSub7 sub7;
CommonDataSub8 sub8;
CommonDataSub9 sub9;
CommonDataSub10 sub10;
CommonDataSub11 sub11;
CommonDataSub12 sub12;
u8 gap_3668[14];
__attribute__((packed)) __attribute__((aligned(1))) int field_3676;
__attribute__((packed)) __attribute__((aligned(1))) int field_367A;
__attribute__((packed)) __attribute__((aligned(1))) int field_367E;
u16 field_3682;
u16 field_3684;
u16 field_3686;
u16 field_3688;
u16 field_368A;
u16 field_368C;
u16 field_368E;
u16 field_3690;
u16 field_3692;
u16 field_3694;
u16 field_3696;
u16 field_3698;
u16 field_369A;
u16 field_369C;
u16 field_369E;
u16 time_copy_2;
u16 time_copy;
u8 field_36A4[32];
u32 field_36C4;
u8 gap_36C8[4];
// MPO data.
u8 pictograph_data[65356];
// Data below isn't read from or written to save files.
u16 save_idx;
u8 gap_1361A[2];
int field_1361C;
int field_13620;
int field_13624;
CommonDataSub13 sub13s[8];
u32 field_13728;
int field_1372C;
char field_13730;
char field_13731;
u16 field_13732;
u16 field_13734;
u16 field_13736;
int field_13738;
int field_1373C;
u8 field_13740[9];
char field_13749[15];
int field_13758;
char field_1375C;
char field_1375D;
char field_1375E;
char field_1375F;
u16 time_copy_3;
char field_13762;
char field_13763;
u8 gap_13764[2204];
u32 field_14000;
u8 gap_14004[192];
int field_140C4;
int field_140C8;
int field_140CC;
int field_140D0;
int field_140D4;
u32 field_140D8;
int field_140DC;
int field_140E0;
int field_140E4;
int field_140E8;
int field_140EC;
char field_140F0;
u16 field_140F2;
int field_140F4;
};
static_assert(sizeof(CommonData) == 0x140F8);
CommonData& GetCommonData();
} // namespace game

21
source/game/context.cpp Normal file
View File

@@ -0,0 +1,21 @@
#include "game/context.h"
#include "game/player.h"
namespace game {
act::Actor* GlobalContext::FindActorWithId(act::Id id, act::Type type) const {
if (u8(type) >= actors.lists.size())
return nullptr;
act::Actor* actor = this->actors.lists[u8(type)].first;
while (actor && actor->id != id)
actor = actor->next;
return actor;
}
act::Player* GlobalContext::GetPlayerActor() const {
return static_cast<act::Player*>(this->actors.lists[u8(act::Type::Player)].first);
}
} // namespace game

163
source/game/context.h Normal file
View File

@@ -0,0 +1,163 @@
#pragma once
#include <cstddef>
#include "common/types.h"
#include "common/utils.h"
#include "game/actor.h"
#include "game/pad.h"
namespace game {
namespace act {
class Player;
}
// based on a quick experiment - probably wrong
enum class UiMenuState : u16 {
Closed = 0,
Opening = 1,
Opened = 3,
};
enum class GameStateType : u8 {
/// Initial game state
Initial = 0,
/// Second game state after the initial game state
/// Responsible for allocating a 0x2aa0 byte structure.
FirstGame = 1,
/// Remnant of N64 "ovl_title" state?
Dummy = 2,
/// Sets several variables before changing to state 5.
PrepareTitleScreen = 3,
/// Main game. An instance of this type is unofficially called the "global context".
Play = 4,
/// Initialises player data (notably save data).
/// Responsible for setting time to 0x5555.
InitPlayer = 5,
/// File Select
FileSelect = 6,
/// 72/48/24 Hours Remaining / Dawn of a New Day
DayTelop = 7,
/// Majora's Mask 3D video hints
JokerHintMovie = 8,
/// Majora's Mask 3D credits (including THE END and save prompt)
JokerEnding = 9,
};
// Keeps track of spawned actors.
struct ActorList {
u32 num_actors;
act::Actor* first;
u32 unknown;
};
static_assert(sizeof(ActorList) == 0xc);
struct ActorLists {
u8 gap_0[0xe];
u8 num_actors;
std::array<ActorList, 12> lists;
};
static_assert(sizeof(ActorLists) == 0xa0);
// Likely incomplete.
// The "global context" is actually a game state, and the start of the structure
// is common to all game states. But I haven't bothered looking at the other ones...
struct GlobalContext {
bool IsUiMenuActive() const {
return ui_menu_state != game::UiMenuState::Closed;
}
act::Actor* FindActorWithId(act::Id id, act::Type type) const;
template <typename T>
T* FindActorWithId(act::Id id, act::Type type) const {
return static_cast<T*>(FindActorWithId(id, type));
}
act::Player* GetPlayerActor() const;
int field_0;
u8 gap_4[36];
pad::State pad_state;
u8 gap_94[108];
u32 field_100;
int (*calc_fn)(GlobalContext*);
int (*exit_fn)(GlobalContext*);
void (*next_game_state_init_fn)(GlobalContext*);
u32 field_110;
u32 field_114;
u8 gap_118[12];
u32 field_124;
u8 gap_128[16];
/// Nnumber of frames since the game state was initialised.
u32 frame_counter;
u8 field_13C;
GameStateType type;
u16 field_13E;
u8 gap_140[212];
float field_214;
u8 gap_218[2144];
s16 field_A78;
__attribute__((aligned(4))) u8 gap_A7C[48];
UiMenuState ui_menu_state;
u32 field_AB0;
u8 gap_AB4[76];
int field_B00;
u8 gap_B04[5372];
u32 field_2000;
u8 gap_2004[172];
ActorLists actors;
u8 gap_2150[600];
char field_23A8;
u8 gap_23A9[3];
pad::State pad_state_copy;
u8 gap_2418[624];
u32 field_2688;
u8 gap_268C[22932];
u32 field_8020;
u8 gap_8024[570];
u16 field_825E;
u8 gap_8260[226];
u16 field_8342;
u8 gap_8344[34];
u16 field_8366;
u8 gap_8368[192];
u32 field_8428;
u8 gap_842C[468];
u32 field_8600;
u8 gap_8604[68];
u16 field_8648;
u8 gap_864A[12];
u16 field_8656;
u8 gap_8658[52];
char field_868C;
__attribute__((aligned(4))) u8 gap_8690[20];
bool field_86A4_involved_in_form_check;
__attribute__((aligned(4))) u8 gap_86A8[4];
u16 field_86AC;
__attribute__((aligned(4))) u8 gap_86B0[272];
u32 field_87C0;
u8 gap_87C4[3188];
u32 field_9438;
u8 gap_943C[11204];
u32 field_C000;
u8 gap_C004[604];
char field_C260;
char field_C261;
char field_C262;
char field_C263;
u8 gap_C264[445];
__attribute__((packed)) __attribute__((aligned(1))) u32 field_C421;
u8 gap_C425[163];
char field_C4C8[4];
u16 field_C4CC;
u8 gap_C4CE[91];
u8 field_C529;
u8 gap_C52A[8];
char field_C532;
u8 gap_C533[5];
int field_C538;
};
static_assert(rst::util::OffsetOf(&GlobalContext::field_C000) == 0xc000);
} // namespace game

50
source/game/items.cpp Normal file
View File

@@ -0,0 +1,50 @@
#include "game/items.h"
#include <algorithm>
#include "common/utils.h"
#include "game/common_data.h"
namespace game {
Action ItemToAction(ItemId item) {
// 0x68F800 in Joker 1.0
static constexpr Action table[] = {
Action::Ocarina, Action::Bow, Action::FireArrow, Action::IceArrow,
Action::LightArrow, Action::PictographBox, Action(0xE), Action(0x10),
Action(0x7), Action(0x12), Action(0x30), Action::PictographBox,
Action(0xF), Action::PictographBox, Action(0x54), Action(0xD),
Action(0x6), Action::PictographBox, Action(0x15), Action(0x23),
Action(0x25), Action(0x24), Action(0x29), Action(0x1A),
Action(0x26), Action(0x27), Action(0x16), Action(0x20),
Action(0x20), Action(0x21), Action(0x22), Action(0x17),
Action(0x18), Action(0x19), Action(0x1B), Action(0x1E),
Action(0x1D), Action(0x28), Action(0x2A), Action(0x2B),
Action(0x2C), Action(0x2D), Action(0x31), Action(0x32),
Action(0x33), Action(0x2E), Action(0x35), Action(0x2F),
Action(0x38), Action(0x3A), Action::DekuMask, Action::GoronMask,
Action::ZoraMask, Action(0x50), Action(0x3C), Action(0x3D),
Action(0x3E), Action(0x3F), Action(0x40), Action(0x41),
Action(0x42), Action(0x43), Action(0x44), Action(0x45),
Action(0x46), Action(0x47), Action(0x48), Action(0x49),
Action(0x4A), Action(0x4B), Action(0x4C), Action(0x4D),
Action(0x4E), Action(0x4F), Action::FireArrow, Action::IceArrow,
Action::LightArrow, Action(0x3), Action(0x4), Action(0x5),
Action(0x6), Action::None, Action::None, Action::None,
};
if (u8(item) >= std::size(table))
return Action(0xffffffff);
return table[u8(item)];
}
bool HasOcarina() {
const auto& items = GetCommonData().save.inventory.items;
return items[0] == ItemId::Ocarina;
}
bool HasMask(ItemId item_id) {
const auto& masks = GetCommonData().save.inventory.masks;
return std::any_of(masks.begin(), masks.end(), [&](ItemId id) { return item_id == id; });
}
} // namespace game

79
source/game/items.h Normal file
View File

@@ -0,0 +1,79 @@
#pragma once
#include <array>
#include "common/types.h"
namespace game {
enum class ItemId : u8 {
Ocarina = 0x0,
Bow = 1,
FireArrow = 2,
IceArrow = 3,
LightArrow = 4,
Bomb = 6,
Bombchu = 7,
DekuStick = 8,
DekuNuts = 9,
MagicBean = 0xa,
PowderKeg = 0xc,
PictographBox = 0xd,
LensOfTruth = 0xe,
Hookshot = 0xf,
GreatFairySword = 0x10,
Bottle = 0x12,
RedPotion = 0x13,
GreenPotion = 0x14,
BluePotion = 0x15,
Fairy = 0x16,
DekuPrincess = 0x17,
Milk = 0x18,
MilkHalf = 0x19,
Fish = 0x1a,
Bug = 0x1b,
Poe = 0x1d,
BigPoe = 0x1e,
Water = 0x1f,
HotSpringWater = 0x20,
ZoraEgg = 0x21,
GoldDust = 0x22,
MagicalMushroom = 0x23,
SeaHorse = 0x24,
ChateauRomani = 0x25,
MoonTear = 0x28,
DekuMask = 0x32,
GoronMask = 0x33,
ZoraMask = 0x34,
FierceDeityMask = 0x35,
None = 0xff,
};
enum class Action : u8 {
None = 0,
Bow = 0x9,
FireArrow = 0xa,
IceArrow = 0xb,
LightArrow = 0xc,
PictographBox = 0x13,
Ocarina = 0x14,
GoronMask = 0x51,
ZoraMask = 0x52,
DekuMask = 0x53,
};
Action GetActionForItem(ItemId item);
// Mask IDs are action IDs - 0x3b
enum class MaskId : u8 {
GiantMask = 0x14,
FierceDeityMask = 0x15,
GoronMask = 0x16,
ZoraMask = 0x17,
DekuMask = 0x18,
};
bool HasOcarina();
bool HasMask(ItemId item_id);
} // namespace game

107
source/game/pad.h Normal file
View File

@@ -0,0 +1,107 @@
#pragma once
#include "common/flags.h"
#include "common/types.h"
namespace game::pad {
enum class Button : u32 {
A = 0x1,
B = 0x2,
Select = 0x4,
Start = 0x8,
Right = 0x10,
Left = 0x20,
Up = 0x40,
Down = 0x80,
R = 0x100,
L = 0x200,
X = 0x400,
Y = 0x800,
Debug = 0x1000,
Gpio14 = 0x2000,
ZL = 0x4000,
ZR = 0x8000,
CStickRight = 0x1000000,
CStickLeft = 0x2000000,
CStickUp = 0x4000000,
CStickDown = 0x8000000,
MainStickRight = 0x10000000,
MainStickLeft = 0x20000000,
MainStickUp = 0x40000000,
MainStickDown = 0x80000000,
};
struct State {
struct Input {
s16 main_stick_x;
s16 main_stick_y;
s16 c_stick_x;
s16 c_stick_y;
rst::Flags<Button> buttons;
rst::Flags<Button> new_buttons;
rst::Flags<Button> released_buttons;
u8 field_14;
u8 field_15;
u8 field_16;
u8 field_17;
};
static_assert(sizeof(Input) == 0x18);
struct AnalogInput {
/// Horizontal axis. From -1.0 (left) to 1.0 (right).
float x;
/// Vertical axis. From -1.0 (bottom) to 1.0 (top).
float y;
/// Horizontal axis. From -60.0 (left) to 60.0 (right).
float x_raw;
/// Vertical axis. From -60.0 (bottom) to 60.0 (top).
float y_raw;
float x_raw_last;
float y_raw_last;
};
static_assert(sizeof(AnalogInput) == 0x18);
Input input;
Input input_last;
AnalogInput main_stick;
AnalogInput c_stick;
u32 field_60;
/// Buttons, but the value switches between input.buttons and 0 every other frame...
rst::Flags<Button> field_64;
/// Buttons, but the value switches between input.buttons and 0 every other frame...
/// 0 when field_64 is non-zero, and vice versa.
rst::Flags<Button> field_68;
};
static_assert(sizeof(State) == 0x6c);
enum class TouchscreenButton : u8 {
I = 1 << 0,
II = 1 << 1,
/// Note: does not support holding.
PictographBox = 1 << 2,
};
struct TouchscreenState {
rst::Flags<TouchscreenButton> buttons;
rst::Flags<TouchscreenButton> new_buttons;
};
static_assert(sizeof(TouchscreenState) == 2);
struct ControllerInfo {
/// state is copied from ControllerMgr to GlobalContext, then to the Player actor
State* state;
TouchscreenState* touchscreen;
/// 0.0-60.0
float main_stick_strength;
/// 0x0000 (top) to 0xffff (counterclockwise)
u16 angle;
u8 gap_E[22];
u32 field_24;
u32 field_28;
u8 gap_2C[20];
u32 field_40;
u32 field_44;
};
} // namespace game::pad

11
source/game/player.cpp Normal file
View File

@@ -0,0 +1,11 @@
#include "game/player.h"
#include "common/utils.h"
namespace game::act {
FormParam& GetFormParam(FormParamIndex idx) {
return rst::util::GetPointer<FormParam>(0x7AE9E8, 0x7AF9E8)[u8(idx) % 8];
}
} // namespace game::act

449
source/game/player.h Normal file
View File

@@ -0,0 +1,449 @@
#pragma once
#include "common/flags.h"
#include "common/types.h"
#include "common/utils.h"
#include "game/actor.h"
#include "game/context.h"
#include "game/items.h"
#include "game/pad.h"
namespace game::act {
enum class FormParamIndex : u8 {
FierceDeity = 0,
Human = 1,
Giant = 2,
Deku = 3,
Zora = 4,
ZoraDiving = 5,
Goron = 6,
Unknown = 7,
};
struct FormParam {
u16 run_accel;
u16 run_decel;
u16 field_4;
u16 field_6;
u16 field_8;
u16 field_A;
u16 field_C;
u16 field_E;
u16 field_10;
u16 field_12;
u16 roll_decel_maybe;
u16 walk_speed;
u16 field_18;
u16 field_1A;
u16 field_1C;
u16 field_1E;
u16 field_20;
u16 field_22;
u16 field_24;
u16 field_26;
};
FormParam& GetFormParam(FormParamIndex idx);
// XXX: Very incomplete.
struct Player : public Actor {
enum class Form : u8 {
FierceDeity = 0,
Goron = 1,
Zora = 2,
Deku = 3,
Human = 4,
};
enum class ActionType : u8 {
Type1 = 1,
Type2 = 2,
Type3 = 3,
Type4 = 4,
OcarinaOrTransformation = 5,
};
enum class Flag1 : u32 {
Locked = 0x1,
Unk2 = 0x2,
Unk4 = 0x4,
Unk8 = 0x8,
Unk10 = 0x10,
Unk20 = 0x20,
Unk40 = 0x40,
Unk80 = 0x80,
Unk100 = 0x100,
Unk200 = 0x200,
Unk400 = 0x400,
Unk800 = 0x800,
Unk1000 = 0x1000,
Unk2000 = 0x2000,
Unk4000 = 0x4000,
Unk8000 = 0x8000,
Unk10000 = 0x10000,
Unk20000 = 0x20000,
Unk40000 = 0x40000,
Unk80000 = 0x80000,
Unk100000 = 0x100000,
Unk200000 = 0x200000,
Unk400000 = 0x400000,
Unk800000 = 0x800000,
Unk1000000 = 0x1000000,
Unk2000000 = 0x2000000,
Unk4000000 = 0x4000000,
InWater = 0x8000000,
FreezeWorld = 0x10000000,
FreezeLink = 0x20000000,
Unk40000000 = 0x40000000,
Unk80000000 = 0x80000000,
};
enum class Flag3 : u32 {
Unk1 = 0x1,
Unk2 = 0x2,
Unk4 = 0x4,
AttackingB = 0x8,
Unk10 = 0x10,
Unk20 = 0x20,
Shooting = 0x40,
Unk80 = 0x80,
DekuInFlower = 0x100,
DekuLaunching = 0x200,
DekuStuffMaybe = 0x400,
Unk800 = 0x800,
GoronRolling = 0x1000,
DekuFlyingCamera = 0x2000,
Unk4000 = 0x4000,
ZoraFastSwimCamera = 0x8000,
Unk10000 = 0x10000,
AfterChangeMask = 0x20000,
Unk40000 = 0x40000,
GoronRollingFast = 0x80000,
DekuSpin = 0x100000,
Unk200000 = 0x200000,
Unk400000 = 0x400000,
AfterUseBoomerang = 0x800000,
DekuNutsOnB = 0x1000000,
Unk2000000 = 0x2000000,
Unk4000000 = 0x4000000,
Unk8000000 = 0x8000000,
Unk10000000 = 0x10000000,
Unk20000000 = 0x20000000,
Unk40000000 = 0x40000000,
Unk80000000 = 0x80000000,
};
char field_1F8;
FormParamIndex form_param_idx;
char field_1FA;
u8 current_action_flags;
ItemId held_item;
FormParamIndex form_param_idx2;
Action action;
Form active_form;
ItemId transform_mask_item_id;
char field_201;
char field_202;
u8 gap_203[4];
u8 field_207;
u8 gap208[3];
MaskId active_mask_id;
char field_20C;
MaskId previous_mask_id;
char field_20E;
u8 gap_20F[293];
u32 field_334;
u8 gap_338[48];
u32 field_368;
u8 gap_36C[4];
u32 field_370;
float field_374;
u8 gap_378[4];
float field_37C;
float field_380;
u8 gap_384[4];
float field_388;
u8 gap_38C[6];
__attribute__((packed)) __attribute__((aligned(1))) u32 field_392;
u8 gap_396[30];
u32 field_3B4;
u32 field_3B8;
u8 gap_3BC;
u8 gap_3BD[19];
u32 field_3D0;
u8 gap_3D4[128];
u32 field_454;
u8 gap_458[24];
u32 field_470;
u8 gap_474[652];
u32 field_700;
u8 gap_704[12];
u32 field_710;
u8 gap_714[196];
u32 field_7D8;
u8 gap_7DC[136];
int field_864;
int field_868;
u8 gap_86C[116];
u32 field_8E0;
u32 field_8E4;
u8 gap_8E8[8];
float field_8F0;
u8 gap_8F4[8];
u32 field_8FC;
u32 field_900;
u8 gap_904[16];
char field_914[12];
int field_920;
u8 gap_924[4];
u32 field_928;
u8 some_fn_idx;
char other_fn_idx;
char field_92E;
char field_92F;
u8 gap_930[8];
Vec3 field_938;
u32 field_944;
u8 gap_948[10];
u16 field_952;
s16 field_954;
u8 gap_956[22];
char field_96C;
__attribute__((aligned(2))) u8 gap_96E[50];
u32 field_9A0;
u32 field_9A4;
u8 gap_9A8[20];
u32 field_9BC;
u8 gap_9C0[12];
char field_9CC;
char field_9CD;
char field_9CE;
char field_9CF;
u8 gap_9D0[4];
int field_9D4;
u8 gap_9D8[4];
float field_9DC;
u8 gap_9E0[28];
float field_9FC;
float field_A00;
float field_A04;
u8 gapA08[1];
u8 gapA09[11];
u16 field_A14;
u8 gap_A16[126];
u16 field_A94;
u8 gap_A96[126];
u16 field_B14;
u8 gap_B16[126];
u16 field_B94;
u8 gap_B96[126];
u16 field_C14;
u8 gap_C16[126];
u32 field_C94;
u8 gap_C98[13];
char field_CA5[7];
u32 field_CAC;
u8 gap_CB0[17];
char field_CC1[1];
char field_CC2[2];
u32 field_CC4;
u8 gap_CC8[4];
u32 field_CCC;
u8 gap_CD0[4];
float field_CD4;
float field_CD8;
float field_CDC;
u8 gap_CE0[12];
u16 field_CEC;
u8 gap_CEE[270];
u32 field_DFC;
u8 gap_E00[4];
u32 field_E04;
u32 field_E08;
u32 field_E0C;
u32 field_E10;
void (*state_handler_fn)(Player*, GlobalContext*);
u8 gap_E18[4763];
u8 field_20B3;
u8 gap_20B4[61260];
char field_11000;
u8 gap1[3071];
u32 field_11C00;
u8 gap_11C04[252];
u32 field_11D00;
u8 gap_11D04[168];
u32 field_11DAC;
rst::Flags<Flag1> flags1;
u32 flags2;
rst::Flags<Flag3> flags3;
u32 flags4;
int field_11DC0;
u32 field_11DC4;
u8 gap_DC8[4];
u16 field_DCC;
char field_11DCE;
char active_item_id;
u32 field_11DD0;
int field_11DD4;
int field_11DD8;
float field_11DDC;
u8 gap_11DE0[4];
int field_11DE4;
float field_11DE8;
float field_11DEC;
u8 gap_11DF0;
ActionType action_type;
u16 field_11DF2;
u16 field_11DF4;
u16 field_11DF6;
u16 field_11DF8;
u8 gap_11DFA;
u8 gap_11DFB;
u16 field_11DFC;
u16 field_11DFE;
u16 field_11E00;
__attribute__((packed)) __attribute__((aligned(1))) u32 field_11E02;
u16 field_11E06;
u16 field_11E08;
u16 field_11E0A;
u16 field_11E0C;
u16 field_11E0E;
u16 field_11E10;
u8 gap_11E12[2];
u32 field_11E14;
float field_11E18;
float field_11E1C;
u32 field_11E20;
u8 gap_11E24[8];
u32 field_11E2C;
float lin_vel;
u16 angle;
u16 field_11E36;
u32 field_11E38;
u8 gap_11E3C;
char field_11E3D[1];
char field_111E3E;
u8 field_11E3F;
char field_11E40;
char field_11E41;
u8 gap_11E42[10];
char field_11E4C;
char field_11E4D;
u16 field_11E4E;
u8 gap_11E50[4];
u16 field_11E54;
u8 gap_11E56[22];
float lin_vel_xxx;
float lin_vel_xxx2;
float field_11E74;
float field_11E78;
u32 field_11E7C;
u32 field_11E80;
u32 field_11E84;
float field_E88;
u16 field_11E8C;
u16 field_11E8E;
u8 gap_11E90;
char field_11E91;
char field_11E92;
char field_11E93;
float field_11E94;
u32 field_11E98;
u8 gap_11E9C[20];
float lin_vel_max;
int field_11EB4;
float field_11EB8;
float field_11EBC;
u8 gap_11EC0[6];
char field_11EC6;
char field_11EC7;
u16 field_11EC8;
u16 zora_barrier_timer;
u16 field_11ECC;
char field_11ECE[1];
char field_11ECF;
u8 gapED0[3];
u8 gap_11ED3;
u32 field_11ED4;
u8 gap_11ED8[4];
u32 field_11EDC;
u8 gap_11EE0;
char field_11EE1[11];
float field_11EEC;
u16 field_11EF0;
u16 is_zora_slow_swim;
u16 field_EF4;
u16 field_EF6;
u16 field_EF8;
u16 field_EFA;
u16 field_EFC;
u16 zora_fast_swim_countdown;
u16 field_F00;
u16 field_F02;
u16 field_F04;
u16 field_F06;
u32 field_11F08;
u32 field_11F0C;
u8 gap_11F10[20];
u32 field_11F24;
u8 gap_11F28[20];
u32 field_11F3C;
u8 gap_11F40[48];
u32 field_11F70;
u8 gap_11F74[140];
u32 field_12000;
u8 gap_12004[110];
__attribute__((packed)) __attribute__((aligned(1))) int field_12072;
u8 gap_1076[66];
u32 field_10B8;
u8 gap_10BC[172];
u32 field_1168;
u8 gap_116C[492];
u32 field_1358;
u8 gap_135C[160];
u32 field_13FC;
u8 gap_1400[160];
u32 field_14A0;
u8 gap_14A4[860];
int field_12800;
u8 gap_1804[436];
u32 field_19B8;
u32 field_129BC;
u8 gap_129C0[12];
pad::ControllerInfo controller_info;
u8 gap_12A14[36];
u32 field_12A38;
u8 gap_12A3C[4];
u32 field_12A40;
u8 gap_12A44[12];
u32 field_12A50;
u8 gap_12A54[28];
u32 field_12A70;
u8 gap_12A74[40];
s16 field_12A9C;
s16 field_12A9E;
u32 field_12AA0;
u16 field_12AA4;
u8 gap_12AA6[62];
float field_12AE4;
u8 gap_12AE8[4];
bool field_12AEC;
u8 field_12AED;
u8 gap_12AEE[2];
u8 gap_12AF0[292];
float field_12C14;
u8 gap_12C18[32];
float field_12C38;
u8 gap_12C3C[8];
float field_12C44;
u8 gap_12C48[134];
s16 field_12CCE;
};
static_assert(rst::util::OffsetOf(&Player::transform_mask_item_id) == 0x200);
static_assert(rst::util::OffsetOf(&Player::field_12CCE) == 0x12CCE);
// TODO: complete the struct and add a size assertion.
} // namespace game::act

View File

@@ -0,0 +1,11 @@
#include "game/static_context.h"
#include "common/utils.h"
namespace game {
StaticContext& GetStaticContext() {
return *rst::util::GetPointer<StaticContext>(0x7892D0, 0x78A2D0);
}
} // namespace game

2807
source/game/static_context.h Normal file

File diff suppressed because it is too large Load Diff

107
source/rst/fixes.cpp Normal file
View File

@@ -0,0 +1,107 @@
#include "rst/fixes.h"
#include <optional>
#include "common/context.h"
#include "common/debug.h"
#include "common/utils.h"
#include "game/actor.h"
#include "game/actors/boss_twinmold.h"
#include "game/common_data.h"
#include "game/context.h"
namespace rst {
void FixTime() {
game::CommonData& cdata = game::GetCommonData();
// Restore the effectiveness of the Inverted Song of Time.
//
// In MM, the normal time speed is +3 and the ISoT sets the extra time speed to -2, resulting
// in a +1 effective time speed (which means 1/3 time speed).
//
// In MM3D, the normal speed is +2 and the ISoT only sets the extra speed to -1, which still
// gives the player a +1 effective speed, but only 1/2 time speed.
//
// A quick fix is to skip incrementing the in-game time every 6 frames,
// giving us the desired ratio of 2/6.
if (cdata.save.extra_time_speed == -2)
cdata.save.extra_time_speed = -1;
if (cdata.save.extra_time_speed == -1 && GetContext().gctx->frame_counter % 6 == 0)
cdata.save.extra_time_speed = -2;
}
struct TwinmoldFixState {
s8 blue_prev_life;
s8 red_prev_life;
game::act::BossTwinmold::Status red_prev_status;
u16 red_prev_hit_counter;
bool is_hit_counter_sane;
};
void FixTwinmold() {
static std::optional<TwinmoldFixState> state{};
const game::GlobalContext* gctx = GetContext().gctx;
auto* red_twinmold = gctx->FindActorWithId<game::act::BossTwinmold>(game::act::Id::BossTwinmold,
game::act::Type::Boss);
if (!red_twinmold) {
state.reset();
return;
}
auto* blue_twinmold = red_twinmold->other_twinmold_actor;
if (state) {
// Red Twinmold has 12 HP (after killing their blue friend).
//
// Spinning it deals 3-5 damage points based on lin_vel_xxx:
// boss->life -= 3 + (5 - 2) * player->lin_vel_xxx;
// It is possible to deal 5 damage to the boss by spinning the main stick,
// but that's not obvious at all...
//
// If the player spins the main stick (which is not an obvious thing to do...),
// killing Red Twinmold takes 3 identical cycles.
// If not, 4 cycles (!) are required.
//
// Let's make that less tedious and less boring by reducing the number of required cycles
// (1 if the player touches the stick, 2 otherwise).
if (state->red_prev_life > red_twinmold->life) {
util::Print("%s: dealing more damage to Red Twinmold", __func__);
red_twinmold->life -= 8;
}
// Only update the hit counter if it is sane. One way of ensuring that condition is satisfied
// is to only consider the counter to be sane after the player has hit Twinmold once.
if (red_twinmold->hit_counter == 8)
state->is_hit_counter_sane = true;
// 10 hits are required to stun Red or Blue Twinmold. This would have been acceptable
// if it weren't for the fact that Red Twinmold regularly burrows back into sand during phase 2
// and the hit counter is reset every time that happens.
// This makes for a confusing experience the first time the player fights Twinmold,
// as there is nothing in the game that indicates that the hit counter resets every time
// (and it's still frustrating on subsequent playthroughs).
//
// Fix that by restoring the previous hit counter after it's been reset by the game.
const bool was_reset = red_twinmold->hit_counter == 9 && state->red_prev_hit_counter != 9;
const bool is_legit_reset = red_twinmold->status == game::act::BossTwinmold::Status::Stunned;
if (state->is_hit_counter_sane && was_reset && !is_legit_reset) {
util::Print("%s: restoring hit counter (%u)", __func__, state->red_prev_hit_counter);
red_twinmold->hit_counter = state->red_prev_hit_counter;
}
} else {
util::Print("%s: initialising state", __func__);
state.emplace();
state->is_hit_counter_sane = false;
}
state->blue_prev_life = blue_twinmold->life;
state->red_prev_life = red_twinmold->life;
state->red_prev_status = red_twinmold->status;
if (state->is_hit_counter_sane)
state->red_prev_hit_counter = red_twinmold->hit_counter;
}
} // namespace rst

9
source/rst/fixes.h Normal file
View File

@@ -0,0 +1,9 @@
#pragma once
namespace rst {
void FixTime();
void FixTwinmold();
} // namespace rst

133
source/rst/link.cpp Normal file
View File

@@ -0,0 +1,133 @@
#include "rst/link.h"
#include <algorithm>
#include "common/context.h"
#include "common/debug.h"
#include "common/utils.h"
#include "game/context.h"
#include "game/items.h"
#include "game/pad.h"
#include "game/player.h"
namespace rst::link {
void Init() {
// This reverts some of the MM3D changes to form-specific parameters.
// Fix Deku Link's walk acceleration value
auto& deku_param = game::act::GetFormParam(game::act::FormParamIndex::Deku);
deku_param.run_accel = 200;
// Make Giant Link less painfully slow
auto& giant_param = game::act::GetFormParam(game::act::FormParamIndex::Giant);
giant_param.run_accel = 100;
giant_param.walk_speed = 350;
}
namespace {
struct TransformAction {
game::pad::Button trigger_btn;
bool allow_in_water;
game::ItemId required_mask;
game::Action action;
const char* name;
};
static constexpr TransformAction s_actions[] = {
{game::pad::Button::Left, true, game::ItemId::ZoraMask, game::Action::ZoraMask, "Zora"},
{game::pad::Button::Up, false, game::ItemId::GoronMask, game::Action::GoronMask, "Goron"},
{game::pad::Button::Down, false, game::ItemId::DekuMask, game::Action::DekuMask, "Deku"},
};
bool CanUseFastAction(game::act::Player* player) {
if (GetContext().gctx->IsUiMenuActive()) {
util::Print("%s: player is in a menu, skipping", __func__);
return false;
}
if (player->flags1.IsSet(game::act::Player::Flag1::FreezeLink)) {
util::Print("%s: Flag1::FreezeLink is set, skipping", __func__);
return false;
}
if (player->active_mask_id == game::MaskId::GiantMask) {
util::Print("%s: wearing Giant's Mask, skipping", __func__);
return false;
}
return true;
}
} // namespace
void HandleFastTransform() {
game::GlobalContext* gctx = GetContext().gctx;
game::act::Player* player = gctx->GetPlayerActor();
if (!player)
return;
const auto it =
std::find_if(std::begin(s_actions), std::end(s_actions), [&](const TransformAction& action) {
return gctx->pad_state.input.new_buttons.IsSet(action.trigger_btn);
});
if (it == std::end(s_actions))
return;
if (!CanUseFastAction(player))
return;
if (!game::HasMask(it->required_mask)) {
util::Print("%s: player does not have the %s Mask, skipping", __func__, it->name);
return;
}
if (!it->allow_in_water && player->flags1.IsSet(game::act::Player::Flag1::InWater)) {
util::Print("%s: cannot transform into %s in water, skipping", __func__, it->name);
return;
}
util::Print("%s: transforming (%s)", __func__, it->name);
player->action = it->action;
player->action_type = game::act::Player::ActionType::OcarinaOrTransformation;
}
void HandleFastOcarina() {
game::GlobalContext* gctx = GetContext().gctx;
game::act::Player* player = gctx->GetPlayerActor();
if (!player)
return;
if (!gctx->pad_state.input.new_buttons.IsSet(game::pad::Button::ZR))
return;
if (!CanUseFastAction(player))
return;
if (!game::HasOcarina())
return;
if (gctx->pad_state.input.buttons.IsSet(game::pad::Button::R))
return;
// The ocarina can only be used when grounded (in particular for Zora Link).
if (!player->flags_94.IsSet(game::act::Actor::Flag94::Grounded))
return;
player->action = game::Action::Ocarina;
player->action_type = game::act::Player::ActionType::OcarinaOrTransformation;
}
bool ShouldUseZoraFastSwim() {
const auto& btns = GetContext().gctx->pad_state.input.buttons;
return btns.IsSet(game::pad::Button::A) &&
(btns.IsSet(game::pad::Button::R) || !btns.IsSet(game::pad::Button::ZL));
}
} // namespace rst::link
extern "C" {
bool rst_link_ShouldUseZoraFastSwim() {
return rst::link::ShouldUseZoraFastSwim();
}
}

10
source/rst/link.h Normal file
View File

@@ -0,0 +1,10 @@
#pragma once
namespace rst::link {
void Init();
void HandleFastTransform();
void HandleFastOcarina();
} // namespace rst::link

61
source/rst/main.cpp Normal file
View File

@@ -0,0 +1,61 @@
#include "common/context.h"
#include "common/debug.h"
#include "common/types.h"
#include "common/utils.h"
#include "game/context.h"
#include "rst/fixes.h"
#include "rst/link.h"
namespace rst {
namespace {
extern "C" {
extern char* fake_heap_start;
extern char* fake_heap_end;
}
void Init(Context& context) {
// Just in case something needs to be dynamically allocated...
static char s_fake_heap[0x10000];
fake_heap_start = &s_fake_heap[0];
fake_heap_end = &s_fake_heap[sizeof(s_fake_heap)];
link::Init();
util::Print("Project Restoration initialised");
context.has_initialised = true;
}
// Important: do NOT assume the player actor exists here.
// This is called as soon as a game state is initialised,
// not necessarily when the global context is initialised.
void UpdateContext(game::GlobalContext* gctx) {
Context& context = GetContext();
context.gctx = gctx;
if (!context.has_initialised)
Init(context);
}
} // anonymous namespace
void Calc(game::GlobalContext* gctx) {
UpdateContext(gctx);
if (gctx->type != game::GameStateType::Play)
return;
link::HandleFastTransform();
link::HandleFastOcarina();
FixTime();
FixTwinmold();
}
} // namespace rst
extern "C" {
void rst_Calc(game::GlobalContext* gctx) {
rst::Calc(gctx);
}
}

19
source/rst/trampolines.s Normal file
View File

@@ -0,0 +1,19 @@
.text
.align 4
.macro TRAMPOLINE_R0_RESULT name
.global rst_trampoline_\name
.type rst_trampoline_\name, %function
.align 4
rst_trampoline_\name:
push {r1-r12, lr}
bl \name
pop {r1-r12, pc}
.endm
.global rst_dummy
rst_dummy:
nop
TRAMPOLINE_R0_RESULT rst_link_ShouldUseZoraFastSwim

1
v100/Version.cmake Normal file
View File

@@ -0,0 +1 @@
target_compile_definitions(newcode PRIVATE RST_VER=0)

82
v100/hooks.hks Normal file
View File

@@ -0,0 +1,82 @@
zora_swim_1a:
# Remove fast swim magic check
type: patch
data: E3A00001
addr: 0x00220F60
reverse: true
zora_swim_1b:
# Remove fast swim magic check
type: patch
data: E3A00001
addr: 0x002210DC
reverse: true
zora_swim_1c:
# Remove fast swim magic check
type: patch
data: E3A00001
addr: 0x001FFDBC
reverse: true
zora_swim_2:
# Change fast swim start trigger (A+R -> A)
type: branch
link: true
func: rst_trampoline_rst_link_ShouldUseZoraFastSwim
addr: 0x220EFC
zora_swim_2:
type: patch
data: 00 F0 20 E3 01 00 50 E3
addr: 0x220F2C
zora_swim_3a:
# Change fast swim continue trigger (A+R -> A)
type: branch
link: true
func: rst_trampoline_rst_link_ShouldUseZoraFastSwim
addr: 0x1FFD74
zora_swim_3a:
type: patch
data: 00 F0 20 E3 00 F0 20 E3 01 00 50 E3
addr: 0x1FFD78
zora_swim_3b:
# Change fast swim continue trigger (A+R -> A)
type: branch
link: true
func: rst_trampoline_rst_link_ShouldUseZoraFastSwim
addr: 0x1FFA84
zora_swim_3b:
type: patch
data: 00 F0 20 E3 00 F0 20 E3 01 00 50 E3
addr: 0x1FFA88
zora_swim_4:
type: patch
data: EA000009
addr: 0x00220F00
reverse: true
fix_transformation_mask_equip_checks_1:
# prevent forced transform when mask is not equipped
type: patch
data: E12FFF1E
addr: 0x001E76B0
reverse: true
fix_transformation_mask_equip_checks_2a:
# remove other checks (fix first-person mode, Goron rolling and potentially more)
type: patch
data: EA00003B
addr: 0x001EDFB4
reverse: true
fix_transformation_mask_equip_checks_2b:
type: patch
data: EA000052
reverse: true
addr: 0x001F78CC
decouple_trigger_btns:
type: patch
data: 12 00 00 EA # skips over the ZL/ZR checks
addr: 0x1166C8
main_hook:
type: softbranch
opcode: post
func: rst_Calc
addr: 0x0010676C

1
v110/Version.cmake Normal file
View File

@@ -0,0 +1 @@
target_compile_definitions(newcode PRIVATE RST_VER=1)

82
v110/hooks.hks Normal file
View File

@@ -0,0 +1,82 @@
zora_swim_1a:
# Remove fast swim magic check
type: patch
data: E3A00001
addr: 0x00220F50
reverse: true
zora_swim_1b:
# Remove fast swim magic check
type: patch
data: E3A00001
addr: 0x002210CC
reverse: true
zora_swim_1c:
# Remove fast swim magic check
type: patch
data: E3A00001
addr: 0x001FFDA8
reverse: true
zora_swim_2:
# Change fast swim start trigger (A+R -> A)
type: branch
link: true
func: rst_trampoline_rst_link_ShouldUseZoraFastSwim
addr: 0x220EEC
zora_swim_2:
type: patch
data: 00 F0 20 E3 01 00 50 E3
addr: 0x220F1C
zora_swim_3a:
# Change fast swim continue trigger (A+R -> A)
type: branch
link: true
func: rst_trampoline_rst_link_ShouldUseZoraFastSwim
addr: 0x1FFD64
zora_swim_3a:
type: patch
data: 00 F0 20 E3 00 F0 20 E3 01 00 50 E3
addr: 0x1FFD68
zora_swim_3b:
# Change fast swim continue trigger (A+R -> A)
type: branch
link: true
func: rst_trampoline_rst_link_ShouldUseZoraFastSwim
addr: 0x1FFA74
zora_swim_3b:
type: patch
data: 00 F0 20 E3 00 F0 20 E3 01 00 50 E3
addr: 0x1FFA78
zora_swim_4:
type: patch
data: EA000009
addr: 0x00220EF0
reverse: true
fix_transformation_mask_equip_checks_1:
# prevent forced transform when mask is not equipped
type: patch
data: E12FFF1E
addr: 0x001E76A0
reverse: true
fix_transformation_mask_equip_checks_2a:
# remove other checks (fix first-person mode, Goron rolling and potentially more)
type: patch
data: EA00003B
addr: 0x001EDFA4
reverse: true
fix_transformation_mask_equip_checks_2b:
type: patch
data: EA000052
reverse: true
addr: 0x001F78BC
decouple_trigger_btns:
type: patch
data: 12 00 00 EA # skips over the ZL/ZR checks
addr: 0x1167C8
main_hook:
type: softbranch
opcode: post
func: rst_Calc
addr: 0x106798