mirror of
https://github.com/leoetlino/project-restoration
synced 2025-10-05 16:22:49 +02:00
Initial commit
This commit is contained in:
76
.clang-format
Normal file
76
.clang-format
Normal 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
20
.gitignore
vendored
Normal 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
66
DevkitArm3DS.cmake
Normal 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
17
Makefile
Normal 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
336
license.md
Normal 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
24
loader/CMakeLists.txt
Normal 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
15
loader/Makefile
Normal 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
22
loader/linker_template.ld
Normal 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
5
loader/source/hooks.hks
Normal file
@@ -0,0 +1,5 @@
|
||||
LoaderEntryPoint:
|
||||
type: softbranch
|
||||
opcode: post
|
||||
func: LoaderMain
|
||||
addr: 0x00100000
|
43
loader/source/loader.c
Normal file
43
loader/source/loader.c
Normal 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(¤tPid, 0xffff8001);
|
||||
res = svcOpenProcess(&handle, currentPid);
|
||||
|
||||
if (res != 0)
|
||||
return 0;
|
||||
|
||||
return handle;
|
||||
}
|
45
loader/source/svc.s
Normal file
45
loader/source/svc.s
Normal 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
57
make_release.sh
Executable 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
114
readme.md
Normal 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
51
source/CMakeLists.txt
Normal 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
10
source/common/context.cpp
Normal 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
18
source/common/context.h
Normal 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
40
source/common/debug.cpp
Normal 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
16
source/common/debug.h
Normal 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
39
source/common/flags.h
Normal 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
8
source/common/svc.s
Normal 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
22
source/common/types.h
Normal 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
52
source/common/utils.h
Normal 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
177
source/game/actor.h
Normal 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
|
167
source/game/actors/boss_twinmold.h
Normal file
167
source/game/actors/boss_twinmold.h
Normal 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
|
12
source/game/common_data.cpp
Normal file
12
source/game/common_data.cpp
Normal 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
582
source/game/common_data.h
Normal 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
21
source/game/context.cpp
Normal 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
163
source/game/context.h
Normal 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
50
source/game/items.cpp
Normal 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
79
source/game/items.h
Normal 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
107
source/game/pad.h
Normal 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
11
source/game/player.cpp
Normal 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
449
source/game/player.h
Normal 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
|
11
source/game/static_context.cpp
Normal file
11
source/game/static_context.cpp
Normal 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
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
107
source/rst/fixes.cpp
Normal 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
9
source/rst/fixes.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
namespace rst {
|
||||
|
||||
void FixTime();
|
||||
|
||||
void FixTwinmold();
|
||||
|
||||
} // namespace rst
|
133
source/rst/link.cpp
Normal file
133
source/rst/link.cpp
Normal 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
10
source/rst/link.h
Normal 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
61
source/rst/main.cpp
Normal 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
19
source/rst/trampolines.s
Normal 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
1
v100/Version.cmake
Normal file
@@ -0,0 +1 @@
|
||||
target_compile_definitions(newcode PRIVATE RST_VER=0)
|
82
v100/hooks.hks
Normal file
82
v100/hooks.hks
Normal 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
1
v110/Version.cmake
Normal file
@@ -0,0 +1 @@
|
||||
target_compile_definitions(newcode PRIVATE RST_VER=1)
|
82
v110/hooks.hks
Normal file
82
v110/hooks.hks
Normal 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
|
Reference in New Issue
Block a user