2022-04-02 02:19:26 +02:00
|
|
|
#include "dynos.cpp.h"
|
|
|
|
extern "C" {
|
|
|
|
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
|
|
|
#include "stb/stb_image_write.h"
|
|
|
|
}
|
|
|
|
|
|
|
|
///////////
|
|
|
|
// Utils //
|
|
|
|
///////////
|
|
|
|
|
2022-05-07 07:03:12 +02:00
|
|
|
static bool FileTypeExists(SysPath& aFolder, const char* fileType) {
|
|
|
|
DIR *_Dir = opendir(aFolder.c_str());
|
|
|
|
if (!_Dir) { return false; }
|
|
|
|
|
|
|
|
int fileTypeLen = strlen(fileType);
|
|
|
|
|
|
|
|
struct dirent *_Ent = NULL;
|
|
|
|
while ((_Ent = readdir(_Dir)) != NULL) {
|
|
|
|
int nameLen = strlen(_Ent->d_name);
|
|
|
|
if (nameLen > fileTypeLen && !strcmp(&_Ent->d_name[nameLen - fileTypeLen], fileType)) {
|
|
|
|
closedir(_Dir);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
closedir(_Dir);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static TexData* LoadTextureFromFile(GfxData *aGfxData, const char* aFile) {
|
2022-04-02 02:19:26 +02:00
|
|
|
// Image file
|
2022-05-07 07:03:12 +02:00
|
|
|
SysPath _Filename;
|
|
|
|
int fileNameLen = strlen(aFile);
|
2022-05-08 01:39:19 +02:00
|
|
|
if (aGfxData->mPackFolder.length() == 0) {
|
|
|
|
_Filename = aFile;
|
|
|
|
} else if (fileNameLen > 4 && !strcmp(&aFile[fileNameLen - 4], ".png")) {
|
2022-05-07 07:03:12 +02:00
|
|
|
_Filename = fstring("%s/%s", aGfxData->mPackFolder.c_str(), aFile);
|
|
|
|
} else {
|
|
|
|
_Filename = fstring("%s/%s.png", aGfxData->mPackFolder.c_str(), aFile);
|
|
|
|
}
|
|
|
|
|
2022-04-02 02:19:26 +02:00
|
|
|
FILE *_File = fopen(_Filename.c_str(), "rb");
|
2022-05-07 07:03:12 +02:00
|
|
|
|
2022-04-12 06:24:35 +02:00
|
|
|
// Check as if we're an Actor.
|
2022-04-02 02:19:26 +02:00
|
|
|
if (!_File) {
|
2022-05-07 07:03:12 +02:00
|
|
|
SysPath _ActorFilename = "";
|
|
|
|
const char* _SubString = strchr(aFile, '/'); // Remove the "actors/"
|
|
|
|
if (_SubString && *_SubString) {
|
|
|
|
_SubString++;
|
|
|
|
_ActorFilename = fstring("%s/%s.png", aGfxData->mPackFolder.c_str(), _SubString);
|
|
|
|
_File = fopen(_ActorFilename.c_str(), "rb");
|
|
|
|
}
|
|
|
|
|
2022-04-12 06:24:35 +02:00
|
|
|
// The file does not exist in either spot!
|
|
|
|
if (!_File) {
|
2023-05-19 13:20:08 +02:00
|
|
|
PrintDataError(" ERROR: Unable to open file at \"%s\" or \"%s\"", _Filename.c_str(), _ActorFilename.c_str());
|
2022-04-12 06:24:35 +02:00
|
|
|
return NULL;
|
|
|
|
}
|
2022-04-02 02:19:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Texture data
|
|
|
|
fseek(_File, 0, SEEK_END);
|
|
|
|
TexData* _Texture = New<TexData>();
|
|
|
|
_Texture->mPngData.Resize(ftell(_File)); rewind(_File);
|
|
|
|
fread(_Texture->mPngData.begin(), sizeof(u8), _Texture->mPngData.Count(), _File);
|
|
|
|
fclose(_File);
|
|
|
|
return _Texture;
|
|
|
|
}
|
|
|
|
|
|
|
|
void DynOS_Tex_ConvertTextureDataToPng(GfxData *aGfxData, TexData* aTexture) {
|
|
|
|
|
|
|
|
// Convert to RGBA32
|
|
|
|
const u8 *_Palette = (aGfxData->mGfxContext.mCurrentPalette ? aGfxData->mGfxContext.mCurrentPalette->mData->mRawData.begin() : NULL);
|
2022-04-20 03:24:26 +02:00
|
|
|
u8 *_Buffer = DynOS_Tex_ConvertToRGBA32(aTexture->mRawData.begin(), aTexture->mRawData.Count(), aTexture->mRawFormat, aTexture->mRawSize, _Palette);
|
2022-04-02 02:19:26 +02:00
|
|
|
if (_Buffer == NULL) {
|
2023-05-19 13:20:08 +02:00
|
|
|
PrintDataError(" ERROR: Unknown texture format");
|
2022-04-02 02:19:26 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Convert to PNG
|
|
|
|
s32 _PngLength = 0;
|
|
|
|
u8 *_PngData = stbi_write_png_to_mem(_Buffer, 0, aTexture->mRawWidth, aTexture->mRawHeight, 4, &_PngLength);
|
|
|
|
if (!_PngData || !_PngLength) {
|
2023-05-19 13:20:08 +02:00
|
|
|
PrintDataError(" ERROR: Cannot convert texture to PNG");
|
2022-04-02 02:19:26 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
aTexture->mPngData = Array<u8>(_PngData, _PngData + _PngLength);
|
|
|
|
Delete(_PngData);
|
|
|
|
}
|
|
|
|
|
|
|
|
/////////////
|
|
|
|
// Parsing //
|
|
|
|
/////////////
|
|
|
|
|
|
|
|
DataNode<TexData>* DynOS_Tex_Parse(GfxData* aGfxData, DataNode<TexData>* aNode) {
|
|
|
|
if (aNode->mData) return aNode;
|
|
|
|
|
|
|
|
// Check tokens Count
|
|
|
|
if (aNode->mTokens.Count() < 1) {
|
2023-05-19 13:20:08 +02:00
|
|
|
PrintDataError(" ERROR: %s: not enough data", aNode->mName.begin());
|
2022-04-02 02:19:26 +02:00
|
|
|
return aNode;
|
|
|
|
}
|
|
|
|
|
|
|
|
// #include"[texture].inc.c"
|
|
|
|
s32 i0 = aNode->mTokens[0].Find("#include");
|
|
|
|
if (i0 != -1) {
|
|
|
|
s32 i1 = aNode->mTokens[0].Find(".inc.c");
|
|
|
|
if (i1 == -1) {
|
2023-06-12 11:08:20 +02:00
|
|
|
if (strstr(aNode->mName.begin(), "_pal_") == NULL) {
|
|
|
|
PrintDataError(" ERROR: %s: missing .inc.c in String %s", aNode->mName.begin(), aNode->mTokens[0].begin());
|
|
|
|
} else {
|
|
|
|
// hack for pal textures to be "found"
|
|
|
|
TexData* _Texture = New<TexData>();
|
|
|
|
aNode->mData = _Texture;
|
|
|
|
aNode->mLoadIndex = aGfxData->mLoadIndex++;
|
|
|
|
}
|
2022-04-02 02:19:26 +02:00
|
|
|
return aNode;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Filename
|
|
|
|
String _Filename = aNode->mTokens[0].SubString(i0 + 9, i1 - i0 - 9);
|
2022-05-07 07:03:12 +02:00
|
|
|
aNode->mData = LoadTextureFromFile(aGfxData, _Filename.begin());
|
2022-04-02 02:19:26 +02:00
|
|
|
aNode->mLoadIndex = aGfxData->mLoadIndex++;
|
|
|
|
return aNode;
|
|
|
|
}
|
|
|
|
|
|
|
|
// double quoted String
|
|
|
|
s32 dq0 = aNode->mTokens[0].Find('\"');
|
|
|
|
if (dq0 != -1) {
|
|
|
|
s32 dq1 = aNode->mTokens[0].Find('\"', dq0 + 1);
|
|
|
|
if (dq1 == -1) {
|
2023-05-19 13:20:08 +02:00
|
|
|
PrintDataError(" ERROR: %s: missing second quote in String %s", aNode->mName.begin(), aNode->mTokens[0].begin());
|
2022-04-02 02:19:26 +02:00
|
|
|
return aNode;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Filename
|
|
|
|
String _Filename = aNode->mTokens[0].SubString(dq0 + 1, dq1 - dq0 - 1);
|
2022-05-07 07:03:12 +02:00
|
|
|
aNode->mData = LoadTextureFromFile(aGfxData, _Filename.begin());
|
2022-04-02 02:19:26 +02:00
|
|
|
aNode->mLoadIndex = aGfxData->mLoadIndex++;
|
|
|
|
return aNode;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stream of bytes
|
|
|
|
aNode->mData = New<TexData>();
|
|
|
|
aNode->mData->mRawWidth = -1; // Unknown for now, will be set later
|
|
|
|
aNode->mData->mRawHeight = -1; // Unknown for now, will be set later
|
|
|
|
aNode->mData->mRawFormat = -1; // Unknown for now, will be set later
|
|
|
|
aNode->mData->mRawSize = -1; // Unknown for now, will be set later
|
|
|
|
aNode->mData->mRawData.Resize(aNode->mTokens.Count());
|
|
|
|
for (u64 j = 0; j != aNode->mTokens.Count(); ++j) {
|
|
|
|
aNode->mData->mRawData[j] = aNode->mTokens[j].ParseInt();
|
|
|
|
}
|
|
|
|
aNode->mLoadIndex = aGfxData->mLoadIndex++;
|
|
|
|
return aNode;
|
|
|
|
}
|
2022-04-02 04:50:42 +02:00
|
|
|
|
|
|
|
/////////////
|
|
|
|
// Writing //
|
|
|
|
/////////////
|
|
|
|
|
2022-06-25 09:52:53 +02:00
|
|
|
void DynOS_Tex_Write(BinFile* aFile, GfxData* aGfxData, DataNode<TexData> *aNode) {
|
2022-04-02 04:50:42 +02:00
|
|
|
if (!aNode->mData) return;
|
|
|
|
|
|
|
|
// Header
|
2022-06-25 09:52:53 +02:00
|
|
|
aFile->Write<u8>(DATA_TYPE_TEXTURE);
|
2022-04-02 04:50:42 +02:00
|
|
|
aNode->mName.Write(aFile);
|
|
|
|
|
|
|
|
// Data
|
Fixed various audio bugs; DynOS can now detect texture duplicates to decrease generated bin files size (#110)
Fixed the following audio bugs:
Bug: Rom-hacks sequences don't seem to be affected by volume scaling and
muting
Fix: Force the BGM sequences to follow the vanilla behavior:
Volume can't go higher than default volume
Volume is reduced to 31% when the game is paused
Audio is stopped when the game is paused outside the Castle levels
Bug: (Pointed out by Draco) Mario's voice clips are not replaced by the
player's character's in the following instances: fall to death
barrier, "here we go" in the ending cutscene, "let's a go"
after selecting a star, "okey dokey" after starting the game.
Fix: The first two ones now call
play_character_sound(m, CHAR_SOUND_...) instead of
play_sound(SOUND_MARIO_..., pos). The last two ones couldn't be
fixed the same way for two reasons: First, the corresponding sounds
were not referenced in the sound table, second, the sound played is
always cut-off after a few frames (due to how sm64 resets the sound
banks after loading a level).
Added SOUND_*_LETS_A_GO and SOUND_*_OKEY_DOKEY sounds for each playable
character as Bass samples.
Character Bass sounds work the same way as vanilla sounds (i.e. can be
played with play_character_sound), but they cannot be prematurely stopped
by sm64 sound banks shenanigans.
This fixes the cut-off for both the star select and the castle grounds
entry, plays the sound corresponding to the player's character, and doesn't
need to extend or edit the sound table.
DynOS can detect texture duplicates when generating a bin or lvl file.
When a duplicate is detected, the name of the original texture node is
written instead of the whole PNG data, decreasing significantly the
resulting file size.
2022-05-20 01:40:45 +02:00
|
|
|
// Look for texture duplicates
|
|
|
|
// If that's the case, store the name of the texture node instead of the whole PNG data
|
|
|
|
// (Don't bother to look for duplicates if there is no data to write)
|
|
|
|
if (!aNode->mData->mPngData.Empty()) {
|
|
|
|
for (const auto& _Node : aGfxData->mTextures) {
|
|
|
|
if (_Node->mLoadIndex < aNode->mLoadIndex && // Check load order: duplicates should reference only an already loaded node
|
|
|
|
_Node->mData != NULL && // Check node data
|
|
|
|
aNode->mData->mPngData.Count() == _Node->mData->mPngData.Count() && // Check PNG data lengths
|
|
|
|
memcmp(aNode->mData->mPngData.begin(), _Node->mData->mPngData.begin(), aNode->mData->mPngData.Count()) == 0) // Check PNG data content
|
|
|
|
{
|
2022-06-25 09:52:53 +02:00
|
|
|
aFile->Write<u32>(TEX_REF_CODE);
|
Fixed various audio bugs; DynOS can now detect texture duplicates to decrease generated bin files size (#110)
Fixed the following audio bugs:
Bug: Rom-hacks sequences don't seem to be affected by volume scaling and
muting
Fix: Force the BGM sequences to follow the vanilla behavior:
Volume can't go higher than default volume
Volume is reduced to 31% when the game is paused
Audio is stopped when the game is paused outside the Castle levels
Bug: (Pointed out by Draco) Mario's voice clips are not replaced by the
player's character's in the following instances: fall to death
barrier, "here we go" in the ending cutscene, "let's a go"
after selecting a star, "okey dokey" after starting the game.
Fix: The first two ones now call
play_character_sound(m, CHAR_SOUND_...) instead of
play_sound(SOUND_MARIO_..., pos). The last two ones couldn't be
fixed the same way for two reasons: First, the corresponding sounds
were not referenced in the sound table, second, the sound played is
always cut-off after a few frames (due to how sm64 resets the sound
banks after loading a level).
Added SOUND_*_LETS_A_GO and SOUND_*_OKEY_DOKEY sounds for each playable
character as Bass samples.
Character Bass sounds work the same way as vanilla sounds (i.e. can be
played with play_character_sound), but they cannot be prematurely stopped
by sm64 sound banks shenanigans.
This fixes the cut-off for both the star select and the castle grounds
entry, plays the sound corresponding to the player's character, and doesn't
need to extend or edit the sound table.
DynOS can detect texture duplicates when generating a bin or lvl file.
When a duplicate is detected, the name of the original texture node is
written instead of the whole PNG data, decreasing significantly the
resulting file size.
2022-05-20 01:40:45 +02:00
|
|
|
_Node->mName.Write(aFile);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-04-02 04:50:42 +02:00
|
|
|
aNode->mData->mPngData.Write(aFile);
|
|
|
|
}
|
|
|
|
|
2022-05-07 07:03:12 +02:00
|
|
|
static bool DynOS_Tex_WriteBinary(GfxData* aGfxData, const SysPath &aOutputFilename, String& aName, TexData* aTexData, bool aRawTexture) {
|
2022-06-25 09:52:53 +02:00
|
|
|
BinFile *_File = BinFile::OpenW(aOutputFilename.c_str());
|
2022-05-07 07:03:12 +02:00
|
|
|
if (!_File) {
|
2023-05-19 13:20:08 +02:00
|
|
|
PrintDataError(" ERROR: Unable to create file \"%s\"", aOutputFilename.c_str());
|
2022-05-07 07:03:12 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!aRawTexture) {
|
|
|
|
// Write png-texture
|
|
|
|
|
|
|
|
// Header
|
2022-06-25 09:52:53 +02:00
|
|
|
_File->Write<u8>(DATA_TYPE_TEXTURE);
|
2022-05-07 07:03:12 +02:00
|
|
|
aName.Write(_File);
|
|
|
|
|
|
|
|
// Data
|
|
|
|
aTexData->mPngData.Write(_File);
|
|
|
|
|
2022-06-25 09:52:53 +02:00
|
|
|
BinFile::Close(_File);
|
2022-05-07 07:03:12 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write raw-texture
|
|
|
|
|
|
|
|
// Header
|
2022-06-25 09:52:53 +02:00
|
|
|
_File->Write<u8>(DATA_TYPE_TEXTURE_RAW);
|
2022-05-07 07:03:12 +02:00
|
|
|
aName.Write(_File);
|
|
|
|
|
|
|
|
// load
|
|
|
|
u8 *_RawData = stbi_load_from_memory(aTexData->mPngData.begin(), aTexData->mPngData.Count(), &aTexData->mRawWidth, &aTexData->mRawHeight, NULL, 4);
|
|
|
|
aTexData->mRawFormat = G_IM_FMT_RGBA;
|
|
|
|
aTexData->mRawSize = G_IM_SIZ_32b;
|
|
|
|
aTexData->mRawData = Array<u8>(_RawData, _RawData + (aTexData->mRawWidth * aTexData->mRawHeight * 4));
|
|
|
|
free(_RawData);
|
|
|
|
|
|
|
|
// Data
|
2022-06-25 09:52:53 +02:00
|
|
|
_File->Write<s32>(aTexData->mRawFormat);
|
|
|
|
_File->Write<s32>(aTexData->mRawSize);
|
|
|
|
_File->Write<s32>(aTexData->mRawWidth);
|
|
|
|
_File->Write<s32>(aTexData->mRawHeight);
|
2022-05-07 07:03:12 +02:00
|
|
|
aTexData->mRawData.Write(_File);
|
|
|
|
|
2022-06-25 09:52:53 +02:00
|
|
|
BinFile::Close(_File);
|
2022-05-07 07:03:12 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-04-02 04:50:42 +02:00
|
|
|
/////////////
|
|
|
|
// Reading //
|
|
|
|
/////////////
|
|
|
|
|
2022-06-25 09:52:53 +02:00
|
|
|
DataNode<TexData>* DynOS_Tex_Load(BinFile *aFile, GfxData *aGfxData) {
|
2023-11-08 22:19:18 +01:00
|
|
|
if (!aFile || !aGfxData) { return NULL; }
|
|
|
|
|
2022-04-02 04:50:42 +02:00
|
|
|
DataNode<TexData> *_Node = New<DataNode<TexData>>();
|
|
|
|
|
|
|
|
// Name
|
|
|
|
_Node->mName.Read(aFile);
|
|
|
|
|
|
|
|
// Data
|
|
|
|
_Node->mData = New<TexData>();
|
|
|
|
_Node->mData->mUploaded = false;
|
Fixed various audio bugs; DynOS can now detect texture duplicates to decrease generated bin files size (#110)
Fixed the following audio bugs:
Bug: Rom-hacks sequences don't seem to be affected by volume scaling and
muting
Fix: Force the BGM sequences to follow the vanilla behavior:
Volume can't go higher than default volume
Volume is reduced to 31% when the game is paused
Audio is stopped when the game is paused outside the Castle levels
Bug: (Pointed out by Draco) Mario's voice clips are not replaced by the
player's character's in the following instances: fall to death
barrier, "here we go" in the ending cutscene, "let's a go"
after selecting a star, "okey dokey" after starting the game.
Fix: The first two ones now call
play_character_sound(m, CHAR_SOUND_...) instead of
play_sound(SOUND_MARIO_..., pos). The last two ones couldn't be
fixed the same way for two reasons: First, the corresponding sounds
were not referenced in the sound table, second, the sound played is
always cut-off after a few frames (due to how sm64 resets the sound
banks after loading a level).
Added SOUND_*_LETS_A_GO and SOUND_*_OKEY_DOKEY sounds for each playable
character as Bass samples.
Character Bass sounds work the same way as vanilla sounds (i.e. can be
played with play_character_sound), but they cannot be prematurely stopped
by sm64 sound banks shenanigans.
This fixes the cut-off for both the star select and the castle grounds
entry, plays the sound corresponding to the player's character, and doesn't
need to extend or edit the sound table.
DynOS can detect texture duplicates when generating a bin or lvl file.
When a duplicate is detected, the name of the original texture node is
written instead of the whole PNG data, decreasing significantly the
resulting file size.
2022-05-20 01:40:45 +02:00
|
|
|
|
|
|
|
// Check for the texture ref magic
|
2022-06-25 09:52:53 +02:00
|
|
|
s32 _FileOffset = aFile->Offset();
|
|
|
|
u32 _TexRefCode = aFile->Read<u32>();
|
Fixed various audio bugs; DynOS can now detect texture duplicates to decrease generated bin files size (#110)
Fixed the following audio bugs:
Bug: Rom-hacks sequences don't seem to be affected by volume scaling and
muting
Fix: Force the BGM sequences to follow the vanilla behavior:
Volume can't go higher than default volume
Volume is reduced to 31% when the game is paused
Audio is stopped when the game is paused outside the Castle levels
Bug: (Pointed out by Draco) Mario's voice clips are not replaced by the
player's character's in the following instances: fall to death
barrier, "here we go" in the ending cutscene, "let's a go"
after selecting a star, "okey dokey" after starting the game.
Fix: The first two ones now call
play_character_sound(m, CHAR_SOUND_...) instead of
play_sound(SOUND_MARIO_..., pos). The last two ones couldn't be
fixed the same way for two reasons: First, the corresponding sounds
were not referenced in the sound table, second, the sound played is
always cut-off after a few frames (due to how sm64 resets the sound
banks after loading a level).
Added SOUND_*_LETS_A_GO and SOUND_*_OKEY_DOKEY sounds for each playable
character as Bass samples.
Character Bass sounds work the same way as vanilla sounds (i.e. can be
played with play_character_sound), but they cannot be prematurely stopped
by sm64 sound banks shenanigans.
This fixes the cut-off for both the star select and the castle grounds
entry, plays the sound corresponding to the player's character, and doesn't
need to extend or edit the sound table.
DynOS can detect texture duplicates when generating a bin or lvl file.
When a duplicate is detected, the name of the original texture node is
written instead of the whole PNG data, decreasing significantly the
resulting file size.
2022-05-20 01:40:45 +02:00
|
|
|
if (_TexRefCode == TEX_REF_CODE) {
|
|
|
|
|
|
|
|
// That's a duplicate, find the original node and copy its content
|
|
|
|
String _NodeName; _NodeName.Read(aFile);
|
|
|
|
for (const auto& _LoadedNode : aGfxData->mTextures) {
|
|
|
|
if (_LoadedNode->mName == _NodeName) {
|
|
|
|
_Node->mData->mPngData = _LoadedNode->mData->mPngData;
|
|
|
|
_Node->mData->mRawData = _LoadedNode->mData->mRawData;
|
|
|
|
_Node->mData->mRawWidth = _LoadedNode->mData->mRawWidth;
|
|
|
|
_Node->mData->mRawHeight = _LoadedNode->mData->mRawHeight;
|
|
|
|
_Node->mData->mRawFormat = _LoadedNode->mData->mRawFormat;
|
|
|
|
_Node->mData->mRawSize = _LoadedNode->mData->mRawSize;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2022-06-25 09:52:53 +02:00
|
|
|
aFile->SetOffset(_FileOffset);
|
Fixed various audio bugs; DynOS can now detect texture duplicates to decrease generated bin files size (#110)
Fixed the following audio bugs:
Bug: Rom-hacks sequences don't seem to be affected by volume scaling and
muting
Fix: Force the BGM sequences to follow the vanilla behavior:
Volume can't go higher than default volume
Volume is reduced to 31% when the game is paused
Audio is stopped when the game is paused outside the Castle levels
Bug: (Pointed out by Draco) Mario's voice clips are not replaced by the
player's character's in the following instances: fall to death
barrier, "here we go" in the ending cutscene, "let's a go"
after selecting a star, "okey dokey" after starting the game.
Fix: The first two ones now call
play_character_sound(m, CHAR_SOUND_...) instead of
play_sound(SOUND_MARIO_..., pos). The last two ones couldn't be
fixed the same way for two reasons: First, the corresponding sounds
were not referenced in the sound table, second, the sound played is
always cut-off after a few frames (due to how sm64 resets the sound
banks after loading a level).
Added SOUND_*_LETS_A_GO and SOUND_*_OKEY_DOKEY sounds for each playable
character as Bass samples.
Character Bass sounds work the same way as vanilla sounds (i.e. can be
played with play_character_sound), but they cannot be prematurely stopped
by sm64 sound banks shenanigans.
This fixes the cut-off for both the star select and the castle grounds
entry, plays the sound corresponding to the player's character, and doesn't
need to extend or edit the sound table.
DynOS can detect texture duplicates when generating a bin or lvl file.
When a duplicate is detected, the name of the original texture node is
written instead of the whole PNG data, decreasing significantly the
resulting file size.
2022-05-20 01:40:45 +02:00
|
|
|
_Node->mData->mPngData.Read(aFile);
|
|
|
|
if (!_Node->mData->mPngData.Empty()) {
|
|
|
|
u8 *_RawData = stbi_load_from_memory(_Node->mData->mPngData.begin(), _Node->mData->mPngData.Count(), &_Node->mData->mRawWidth, &_Node->mData->mRawHeight, NULL, 4);
|
|
|
|
_Node->mData->mRawFormat = G_IM_FMT_RGBA;
|
|
|
|
_Node->mData->mRawSize = G_IM_SIZ_32b;
|
|
|
|
_Node->mData->mRawData = Array<u8>(_RawData, _RawData + (_Node->mData->mRawWidth * _Node->mData->mRawHeight * 4));
|
|
|
|
free(_RawData);
|
|
|
|
} else { // Probably a palette
|
|
|
|
_Node->mData->mRawData = Array<u8>();
|
|
|
|
_Node->mData->mRawWidth = 0;
|
|
|
|
_Node->mData->mRawHeight = 0;
|
|
|
|
_Node->mData->mRawFormat = 0;
|
|
|
|
_Node->mData->mRawSize = 0;
|
|
|
|
}
|
2022-04-02 04:50:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Append
|
2022-05-07 07:03:12 +02:00
|
|
|
if (aGfxData) {
|
|
|
|
aGfxData->mTextures.Add(_Node);
|
|
|
|
}
|
|
|
|
|
|
|
|
return _Node;
|
|
|
|
}
|
|
|
|
|
|
|
|
DataNode<TexData>* DynOS_Tex_LoadFromBinary(const SysPath &aPackFolder, const SysPath &aFilename, const char *aTexName, bool aAddToPack) {
|
|
|
|
// Look for pack in cache
|
|
|
|
PackData* _Pack = DynOS_Pack_GetFromPath(aPackFolder);
|
|
|
|
|
|
|
|
// Look for tex in pack
|
|
|
|
if (_Pack) {
|
|
|
|
auto _Tex = DynOS_Pack_GetTex(_Pack, aTexName);
|
|
|
|
if (_Tex != NULL) {
|
|
|
|
return _Tex;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Load data from binary file
|
|
|
|
DataNode<TexData>* _TexNode = NULL;
|
2022-06-25 09:52:53 +02:00
|
|
|
BinFile *_File = BinFile::OpenR(aFilename.c_str());
|
2022-05-07 07:03:12 +02:00
|
|
|
if (!_File) { return NULL; }
|
|
|
|
|
2022-06-25 09:52:53 +02:00
|
|
|
u8 type = _File->Read<u8>();
|
2022-05-07 07:03:12 +02:00
|
|
|
if (type == DATA_TYPE_TEXTURE) {
|
|
|
|
// load png-texture
|
|
|
|
_TexNode = New<DataNode<TexData>>();
|
|
|
|
_TexNode->mData = New<TexData>();
|
|
|
|
|
|
|
|
_TexNode->mName.Read(_File);
|
|
|
|
_TexNode->mData->mPngData.Read(_File);
|
2022-06-25 09:52:53 +02:00
|
|
|
BinFile::Close(_File);
|
2022-05-07 07:03:12 +02:00
|
|
|
|
|
|
|
if (aAddToPack) {
|
|
|
|
if (!_Pack) { _Pack = DynOS_Pack_Add(aPackFolder); }
|
|
|
|
DynOS_Pack_AddTex(_Pack, _TexNode);
|
|
|
|
}
|
|
|
|
|
|
|
|
return _TexNode;
|
|
|
|
} else if (type != DATA_TYPE_TEXTURE_RAW) {
|
2022-06-25 09:52:53 +02:00
|
|
|
BinFile::Close(_File);
|
2022-05-07 07:03:12 +02:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
// load raw-texture
|
|
|
|
_TexNode = New<DataNode<TexData>>();
|
|
|
|
_TexNode->mData = New<TexData>();
|
|
|
|
|
|
|
|
_TexNode->mName.Read(_File);
|
2022-06-25 09:52:53 +02:00
|
|
|
_TexNode->mData->mRawFormat = _File->Read<s32>();
|
|
|
|
_TexNode->mData->mRawSize = _File->Read<s32>();
|
|
|
|
_TexNode->mData->mRawWidth = _File->Read<s32>();
|
|
|
|
_TexNode->mData->mRawHeight = _File->Read<s32>();
|
2022-05-07 07:03:12 +02:00
|
|
|
_TexNode->mData->mRawData.Read(_File);
|
|
|
|
|
2022-06-25 09:52:53 +02:00
|
|
|
BinFile::Close(_File);
|
2022-05-07 07:03:12 +02:00
|
|
|
|
|
|
|
if (aAddToPack) {
|
|
|
|
if (!_Pack) { _Pack = DynOS_Pack_Add(aPackFolder); }
|
|
|
|
DynOS_Pack_AddTex(_Pack, _TexNode);
|
|
|
|
}
|
|
|
|
|
|
|
|
return _TexNode;
|
|
|
|
}
|
|
|
|
|
|
|
|
//////////////
|
|
|
|
// Generate //
|
|
|
|
//////////////
|
|
|
|
|
2022-06-02 08:04:21 +02:00
|
|
|
static bool is_level_number_png(SysPath& aPath) {
|
|
|
|
// normalize
|
|
|
|
String path = aPath.c_str();
|
|
|
|
char* p = path.begin();
|
|
|
|
while (*p != '\0') {
|
|
|
|
if (*p == '\\') {
|
|
|
|
*p = '/';
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
p = path.begin();
|
|
|
|
|
|
|
|
// compare 'levels/'
|
|
|
|
s16 levelsLength = strlen("levels/");
|
|
|
|
if (strncmp(p, "levels/", levelsLength)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// skip past level name
|
|
|
|
p += levelsLength;
|
|
|
|
while (*p != '\0') {
|
|
|
|
if (*p == '/') { break; }
|
|
|
|
p++;
|
|
|
|
}
|
|
|
|
if (*p != '/') { return false; }
|
|
|
|
p++;
|
|
|
|
|
|
|
|
return (*p >= '0' && *p <= '9');
|
|
|
|
}
|
|
|
|
|
2022-05-08 00:54:27 +02:00
|
|
|
static void DynOS_Tex_GeneratePack_Recursive(const SysPath &aPackFolder, SysPath &aOutputFolder, SysPath& aRelativePath, SysPath& aPrefix, GfxData *aGfxData, bool aAllowCustomTextures) {
|
|
|
|
SysPath _DirPath = fstring("%s/%s", aPackFolder.c_str(), aRelativePath.c_str());
|
|
|
|
|
2022-06-02 08:04:21 +02:00
|
|
|
// skip generation if any .c files exist, and it isn't levels/xxx/NUMBER
|
|
|
|
bool containsC = FileTypeExists(_DirPath, ".c");
|
2022-05-07 07:03:12 +02:00
|
|
|
|
|
|
|
DIR *_PackDir = opendir(_DirPath.c_str());
|
|
|
|
if (!_PackDir) { return; }
|
|
|
|
|
|
|
|
struct dirent *_PackEnt = NULL;
|
|
|
|
while ((_PackEnt = readdir(_PackDir)) != NULL) {
|
|
|
|
|
|
|
|
// Skip . and ..
|
|
|
|
if (SysPath(_PackEnt->d_name) == ".") continue;
|
|
|
|
if (SysPath(_PackEnt->d_name) == "..") continue;
|
|
|
|
|
2022-05-08 01:39:19 +02:00
|
|
|
SysPath _Path = fstring("%s%s", _DirPath.c_str(), _PackEnt->d_name);
|
2022-05-07 07:03:12 +02:00
|
|
|
|
|
|
|
// Recurse through subfolders
|
|
|
|
if (fs_sys_dir_exists(_Path.c_str())) {
|
|
|
|
SysPath _NextPath = fstring("%s%s/", aRelativePath.c_str(), _PackEnt->d_name);
|
|
|
|
SysPath _Prefix = fstring("%s.", _PackEnt->d_name);
|
2022-05-08 00:54:27 +02:00
|
|
|
DynOS_Tex_GeneratePack_Recursive(aPackFolder, aOutputFolder, _NextPath, _Prefix, aGfxData, aAllowCustomTextures);
|
2022-05-07 07:03:12 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2024-05-12 15:15:09 +02:00
|
|
|
|
2022-05-07 07:03:12 +02:00
|
|
|
size_t nameLen = strlen(_PackEnt->d_name);
|
|
|
|
if (nameLen < 4) continue;
|
2024-05-12 15:15:09 +02:00
|
|
|
|
|
|
|
// skip files that don't end in '.png'
|
2022-05-07 07:03:12 +02:00
|
|
|
if (strcmp(&_PackEnt->d_name[nameLen - 4], ".png")) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2024-05-12 15:15:09 +02:00
|
|
|
// skip files that have already been generated
|
2024-05-16 00:00:08 +02:00
|
|
|
char buffer[SYS_MAX_PATH];
|
|
|
|
snprintf(buffer, SYS_MAX_PATH, "%s.tex", _Path.substr(0, _Path.size() - 4).c_str());
|
2024-05-12 15:15:09 +02:00
|
|
|
if (fs_sys_file_exists(buffer)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2022-05-07 07:03:12 +02:00
|
|
|
// read the file
|
|
|
|
aGfxData->mModelIdentifier++;
|
|
|
|
TexData* _TexData = LoadTextureFromFile(aGfxData, _Path.c_str());
|
|
|
|
if (_TexData == NULL) {
|
2023-05-19 13:20:08 +02:00
|
|
|
PrintDataError("Error reading texture from file: %s", _Path.c_str());
|
2022-05-07 07:03:12 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
SysPath _RelativePath = fstring("%s%s", aRelativePath.c_str(), _PackEnt->d_name);
|
2022-06-02 08:04:21 +02:00
|
|
|
if (containsC && !is_level_number_png(_RelativePath)) {
|
2023-02-23 06:44:51 +01:00
|
|
|
// Don't forgot to free the texture data we've read.
|
|
|
|
Delete<TexData>(_TexData);
|
2022-06-02 08:04:21 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// write the file
|
2022-05-07 07:03:12 +02:00
|
|
|
String _BaseName;
|
|
|
|
const char* _OverrideName = DynOS_Builtin_Tex_GetNameFromFileName(_RelativePath.c_str());
|
|
|
|
if (_OverrideName) {
|
|
|
|
_BaseName = _OverrideName;
|
|
|
|
} else {
|
|
|
|
_BaseName = _PackEnt->d_name;
|
|
|
|
_BaseName = _BaseName.SubString(0, nameLen - 4);
|
|
|
|
}
|
2022-05-08 00:54:27 +02:00
|
|
|
|
|
|
|
// if we aren't overriding a texture, only generate textures in the output directory
|
|
|
|
SysPath _OutputFolder = fstring("%s/", aOutputFolder.c_str());
|
|
|
|
if (_OverrideName == NULL && (!aAllowCustomTextures || strcmp(_DirPath.c_str(), _OutputFolder.c_str()))) {
|
2023-02-23 06:44:51 +01:00
|
|
|
// Don't forgot to free the texture data we've read.
|
|
|
|
Delete<TexData>(_TexData);
|
2022-05-08 00:54:27 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2022-05-07 07:03:12 +02:00
|
|
|
SysPath _OutputPath = fstring("%s/%s.tex", aOutputFolder.c_str(), _BaseName.begin());
|
|
|
|
|
|
|
|
// create output dir if it doesn't exist
|
|
|
|
if (!fs_sys_dir_exists(aOutputFolder.c_str())) {
|
|
|
|
fs_sys_mkdir(aOutputFolder.c_str());
|
|
|
|
}
|
|
|
|
|
|
|
|
DynOS_Tex_WriteBinary(aGfxData, _OutputPath, _BaseName, _TexData, (_OverrideName != NULL));
|
2023-02-23 06:44:51 +01:00
|
|
|
|
|
|
|
// Don't forgot to free the texture data we've read.
|
|
|
|
Delete<TexData>(_TexData);
|
2022-05-07 07:03:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
closedir(_PackDir);
|
|
|
|
}
|
|
|
|
|
2022-05-08 00:54:27 +02:00
|
|
|
void DynOS_Tex_GeneratePack(const SysPath &aPackFolder, SysPath &aOutputFolder, bool aAllowCustomTextures) {
|
2024-04-19 19:32:39 +02:00
|
|
|
Print("Processing textures: \"%s\"", aPackFolder.c_str());
|
2022-05-07 07:03:12 +02:00
|
|
|
|
|
|
|
GfxData *_GfxData = New<GfxData>();
|
|
|
|
_GfxData->mModelIdentifier = 0;
|
|
|
|
SysPath _Empty = "";
|
2022-05-08 00:54:27 +02:00
|
|
|
DynOS_Tex_GeneratePack_Recursive(aPackFolder, aOutputFolder, _Empty, _Empty, _GfxData, aAllowCustomTextures);
|
2022-05-07 07:03:12 +02:00
|
|
|
DynOS_Gfx_Free(_GfxData);
|
|
|
|
}
|