Thank you for your interest in the project!
We ask that you please follow these contribution guidelines to ensure consistency across the codebase and its documentation.
└── src / include:
├── egg: Nintendo EAD middleware
├── MetroTRK: Metrowerks Target Resident Kernel (TRK) Debugger
├── MSL: Metrowerks Standard Library
├── nw4r: NintendoWare for Revolution (NW4R) SDK
├── Pack: Revolution Pack Project (RP)
├── revolution: Revolution (RVL) SDK
├── runtime: CodeWarrior compiler runtime
└── RVLFaceLib: Revolution Face Library (RFL)
Please use the following file extensions for code:
| File Extension | Usage |
|---|---|
*.c |
C function definition(s) |
*.cpp |
C++ function/class definition(s) |
*.h |
C/C++ function/class declaration(s) |
This repository contains a .clang-format configuration file which handles most of the code formatting automatically.
Even then, there are some important style elements to manually conform to:
Although this project will likely always use the CodeWarrior compiler, which supports the #pragma once directive, please use standard include guards instead.
This is done both for readability purposes and to provide context about the file inside the guard:
#ifndef EGG_CORE_ARCHIVE_H->include/egg/core/eggArchive.h#ifndef NW4R_UT_LIST_H->include/nw4r/ut/ut_list.h
Please use the following format:
#ifndef {library}_{module}_{filename}_H
#define {library}_{module}_{filename}_H
// . . .
#endifIdentifier terms separated by case/underscores should be converted to underscores in the guard name:
myClass->MY_CLASSMyClass->MY_CLASSMy_Class->MY_CLASSMYClass->MY_CLASS
Important
The only exception is for Pack Project (RP) files. The classes exist in the global namespace, and as such often include the module name in both the directory and filename:
include/Pack/RPAudio/RPSndAudioMgr.h->#ifndef RP_AUDIO_SND_AUDIO_MGR_Hinclude/Pack/RPGraphics/RPGrpRenderer.h->#ifndef RP_GRAPHICS_RENDERER_Hinclude/Pack/RPUtility/RPUtlBaseFsm.h->#ifndef RP_UTILITY_BASE_FSM_H
Include-what-you-use. Unnecessary #include directives only increase the duration of compilation.
Please use the <> characters over "" when writing include paths, and use filepaths relative to include/ or lib/:
#include "include/Pack/RPGraphics.h" // ❌ Don't do this!!!
#include <include/Pack/RPGraphics.h> // ❌ Don't do this!!!
#include "Pack/RPGraphics.h" // ❌ Don't do this!!!
#include <Pack/RPGraphics.h> // ✅ Do this!!!If a forward declaration can resolve the dependency, favor that over #include'ing the corresponding file:
#include <Pack/RPGraphics.h> // ❌ Don't do this!!!
class RPGrpRenderer {
public:
void AppendDrawObject(IRPGrpDrawObject* pDrawObject);// Forward declarations
class IRPGrpDrawObject; // ✅ Do this!!!
class RPGrpRenderer {
public:
void AppendDrawObject(IRPGrpDrawObject* pDrawObject);The only exception is when dealing with symbols from other libraries:
#include <egg/core.h> // ✅ From different library, so #include is OK.
// Forward declarations
class IRPGrpDrawObject; // ✅ From same library, so forward declare it.
namespace EGG {
class Heap; // ❌ Don't do this!!! Heap is from the EGG library, not RP!
}
class RPGrpRenderer {
public:
static void SetEfbClearColor(const EGG::Color& rColor);
void AppendDrawObject(IRPGrpDrawObject* pDrawObject);If you do need to write forward declarations, please write a comment as follows:
// Forward declarations
class IRPGrpDrawObject;
class IRPGrpModelCallback;
class IRPGrpHostIOSocket;Many libraries/modules have a "public" or "external" header file which will, in-turn, #include all of its internal files.
For example, include/revolution/NAND.h is the public header file for the NAND library:
#ifndef RVL_SDK_PUBLIC_NAND_H
#define RVL_SDK_PUBLIC_NAND_H
#ifdef __cplusplus
extern "C" {
#endif
#include <revolution/NAND/NANDCheck.h>
#include <revolution/NAND/NANDCore.h>
#include <revolution/NAND/NANDOpenClose.h>
#include <revolution/NAND/nand.h>
#ifdef __cplusplus
}
#endif
#endifCaution
Using public headers is OK as long as:
- They are from another library
- You are writing the
#includedirective in an implementation file (*.c/*.cpp)
Otherwise, it is very easy to form a circular dependency. Here's an example!
egg/core/eggDvdFile.h:
#include <egg/core.h> // ❌ It will cause a circular dependency!
#include <egg/core/eggFile.h> // ✅ Include only what you use.
#include <revolution/DVD.h> // ✅ DVD is in another library (RVL SDK).egg/core/eggDvdFile.cpp:
#include <egg/core.h> // ✅ OK because this is the implementation file!
#include <egg/core/eggFile.h> // ✅ Also OK.
#include <revolution/DVD.h> // ✅ DVD is in another library (RVL SDK).All header files must at least include the types header file:
| Library | Types header |
|---|---|
| NW4R | <nw4r/types_nw4r.h> |
| EGG | <egg/types_egg.h> |
| RP | <Pack/types_pack.h> |
| All others | <types.h> |
Important
The only exceptions to the rules below are identifiers that must be named otherwise for the sake of decompilation accuracy.
This includes functions/data with known symbols, and any identifiers whose names are revealed by assertions and other strings in the binary.
Please write function names in PascalCase:
void myFunction(); // ❌ Don't do this!!!
void my_function(); // ❌ Don't do this!!!
void MyFunction(); // ✅ Do this!!!Please use the following prefixes for data members/function parameters:
| Prefix | Meaning |
|---|---|
m |
C++ class private member |
s |
C/C++ static, private member |
p |
C/C++ pointer-typed member or variable |
r |
C++ reference-typed member or variable |
If no prefix is required, write the name in camelCase.
These can be combined/stacked in the same order as the table; however, the member (m) and static (s) prefixes are mutually exclusive:
class X {
public:
int var; // ✅ Do this!!!
int mVar; // ❌ Don't do this!!! This is a public member!
private:
int id; // ❌ Don't do this!!! This is a private member!
int mID; // ✅ Do this!!!
int* mNum; // ❌ Don't do this!!! This is a pointer!
int* mpNum; // ✅ Do this!!!
int** mppNum; // ✅ Do this!!!
int& mHandle; // ❌ Don't do this!!! This is a reference!
int& mrHandle; // ✅ Do this!!!
static void* mpBuffer; // ❌ Don't do this!!! This is a static member!
static void* mspBuffer; // ❌ Don't do this!!! Don't combine 'm' and 's'!
static void* spBuffer; // ✅ Do this!!!
};
int MemCopy(void* buffer, u32 size); // ❌ Don't do this!!! 'buffer' is a pointer!
int MemCopy(void* pBuffer, u32 size); // ✅ Do this!!!
bool GetHandle(int& handle); // ❌ Don't do this!!! 'handle' is a reference!
bool GetHandle(int& rHandle); // ✅ Do this!!!Please write macros in uppercase SNAKE_CASE.
Please use the following order when writing C++ classes:
- Public members
- Protected members
- Private members
In each section (public/protected/private), use the following order of declarations:
- Type aliases (
friend,typedef,using) - Nested types
- Literal constants
Re-declare the access specifier at this point!
- Constructor(s) and assignment operator(s)
- Destructor
- Virtual functions
- All other functions
Re-declare the access specifier at this point!
- Constant, static members
- Non-constant, static members
- All other data members
class FruitBasket : public IBasket {
// 1. PUBLIC ACCESS
public:
// 1. Type aliases (`friend`, `typedef`, `using`)
friend class BasketMgr;
typedef TList<FruitBasket> FruitBasketList;
using IBasket::Foo;
// 2. Nested types
struct Fruit : public IObject {
String name;
Color color;
};
// 3. Literal constants
static const int FRUIT_MAX = 10;
// Re-declare access specifier
public:
// 4. Constructor(s) and assignment operator(s)
FruitBasket();
FruitBasket& operator=(const FruitBasket& rOther);
// 5. Destructor
virtual ~FruitBasket();
// 6. Virtual functions
virtual String GetName() {
return "FruitBasket";
}
// 7. All other functions
void AddFruit(const Fruit& rFruit) {
mFruits.PushBack(rFruit);
}
static void Initialize();
// 2. PROTECTED ACCESS
protected:
// 7. Non-static functions
const Fruit& GetFruit(int i) const {
return mFruits[i];
}
// 3. PRIVATE ACCESS
private:
// 7. Non-static functions
void Clear();
// 8. Constant, static members
static const IBasket::Profile PROFILE;
// 9. Non-constant, static members
static TList<FruitBasket> sInstances;
// 10. All other data members
TList<Fruit> mFruits;
};This repository uses Doxygen as the standard format for documentation.
- Use the
@prefix to issue Doxygen commands, rather than\. - Use
//!for single-line Doxygen comments, rather than///.
The only required Doxygen grouping is for modules that are not distinguished by namespaces. This is especially true for C code where there is no such construct as a "namespace".
For example, the Doxygen group hierarchy NW4R -> ut is represented by the contents of the namespace nw4r::ut.
For some libraries, this relation does not occur. EGG has many modules but only one namespace. To get around this, the contents of their header files are divided into Doxygen groups depending on the directory the file is in.
Below is the outline of egg/core/eggDvdFile.h. It exists in the core module of EGG, but must be explicitly placed into a Doxygen group because it only uses the root EGG namespace:
#ifndef EGG_CORE_DVD_FILE_H
#define EGG_CORE_DVD_FILE_H
#include <egg/types_egg.h>
namespace EGG {
//! @addtogroup egg_core
//! @{
// File contents . . .
//! @}
} // namespace EGG
#endifNote
Due to this repository being in the early stages, it is not important to include documentation in files outside of the Pack Project modules (the "game code").
To see a list of all Doxygen groups, please see include/__doxygen.hpp.
Comments do not have heavy restrictions, but they should follow a consistent format to generate readable Doxygen pages:
When documenting functions, write the @brief description such that it could be prefixed with "This function" and still make sense:
// ❌ [This function] **CLEAR** the memory buffer
/**
* @brief Clear the memory buffer
*/
void Clear();
// ✅ [This function] **CLEARS** the memory buffer
/**
* @brief Clears the memory buffer
*/
void Clear();If the function is a simple check that returns a boolean result, start the description with "Tests whether":
/**
* @brief Tests whether the buffer is empty
*/
bool Empty() const;Functions should document all parameters with the @param command:
/**
* @brief Advances this stream's position
*
* @param dir Seek direction
* @param offset Seek offset
*/
void Seek(ESeekDir dir, s32 offset);If the return value is not obvious, explain its purpose with the @return command:
Warning
It is easy to accidentally use @returns (with the "s") instead of @return.
Please use @return in your documentation.
/**
* @brief Writes data to this stream (internal implementation)
*
* @param pSrc Source buffer
* @param size Number of bytes to write
* @return Number of bytes written, or DVD error code
*/
s32 WriteImpl(const void* pSrc, u32 size);If a pointer/reference typed parameter is meant to be given an initial value through the function, mark it as an out parameter, like so:
/**
* @brief Gets the console's IP address
*
* @param rAddr[out] IPv4 address
*/
void GetHostAddr(SockAddr4& rAddr)Doxygen also provides the [in] tag to mark input parameters.
However, please only use this tag if the parameter is both an in and out parameter. This is common with "in-place" algorithms:
/**
* @brief Performs the Fisher-Yates shuffle algorithm to generate a random
* permutation of the specified values
*
* @param[in,out] rArray Input array (shuffled in-place)
* @param size Length of input array
*/
void Shuffle(int* pArray, size_t size) {Structures (struct/class) should at least provide a @brief description:
/**
* @brief Interface for binary file serialization/deserialization
*/
class IBinary {
public:Enumerations (enum) should at least provide a @brief description:
/**
* @brief Stream seek origin
*/
enum ESeekDir { ESeekDir_Begin, ESeekDir_Current, ESeekDir_End };Macros can be documented like functions, but also a single-line comment will suffice when there are no parameters, or their purpose is obvious:
/**
* @brief Helper for creating version numbers
*
* @param major Major version number
* @param minor Minor version number
*/
#define K_VERSION(major, minor) ((major << 8) | minor)
//! Helper for creating version numbers
#define K_VERSION(major, minor) ((major << 8) | minor)Data members should provide a single-line brief description. It does not matter whether this is a comment above or after the declaration:
//! Stream open flag
bool mIsOpen;
//! Position in data
u32 mPosition;
bool mIsOpen; //!< Stream open flag
u32 mPosition; //!< Position in dataNote
Please do not mix these types of comments in the same scope!
The brief description for constructors should be "Constructor", and the brief description for destructors should be "Destructor":
/**
* @brief Constructor
*/
IStream();
/**
* @brief Destructor
*/
virtual ~IStream();If there are many constructor overloads for purposes such as moving/copying, feel free to specify the constructor "type" in the Doxygen detailed description:
/**
* @brief Constructor
*/
Optional();
/**
* @brief Constructor
* @details Null constructor
*/
Optional(NullOpt);
/**
* @brief Constructor
* @details Copy constructor
*
* @param rOther Optional value
*/
Optional(const Optional& rOther);
/**
* @brief Constructor
* @details Copy constructor
*
* @param rValue Value
*/
Optional(const T& rValue);When writing statement-level comments, please try to make sure you are actually solving one of the following problems:
- The purpose (the "why") of the code statement is unclear:
void IScene::Configure() {
// . . . . .
// 2D view is required for drawing layouts (✅ Explains purpose!)
RPGrpRenderer::GetCurrent()->CreateView2D(1, pScreen);
RPGrpRenderer::GetCurrent()->CorrectView();
// Derived class behavior (✅ Explains purpose!)
OnConfigure();
}- The functionality (the "what") of the code statement is unclear:
int Decomp::decodeSZS(u8* pSrc, u8* pDst) {
// . . . . .
// Literal (chunk bit is set) (✅ Provides context!)
if (chunk & bit) {
pDst[dstIdx++] = pSrc[srcIdx++];
}
// Back-reference (chunk bit is not set) (✅ Provides context!)
else {
// Next bytes contain run offset, length (✅ Provides context!)
int packed = pSrc[srcIdx] << 8 | pSrc[srcIdx + 1];
srcIdx += 2;While the latter may traditionally be seen as a sign to refactor, we often do not have this freedom with matching decomp. In these cases, statement-level comments explaining what is happening is appreciated.
For simple code statements, or statements where the functionality is obvious, please do not restate the logic in comment form:
void DvdFile::initiate() {
// Initializes the mutexes (❌ This is a redundant explanation!!!)
OSInitMutex(&mSyncMutex);
OSInitMutex(&mAsyncMutex);
// Initializes the message queues (❌ This is a redundant explanation!!!)
OSInitMessageQueue(&mSyncQueue, mSyncBuffer, ARRAY_SIZE(mSyncBuffer));
OSInitMessageQueue(&mAsyncQueue, mAsyncBuffer, ARRAY_SIZE(mAsyncBuffer));
}In situations where you want to make a note of something for later, a "TODO" comment can be very useful.
Tip
We often use these types of comments for:
- Highlighting a "fakematch", or code that is matching but is either likely not accurate or is written in a non-portable manner
- Making a note of an unknown concept
- Making a note of changes to make during a future refactor
Please make sure you include your Git username as part of any TODO comments, so others who may seek further clarification will know who to contact:
// ✅ Provides context, and includes an author name!
// TODO(kiwi) Permuter fake(?)match
font_u8 = (u8*)font;
*widthOut = (font_u8 + font->widthTableOfs)[code];
// ❌ Provides no context or author name!
// TODO
void __OSGetDiscState(u8* out) {
u32 flags;To highlight a bug or oversight in the code, use the @bug Doxygen command:
template <u32 N> struct ResNameDataT {
u32 len; // at 0x0
// @bug 'N' already includes the null terminator
char str[ROUND_UP(N + 1, 4)]; // at 0x4
};// @bug Typo of 'i'
if (rSetting.mColorInput.mTevColor[1] ==
ColorInput::TEVCOLOR1_2) {if (chan < WPAD_CHAN0 || chan >= WPAD_MAX_CONTROLLERS) {
// @bug Will be interpreted as TRUE
return RFLErrcode_WrongParam;
}