Public V0.0.1

This commit is contained in:
tobid7 2025-05-04 13:32:07 +02:00
commit 996998785c
30 changed files with 4397 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
build/
.cache

6
.gitmodules vendored Normal file
View File

@ -0,0 +1,6 @@
[submodule "vendor/cli-fancy"]
path = vendor/cli-fancy
url = https://dev.npid7.de/tobid7/cli-fancy
[submodule "vendor/palladium"]
path = vendor/palladium
url = https://dev.npid7.de/tobid7/palladium.git

19
.vscode/c_cpp_properties.json vendored Normal file
View File

@ -0,0 +1,19 @@
{
"configurations": [
{
"name": "Win32",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [
"_DEBUG",
"UNICODE",
"_UNICODE"
],
"intelliSenseMode": "windows-gcc-x64",
"cppStandard": "c++20",
"cStandard": "c17"
}
],
"version": 4
}

91
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,91 @@
{
"editor.formatOnSave": true,
"files.associations": {
"xstring": "cpp",
"xiosbase": "cpp",
"iostream": "cpp",
"atomic": "cpp",
"bit": "cpp",
"cctype": "cpp",
"charconv": "cpp",
"clocale": "cpp",
"cmath": "cpp",
"codecvt": "cpp",
"compare": "cpp",
"concepts": "cpp",
"cstddef": "cpp",
"cstdint": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"exception": "cpp",
"format": "cpp",
"fstream": "cpp",
"initializer_list": "cpp",
"ios": "cpp",
"iosfwd": "cpp",
"istream": "cpp",
"iterator": "cpp",
"limits": "cpp",
"locale": "cpp",
"memory": "cpp",
"new": "cpp",
"ostream": "cpp",
"stdexcept": "cpp",
"streambuf": "cpp",
"string": "cpp",
"system_error": "cpp",
"tuple": "cpp",
"type_traits": "cpp",
"typeinfo": "cpp",
"utility": "cpp",
"vector": "cpp",
"xfacet": "cpp",
"xlocale": "cpp",
"xlocbuf": "cpp",
"xlocinfo": "cpp",
"xlocmes": "cpp",
"xlocmon": "cpp",
"xlocnum": "cpp",
"xloctime": "cpp",
"xmemory": "cpp",
"xtr1common": "cpp",
"xutility": "cpp",
"iomanip": "cpp",
"sstream": "cpp",
"*.tcc": "cpp",
"array": "cpp",
"cstdarg": "cpp",
"cwctype": "cpp",
"deque": "cpp",
"map": "cpp",
"unordered_map": "cpp",
"algorithm": "cpp",
"functional": "cpp",
"memory_resource": "cpp",
"numeric": "cpp",
"optional": "cpp",
"random": "cpp",
"string_view": "cpp",
"numbers": "cpp",
"span": "cpp",
"text_encoding": "cpp",
"cinttypes": "cpp",
"variant": "cpp",
"csignal": "cpp",
"chrono": "cpp",
"ratio": "cpp",
"any": "cpp",
"condition_variable": "cpp",
"forward_list": "cpp",
"mutex": "cpp",
"ranges": "cpp",
"semaphore": "cpp",
"stop_token": "cpp",
"thread": "cpp",
"valarray": "cpp"
},
"C_Cpp.default.compilerPath": "c:\\devkitPro\\msys2\\mingw64\\bin\\g++.exe"
}

46
CMakeLists.txt Normal file
View File

@ -0,0 +1,46 @@
cmake_minimum_required(VERSION 3.22)
project(ctrff)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED true)
option(CTRFF_DESKTOP OFF "Buid for Desktop Platform")
option(CTRFF_3DS OFF "Build lib for 3ds")
if(${CTRFF_3DS})
set(CTRFF_DESKTOP OFF CACHE BOOL)
set(CTRFF_BUILD_GUI OFF CACHE BOOL)
if(NOT DEFINED CMAKE_TOOLCHAIN_FILE)
if(DEFINED ENV{DEVKITPRO})
set(CMAKE_TOOLCHAIN_FILE "$ENV{DEVKITPRO}/cmake/3DS.cmake" CACHE PATH "toolchain file")
else()
message(FATAL_ERROR "Please define DEVKITPRO to point to your SDK path!")
endif()
endif()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wno-psabi -O3")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_C_FLAGS} -fno-rtti")
endif()
add_library(ctrff STATIC
source/helper.cpp
source/lz11.cpp
source/smdh.cpp
source/binutil.cpp
source/bcstm.cpp
source/bcwav.cpp
source/3dsx.cpp
)
target_include_directories(ctrff PUBLIC include vendor/palladium/include)
if(${CTRFF_DESKTOP})
add_executable(ctrff-cli tool/main.cpp)
target_include_directories(ctrff-cli PUBLIC include vendor/stb vendor/cli-fancy/include)
target_link_libraries(ctrff-cli PUBLIC ctrff)
add_executable(test tool/test.cpp)
target_include_directories(test PUBLIC include vendor/stb vendor/cli-fancy/include)
target_link_libraries(test PUBLIC ctrff)
endif()
install(TARGETS ctrff)
install(DIRECTORY include DESTINATION ".")

9
LICENSE Normal file
View File

@ -0,0 +1,9 @@
MIT License
Copyright (c) 2024 René Amthor (tobid7)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

46
README.md Normal file
View File

@ -0,0 +1,46 @@
# ctrff
Tool/Library to work with Nintendo 3ds File formats
## Note
**CTRFF Requires Palladium Headers from branch devel040 (as this is the branch the lib was latest tested with)**
## Building
- Desktop OS
```bash
# Note that -DCTRFF_DESKTOP is not required to build the lib for desktop
cmake -B build . -DCTRFF_DESKTOP=ON -DCMAKE_BUILD_TYPE=Release
cd build
make
```
- Nintendo 3ds
```bash
# Probably should go for Debug (to debug crashes)
# You could also do --toolchain path/to/debkitpro/cmake/3DS.cmake
# instead of -DCTRFF_3DS=ON
cmake -B build . -DCTRFF_3DS=ON -DCMAKE_BUILD_TYPE=Release
cd build
make
```
## File Formats
Not all Planned formates are listed here yet
| Format | State | Notes |
| ------ | ----- | ----- |
| 3dsx | Basic Loading and Viewing of Meta Data Smdh | |
| bcstm | Loading of almost every Data | Not capable of playing them yet (prefetch kernel panic) |
| bcwav | Basic Loading (not tested yet) | Not finished yet |
| bclim | Nothing done yet (Started creating header) | |
| lz11 | Encoder done, Decoder missing | Files are bit diffrent to the ones bannertool generates (don't know why) |
| romfs | Nothing Done yet (Started creating header) | |
| smdh | Almost done | missing safetey checks |
| cbmd | Nothing done yet | |
| cgfx | Nothing Done yet | |
| darc | Nothing done yet | |

178
docs/bcstm_doku.md Normal file
View File

@ -0,0 +1,178 @@
# BCSTM File Format
**Note that this Docs are based on Current State of development and are very unfinished**
## Contents
- 1 [Header](#header)
- 2 [Reference](#reference)
- 2.1 [Sized Reference](#sized-reference)
- 2.2 [Reference Table](#reference-table)
- 2.3 [Reference Types](#reference-types)
- 3 [Block Header](#block-header)
- 4 [Info Block](#info-block)
- 4.1 [Stream Info](#stream-info)
- 4.2 [Channel Info](#channel-info)
- 5 [DSP ADPCM Info](#dsp-adpcm-info)
- 5.1 [DSP ADPCM Param](#dsp-adpcm-param)
- 5.2 [DSP ADPCM Context](#dsp-adpcm-context)
- 6 [Basic Data Types](#basic-datatypes)
- 7 [Tools Devices used for Research](#tools--devices--file-sources-used-for-research)
## Header
| Offset | Size | Datatype | Description |
|---|---|---|---|
| 0x00 | 4 | [u32](#u32) | Magic **'CSTM'** `0x4D545343` |
| 0x04 | 2 | [u16](#u16) | Endianness `Big == 0xfffe` `Little == 0xfeff` |
| 0x06 | 2 | [u16](#u16) | Header Size `0x40` |
| 0x08 | 4 | [u32](#u32) | Version |
| 0x0c | 4 | [u32](#u32) | File Size |
| 0x10 | 2 | [u16](#u16) | Num Blocks (Should be 3) |
| 0x12 | 2 | [u16](#u16) | Reserved |
| 0x14 | 12 | [Sized Reference](#sized-reference) | Info Block Sized Reference |
| 0x20 | 12 | [Sized Reference](#sized-reference) | Seek Block Sized Reference |
| 0x2c | 12| [Sized Reference](#sized-reference) | Data Block Sized Reference |
## Reference
| Offset | Size | Datatype | Description |
|---|---|---|---|
| 0x00 | 2 | [Reference Type](#reference-types) | TypeID |
| 0x02 | 2 | [u16](u16) | Padding |
| 0x04 | 4 | [u32](u32) | Offset **(0xffffffff represents null)** |
### Sized Reference
| Offset | Size | Datatype | Description |
|---|---|---|---|
| 0x00 | 8 | [Reference](#reference) | Reference |
| 0x08 | 4 | [u32](u32) | Size |
### Reference Table
| Offset | Size | Datatype | Description |
|---|---|---|---|
| 0x00 | 4 | [u32](#u32) | Count |
| 0x04 | Count*8 | [Reference](#reference) | References |
### Reference Types
| ID | Type |
|---|---|
| 0x0100 | Byte Table |
| 0x0101 | [Reference Table](#reference-table) |
| 0x0300 | [DSP ADPCM Info](#dsp-adpcm-info) |
| 0x0301 | IMA ADPCM Info |
| 0x1f00 | Sample Data |
| 0x4000 | [Info Block](#info-block) |
| 0x4001 | Seek Block |
| 0x4002 | Data Block |
| 0x4100 | [Stream Info](#stream-info) |
| 0x4101 | Track Info |
| 0x4102 | [Channel Info](#channel-info) |
## Block Header
| Offset | Size | Datatype | Description |
|---|---|---|---|
| 0x00 | 4 | [u32](#u32) | Magic |
| 0x04 | 4 | [u32](#u32) | Size |
## Info Block
All Reference Offsets at the beginning of Info Block are Relative to the **Infoblock + 0x08 (size of Block Header) Position**
| Offset | Size | Datatype | Description |
|---|---|---|---|
| 0x00 | 8 | [Block Header](#block-header) | Block Header |
| 0x08 | 8 | [Reference](#reference) | Stream Info Reference |
| 0x10 | 8 | [Reference](#reference) | Track Info Reference Table Reference |
| 0x18 | 8 | [Reference](#reference) | Channel Info Reference Table Table Reference |
| 0x20 | 56 | [Stream Info](#stream-info) | Stream Info |
### Stream Info
| Offset | Size | Datatype | Description |
|---|---|---|---|
| 0x00 | 1 | [u8](#u8) | Encoding |
| 0x01 | 1 | [u8](#u8) | Loop `1 == true -- 0 == false` |
| 0x02 | 1 | [u8](#u8) | Num Channels |
| 0x03 | 1 | [u8](#u8) | Padding |
| 0x04 | 4 | [u32](#u32) | Sample Rate |
| 0x04 | 4 | [u32](#u32) | Loop Start |
| 0x04 | 4 | [u32](#u32) | Loop End |
| 0x04 | 4 | [u32](#u32) | Sample Blocks |
| 0x04 | 4 | [u32](#u32) | Sample Block Size |
| 0x04 | 4 | [u32](#u32) | Sample Block Samples |
| 0x04 | 4 | [u32](#u32) | Last Sample Block Size |
| 0x04 | 4 | [u32](#u32) | Last Sample Block Samples |
| 0x04 | 4 | [u32](#u32) | Last Sample Block Padded Size |
| 0x04 | 4 | [u32](#u32) | Seek Data Size |
| 0x04 | 4 | [u32](#u32) | Seek Interval Samples |
| 0x04 | 4 | [Reference](#reference) | Sample Data Reference |
### Channel Info
| Offset | Size | Datatype | Description |
|---|---|---|---|
| 0x00 | 8 | [Reference](#reference) | Reference to [DSP ADPCM Info](#dsp-adpcm-info) **!!! The Offsets are Relative to the start Pos of the Channel Info Object !!!** |
## DSP ADPCM Info
| Offset | Size | Datatype | Description |
|---|---|---|---|
| 0x00 | 32 | [Param](#dsp-adpcm-param) | Coefficients |
| 0x20 | 6 | [DSP Context](#dsp-adpcm-context) | Context |
| 0x26 | 6 | [DSP Context](#dsp-adpcm-context) | Loop Context |
| 0x2c | 2 | [u16](#u16) | Padding |
### DSP ADPCM Param
| Offset | Size | Datatype | Description |
|---|---|---|---|
| 0x00 | 32 | [u16](#u16) | 16 Bit Coefficients |
### DSP ADPCM Context
| Offset | Size | Datatype | Description |
|---|---|---|---|
| 0x00 | 1 | [u8](#u8) | 4Bit Predictor and 4Bit Scale |
| 0x01 | 1 | [u8](#u8) | Reserved |
| 0x02 | 2 | [16](#u16) | Previous Sample |
| 0x02 | 2 | [16](#u16) | Second Previous Sample |
## Basic Datatypes
### u32
```cpp
using u32 = unsigned int; // or uint32_t
```
### u16
```cpp
using u16 = unsigned short; // or uint16_t
```
### u8
```cpp
using u8 = unsigned char; // or uint8_t
```
## Tools / Devices / File SOurces used for research
| Name | Description |
|---|---|
| Visual Studio Code | Used for creating ctrff c++ code for bcstm |
| ImHex | Used to Analyze the Hex Code of the bcstm Files |
| Citra | Fast way to generate Log files when developing ctrff |
| ctrff-cli | Tool to generate Debug Output on Desktop OS like seen in BCSTM-Player File inspector |
| New 3ds XL | Testing on Real Hardware (BCSTM-Player) |
| Mario Kart 7 (Cartridge) | Used to get Test files |
| CTGP 7 | Used to get Test files |
| Super Mario Maker 3ds (Cartridge) | Used to get Test files |
| Mario and Luigi Bowsers inside story (Cartridge) | Used to get Test files |
| Donkey Kong Country Returns 3D | Used to get Test files |

178
docs/bcwav_doku.md Normal file
View File

@ -0,0 +1,178 @@
# BCWAV File Format
**Note that this Docs are based on Current State of development and are very unfinished**
**This file can be very incorect due to copying from bcstm_doku.md**
## Contents
- 1 [Header](#header)
- 2 [Reference](#reference)
- 2.1 [Sized Reference](#sized-reference)
- 2.2 [Reference Table](#reference-table)
- 2.3 [Reference Types](#reference-types)
- 3 [Block Header](#block-header)
- 4 [Info Block](#info-block)
- 4.1 [Stream Info](#stream-info)
- 4.2 [Channel Info](#channel-info)
- 5 [DSP ADPCM Info](#dsp-adpcm-info)
- 5.1 [DSP ADPCM Param](#dsp-adpcm-param)
- 5.2 [DSP ADPCM Context](#dsp-adpcm-context)
- 6 [Basic Data Types](#basic-datatypes)
- 7 [Tools Devices used for Research](#tools--devices--file-sources-used-for-research)
## Header
| Offset | Size | Datatype | Description |
|---|---|---|---|
| 0x00 | 4 | [u32](#u32) | Magic **'CWAV'** `0x56415743` |
| 0x04 | 2 | [u16](#u16) | Endianness `Big == 0xfffe` `Little == 0xfeff` |
| 0x06 | 2 | [u16](#u16) | Header Size `0x40` |
| 0x08 | 4 | [u32](#u32) | Version |
| 0x0c | 4 | [u32](#u32) | File Size |
| 0x10 | 2 | [u16](#u16) | Num Blocks (Should be 2) |
| 0x12 | 2 | [u16](#u16) | Reserved |
| 0x14 | 12 | [Sized Reference](#sized-reference) | Info Block Sized Reference |
| 0x20 | 12| [Sized Reference](#sized-reference) | Data Block Sized Reference |
## Reference
| Offset | Size | Datatype | Description |
|---|---|---|---|
| 0x00 | 2 | [Reference Type](#reference-types) | TypeID |
| 0x02 | 2 | [u16](u16) | Padding |
| 0x04 | 4 | [u32](u32) | Offset **(0xffffffff represents null)** |
### Sized Reference
| Offset | Size | Datatype | Description |
|---|---|---|---|
| 0x00 | 8 | [Reference](#reference) | Reference |
| 0x08 | 4 | [u32](u32) | Size |
### Reference Table
| Offset | Size | Datatype | Description |
|---|---|---|---|
| 0x00 | 4 | [u32](#u32) | Count |
| 0x04 | Count*8 | [Reference](#reference) | References |
### Reference Types
| ID | Type |
|---|---|
| 0x0300 | [DSP ADPCM Info](#dsp-adpcm-info) |
| 0x0301 | IMA ADPCM Info |
| 0x1f00 | Sample Data |
| 0x7000 | [Info Block](#info-block) |
| 0x7001 | Data Block |
| 0x7100 | [Channel Info](#channel-info) |
## Block Header
| Offset | Size | Datatype | Description |
|---|---|---|---|
| 0x00 | 4 | [u32](#u32) | Magic |
| 0x04 | 4 | [u32](#u32) | Size |
## Info Block
All Reference Offsets at the beginning of Info Block are Relative to the **Infoblock + 0x08 (size of Block Header) Position**
| Offset | Size | Datatype | Description |
|---|---|---|---|
| 0x00 | 8 | [Block Header](#block-header) | Block Header |
| 0x08 | 1 | [u8](#u8) | Encoding |
| 0x09 | 1 | [u8](#u8) | Loop `1 == true -- 0 == false` |
| 0x0a | 1 | [u16](#u16) | Padding |
| 0x0c | 4 | [u32](#u32) | Sample Rate |
| 0x10 | 4 | [u32](#u32) | Loop Start |
| 0x14 | 4 | [u32](#u32) | Loop End |
| 0x18 | 4 | [u32](#u32) | Reserved |
| 0x18 | 8 | [Reference Table](#reference-table) | Channel Info Reference Table |
### Stream Info
| Offset | Size | Datatype | Description |
|---|---|---|---|
| 0x00 | 1 | [u8](#u8) | Encoding |
| 0x01 | 1 | [u8](#u8) | Loop `1 == true -- 0 == false` |
| 0x02 | 1 | [u8](#u8) | Num Channels |
| 0x03 | 1 | [u8](#u8) | Padding |
| 0x04 | 4 | [u32](#u32) | Sample Rate |
| 0x04 | 4 | [u32](#u32) | Loop Start |
| 0x04 | 4 | [u32](#u32) | Loop End |
| 0x04 | 4 | [u32](#u32) | Sample Blocks |
| 0x04 | 4 | [u32](#u32) | Sample Block Size |
| 0x04 | 4 | [u32](#u32) | Sample Block Samples |
| 0x04 | 4 | [u32](#u32) | Last Sample Block Size |
| 0x04 | 4 | [u32](#u32) | Last Sample Block Samples |
| 0x04 | 4 | [u32](#u32) | Last Sample Block Padded Size |
| 0x04 | 4 | [u32](#u32) | Seek Data Size |
| 0x04 | 4 | [u32](#u32) | Seek Interval Samples |
| 0x04 | 4 | [Reference](#reference) | Sample Data Reference |
### Channel Info
| Offset | Size | Datatype | Description |
|---|---|---|---|
| 0x00 | 8 | [Reference](#reference) | Reference to [DSP ADPCM Info](#dsp-adpcm-info) **!!! The Offsets are Relative to the start Pos of the Channel Info Object !!!** |
## DSP ADPCM Info
| Offset | Size | Datatype | Description |
|---|---|---|---|
| 0x00 | 32 | [Param](#dsp-adpcm-param) | Coefficients |
| 0x20 | 6 | [DSP Context](#dsp-adpcm-context) | Context |
| 0x26 | 6 | [DSP Context](#dsp-adpcm-context) | Loop Context |
| 0x2c | 2 | [u16](#u16) | Padding |
### DSP ADPCM Param
| Offset | Size | Datatype | Description |
|---|---|---|---|
| 0x00 | 32 | [u16](#u16) | 16 Bit Coefficients |
### DSP ADPCM Context
| Offset | Size | Datatype | Description |
|---|---|---|---|
| 0x00 | 1 | [u8](#u8) | 4Bit Predictor and 4Bit Scale |
| 0x01 | 1 | [u8](#u8) | Reserved |
| 0x02 | 2 | [16](#u16) | Previous Sample |
| 0x02 | 2 | [16](#u16) | Second Previous Sample |
## Basic Datatypes
### u32
```cpp
using u32 = unsigned int; // or uint32_t
```
### u16
```cpp
using u16 = unsigned short; // or uint16_t
```
### u8
```cpp
using u8 = unsigned char; // or uint8_t
```
## Tools / Devices / File SOurces used for research
| Name | Description |
|---|---|
| Visual Studio Code | Used for creating ctrff c++ code for bcstm |
| ImHex | Used to Analyze the Hex Code of the bcstm Files |
| Citra | Fast way to generate Log files when developing ctrff |
| ctrff-cli | Tool to generate Debug Output on Desktop OS like seen in BCSTM-Player File inspector |
| New 3ds XL | Testing on Real Hardware (BCSTM-Player) |
| Mario Kart 7 (Cartridge) | Used to get Test files |
| CTGP 7 | Used to get Test files |
| Super Mario Maker 3ds (Cartridge) | Used to get Test files |
| Mario and Luigi Bowsers inside story (Cartridge) | Used to get Test files |
| Donkey Kong Country Returns 3D | Used to get Test files |

8
include/ctrff.hpp Normal file
View File

@ -0,0 +1,8 @@
#pragma once
#include <ctrff/3dsx.hpp>
#include <ctrff/bcstm.hpp>
#include <ctrff/bcwav.hpp>
#include <ctrff/binutil.hpp>
#include <ctrff/lz11.hpp>
#include <ctrff/smdh.hpp>

48
include/ctrff/3dsx.hpp Normal file
View File

@ -0,0 +1,48 @@
#pragma once
#include <ctrff/helper.hpp>
#include <ctrff/pd_p_api.hpp>
#include <ctrff/smdh.hpp>
#include <pd.hpp>
namespace ctrff {
class CTRFF_API _3dsx : public BinFile {
public:
_3dsx() {}
~_3dsx() {}
void Load(const std::string& path) {
std::fstream f(path, std::ios::in | std::ios::binary);
Read(f);
f.close();
}
bool HasMeta() { return SMDHSize == SMDH_Size; }
/** Write not supported btw */
void Write(std::fstream& f) const override;
void Read(std::fstream& f) override;
PD::u32 Magic;
PD::u16 HeaderSize;
PD::u16 RelocHeaderSize;
PD::u32 FormatVersion;
PD::u32 Flags;
// Sizes of the code, rodata and data segments +
// size of the BSS section (uninitialized latter half of the data segment)
PD::u32 CodeSegSize;
PD::u32 RodataSegSize;
PD::u32 DataSegSize;
PD::u32 BssSize;
/// Extended Header ///
// smdh offset
PD::u32 SMDHOff;
// smdh size
PD::u32 SMDHSize;
// fs offset
PD::u32 FsOff;
SMDH Meta;
};
/** Probably only germen people will understand */
using DreiDSX = _3dsx;
} // namespace ctrff

241
include/ctrff/bcstm.hpp Normal file
View File

@ -0,0 +1,241 @@
#pragma once
#include <ctrff/binutil.hpp>
#include <ctrff/pd_p_api.hpp>
#include <pd.hpp>
namespace ctrff {
class CTRFF_API BCSTM {
public:
BCSTM() : pReader(pFile) {}
~BCSTM() { CleanUp(); }
void LoadFile(const std::string& path);
void CleanUp();
void ReadGotoBeginning(bool use_loop_beg = false);
void ReadBlock(PD::u32 block, PD::u8* ref);
PD::u32 LeseZeiger() { return pFile.tellg(); }
/** Some useful Getters */
PD::u8 GetNumChannels() const { return pInfoBlock.StreamInfo.ChannelCount; }
PD::u32 GetSampleRate() const { return pInfoBlock.StreamInfo.SampleRate; }
PD::u32 GetBlockSize() const { return pInfoBlock.StreamInfo.SampleBlockSize; }
PD::u32 GetNumBlocks() const { return pInfoBlock.StreamInfo.SampleBlockNum; }
PD::u32 GetBlockSamples() const {
return pInfoBlock.StreamInfo.SampleBlockSampleNum;
}
PD::u32 GetLastBlockSamples() const {
return pInfoBlock.StreamInfo.LastSampleBlockSampleNum;
}
bool IsLooping() const { return pInfoBlock.StreamInfo.Loop; }
PD::u32 GetLoopStart() const {
return pInfoBlock.StreamInfo.LoopStartFrame / GetNumBlocks();
}
PD::u32 GetLoopEnd() const {
/** Get temp references for better readability */
const PD::u32& loop_end = pInfoBlock.StreamInfo.LoopEndFrame;
const PD::u32& block_samples = GetNumBlocks();
return (loop_end % block_samples ? block_samples
: loop_end / block_samples);
}
/** Internal Data (can be made private with private: but public by default) */
enum Endianness : PD::u16 {
Big = 0xfffe, ///< Big Endian
Little = 0xfeff, ///< Little Endian
};
enum ReferenceTypes : PD::u16 {
Ref_ByteTable = 0x0100,
Ref_ReferenceTable = 0x0101,
Ref_DSP_ADPCM_Info = 0x0300,
Ref_IMA_ADPCM_Info = 0x0301,
Ref_SampleData = 0x1f00,
Ref_InfoBlock = 0x4000,
Ref_SeekBlock = 0x4001,
Ref_DataBlock = 0x4002,
Ref_StreamInfo = 0x4100,
Ref_TrackInfo = 0x4101,
Ref_ChannelInfo = 0x4102,
};
enum Encoding : PD::u8 {
PCM8 = 0,
PCM16 = 1,
/** Only supported encoding in BCSTM-Player */
DSP_ADPCM = 2,
IMA_ADPCM = 3,
};
struct Reference {
PD::u16 TypeID;
PD::u16 Padding;
PD::u32 Offset; /** null -> uint32_max */
};
struct ReferenceTable {
PD::u32 Count;
PD::Vec<Reference> Refs;
};
struct SizedReference {
Reference Ref;
PD::u32 Size;
};
struct StreamInfo {
PD::u8 Encoding;
PD::u8 Loop;
PD::u8 ChannelCount;
PD::u8 Padding;
PD::u32 SampleRate;
PD::u32 LoopStartFrame;
PD::u32 LoopEndFrame;
PD::u32 SampleBlockNum;
PD::u32 SampleBlockSize;
PD::u32 SampleBlockSampleNum;
PD::u32 LastSampleBlockSize;
PD::u32 LastSampleBlockSampleNum;
PD::u32 LastSampleBlockPaddedSize;
PD::u32 SeekDataSize;
PD::u32 SeekIntervalSampleNum;
Reference SampleDataRef;
};
struct BlockHeader {
PD::u32 Magic;
PD::u32 Size;
};
struct InfoBlock {
BlockHeader Header;
Reference StreamInfoRef;
Reference TrackInfoTabRef;
Reference ChannelInfoTabRef;
BCSTM::StreamInfo StreamInfo;
ReferenceTable TrackInfoTab;
ReferenceTable ChannelInfoTab;
PD::Vec<Reference> ChannelInfoRefs; /** The refs of the refs ?? */
};
/** SeekDataBlock cause they are the same struct */
struct SD_Block {
BlockHeader Header;
PD::Vec<PD::u8> Data;
};
struct DSP_ADPCM_Param {
PD::u16 Coefficients[0x10];
};
struct DSP_ADPCM_Context {
PD::u8 PredictorScale;
PD::u8 Reserved;
PD::u16 PreviousSample;
PD::u16 SecondPreviousSample;
};
struct DSP_ADPCM_Info {
DSP_ADPCM_Param Param;
DSP_ADPCM_Context Context;
DSP_ADPCM_Context LoopContext;
PD::u16 Padding;
};
struct ByteTable {
PD::u32 Size;
PD::Vec<PD::u8> Table;
};
struct TrackInfo {
PD::u8 Volume;
PD::u8 Pan;
PD::u16 Padding;
Reference ChennelIndexTabRef;
ByteTable ChannelIndexTab;
};
struct Header {
PD::u32 Magic; /** CSTM */
PD::u16 Endianness = Little; /** Default */
PD::u16 HeaderSize; /** Header Size probably */
PD::u32 Version; /** Format Version? */
PD::u32 FileSize; /** File Size */
PD::u16 NumBlocks; /** Number of blocks */
PD::u16 Reserved; /** Reserved */
};
Header pHeader;
SizedReference pInfoBlockRef;
SizedReference pSeekBlockRef;
SizedReference pDataBlockRef;
InfoBlock pInfoBlock;
SD_Block pSeekBlock;
SD_Block pDataBlock;
PD::Vec<DSP_ADPCM_Info> pDSP_ADPCM_Info;
/** File Stream */
std::fstream pFile;
/** Endianness based reader */
BinUtil pReader;
void ReadReference(Reference& ref);
void ReadSizedReference(SizedReference& ref);
void ReadInfoBlock(InfoBlock& block);
void ReadSeekBlock(SD_Block& block);
void ReadReferenceTab(ReferenceTable& tab);
static std::string Endianness2String(const Endianness& e) {
switch (e) {
case Little:
return "Little";
case Big:
return "Big";
default:
return "Unknown";
}
}
static std::string ReferenceType2String(const ReferenceTypes& e) {
switch (e) {
case Ref_ByteTable:
return "ByteTable";
case Ref_ReferenceTable:
return "ReferenceTable";
case Ref_DSP_ADPCM_Info:
return "DSP_ADPCM_Info";
case Ref_IMA_ADPCM_Info:
return "IMA_ADPCM_Info";
case Ref_SampleData:
return "SampleData";
case Ref_InfoBlock:
return "InfoBlock";
case Ref_SeekBlock:
return "SeekBlock";
case Ref_DataBlock:
return "DataBlock";
case Ref_StreamInfo:
return "StreamInfo";
case Ref_TrackInfo:
return "TrackInfo";
case Ref_ChannelInfo:
return "ChannelInfo";
default:
return "Unknown";
}
}
static std::string Encoding2String(const Encoding& e) {
switch (e) {
case PCM8:
return "PCM8";
case PCM16:
return "PCM16";
case DSP_ADPCM:
return "DSP ADPCM";
case IMA_ADPCM:
return "IMA ADPCM";
default:
return "Unknown";
}
}
};
} // namespace ctrff

188
include/ctrff/bcwav.hpp Normal file
View File

@ -0,0 +1,188 @@
#pragma once
#include <ctrff/binutil.hpp>
#include <ctrff/pd_p_api.hpp>
#include <pd.hpp>
namespace ctrff {
class CTRFF_API BCWAV {
public:
BCWAV() : pReader(pFile) {}
~BCWAV() { CleanUp(); }
void LoadFile(const std::string& path);
void CleanUp();
void ReadGotoBeginning(bool use_loop_beg = false);
void ReadBlock(PD::u32 block, PD::u8* ref);
/** Internal Data (can be made private with private: but public by default) */
enum Endianness : PD::u16 {
Big = 0xfffe, ///< Big Endian
Little = 0xfeff, ///< Little Endian
};
enum ReferenceTypes : PD::u16 {
Ref_DSP_ADPCM_Info = 0x0300,
Ref_IMA_ADPCM_Info = 0x0301,
Ref_SampleData = 0x1f00,
Ref_InfoBlock = 0x7000,
Ref_DataBlock = 0x7001,
Ref_ChannelInfo = 0x7100,
};
enum Encoding : PD::u8 {
PCM8 = 0,
PCM16 = 1,
/** Only supported encoding in BCSTM-Player */
DSP_ADPCM = 2,
IMA_ADPCM = 3,
};
struct Reference {
PD::u16 TypeID;
PD::u16 Padding;
PD::u32 Offset; /** null -> uint32_max */
};
struct ReferenceTable {
PD::u32 Count;
PD::Vec<Reference> Refs;
};
struct SizedReference {
Reference Ref;
PD::u32 Size;
};
struct StreamInfo {
PD::u8 Encoding;
PD::u8 Loop;
PD::u8 ChannelCount;
PD::u8 Padding;
PD::u32 SampleRate;
PD::u32 LoopStartFrame;
PD::u32 LoopEndFrame;
PD::u32 SampleBlockNum;
PD::u32 SampleBlockSize;
PD::u32 SampleBlockSampleNum;
PD::u32 LastSampleBlockSize;
PD::u32 LastSampleBlockSampleNum;
PD::u32 LastSampleBlockPaddedSize;
PD::u32 SeekDataSize;
PD::u32 SeekIntervalSampleNum;
Reference SampleDataRef;
};
struct BlockHeader {
PD::u32 Magic;
PD::u32 Size;
};
struct InfoBlock {
BlockHeader Header;
PD::u8 Encoding;
PD::u8 Loop;
PD::u16 Padding;
PD::u32 SampleRate;
PD::u32 LoopStartFrame;
PD::u32 LoopEndFrame;
PD::u32 Reserved;
ReferenceTable ChannelInfoTab;
PD::Vec<Reference> ChannelInfoRefs; /** The refs of the refs ?? */
};
struct DataBlock {
BlockHeader Header;
PD::u32 Padding[3];
PD::Vec<PD::u8> Data;
};
struct DSP_ADPCM_Param {
PD::u16 Coefficients[0x10];
};
struct DSP_ADPCM_Context {
PD::u8 PredictorScale;
PD::u8 Reserved;
PD::u16 PreviousSample;
PD::u16 SecondPreviousSample;
};
struct DSP_ADPCM_Info {
DSP_ADPCM_Param Param;
DSP_ADPCM_Context Context;
DSP_ADPCM_Context LoopContext;
PD::u16 Padding;
};
struct Header {
PD::u32 Magic; /** CWAV */
PD::u16 Endianness = Little; /** Default */
PD::u16 HeaderSize; /** Header Size probably */
PD::u32 Version; /** Format Version? */
PD::u32 FileSize; /** File Size */
PD::u16 NumBlocks; /** Number of blocks */
PD::u16 Reserved; /** Reserved */
};
Header pHeader;
SizedReference pInfoBlockRef;
SizedReference pDataBlockRef;
InfoBlock pInfoBlock;
DataBlock pDataBlock;
PD::Vec<DSP_ADPCM_Info> pDSP_ADPCM_Info;
/** File Stream */
std::fstream pFile;
/** Endianness based reader */
BinUtil pReader;
void ReadReference(Reference& ref);
void ReadSizedReference(SizedReference& ref);
void ReadInfoBlock(InfoBlock& block);
void ReadReferenceTab(ReferenceTable& tab);
static std::string Endianness2String(const Endianness& e) {
switch (e) {
case Little:
return "Little";
case Big:
return "Big";
default:
return "Unknown";
}
}
static std::string ReferenceType2String(const ReferenceTypes& e) {
switch (e) {
case Ref_DSP_ADPCM_Info:
return "DSP_ADPCM_Info";
case Ref_IMA_ADPCM_Info:
return "IMA_ADPCM_Info";
case Ref_SampleData:
return "SampleData";
case Ref_InfoBlock:
return "InfoBlock";
case Ref_DataBlock:
return "DataBlock";
case Ref_ChannelInfo:
return "ChannelInfo";
default:
return "Unknown";
}
}
static std::string Encoding2String(const Encoding& e) {
switch (e) {
case PCM8:
return "PCM8";
case PCM16:
return "PCM16";
case DSP_ADPCM:
return "DSP ADPCM";
case IMA_ADPCM:
return "IMA ADPCM";
default:
return "Unknown";
}
}
};
} // namespace ctrff

43
include/ctrff/binutil.hpp Normal file
View File

@ -0,0 +1,43 @@
#pragma once
#include <ctrff/pd_p_api.hpp>
#include <pd.hpp>
namespace ctrff {
class BinFile {
public:
BinFile() = default;
~BinFile() = default;
virtual void Write(std::fstream& s) const = 0;
virtual void Read(std::fstream& s) = 0;
};
class CTRFF_API BinUtil {
public:
BinUtil(std::fstream& f, bool big = false) : m_file(f), m_big(big) {}
~BinUtil() = default;
void SetEndianess(bool big) { m_big = big; }
template <typename T>
void Read(T& v);
template <typename T>
void Write(const T& v);
/** Note that this func ignores Endianness */
template <typename T>
void ReadEx(T& v) {
static_assert(std::is_trivially_copyable_v<T>, "Cannot Read type T");
m_file.read(reinterpret_cast<char*>(&v), sizeof(T));
}
/** Note that this func ignores Endianness */
template <typename T>
void WriteEx(T& v) {
m_file.write(reinterpret_cast<const char*>(&v), sizeof(T));
}
private:
std::fstream& m_file;
bool m_big;
};
} // namespace ctrff

16
include/ctrff/helper.hpp Normal file
View File

@ -0,0 +1,16 @@
#pragma once
#include <ctrff/pd_p_api.hpp>
#include <pd.hpp>
namespace ctrff {
CTRFF_API void String2U16(PD::u16 *res, const std::string &src, size_t max);
CTRFF_API std::string U16toU8(PD::u16 *in, size_t max);
CTRFF_API void RGB565toRGBA(std::vector<PD::u8> &img, PD::u16 *icon,
const int &w, const int &h);
// Image can only be rgba8888
CTRFF_API void RGBA2RGB565(PD::u16 *out, const std::vector<PD::u8> &img,
const int &w, const int &h);
CTRFF_API std::vector<PD::u8> DownscaleImage(const std::vector<PD::u8> &img,
int w, int h, int scale);
} // namespace ctrff

10
include/ctrff/lz11.hpp Normal file
View File

@ -0,0 +1,10 @@
#pragma once
#include <ctrff/pd_p_api.hpp>
#include <pd.hpp>
namespace ctrff {
namespace LZ11 {
CTRFF_API std::vector<PD::u8> Compress(const std::vector<PD::u8>& in);
}
} // namespace ctrff

View File

@ -0,0 +1,50 @@
#pragma once
/*
MIT License
Copyright (c) 2024 - 2025 tobid7
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#ifdef _WIN32 // Windows (MSVC Tested)
#ifdef CTRFF_BUILD_SHARED
#define CTRFF_API __declspec(dllexport)
#else
#define CTRFF_API __declspec(dllimport)
#endif
#elif defined(__APPLE__) // macOS (untested yet)
#ifdef CTRFF_BUILD_SHARED
#define CTRFF_API __attribute__((visibility("default")))
#else
#define CTRFF_API
#endif
#elif defined(__linux__) // Linux (untested yet)
#ifdef CTRFF_BUILD_SHARED
#define CTRFF_API __attribute__((visibility("default")))
#else
#define CTRFF_API
#endif
#elif defined(__3DS__) // 3ds Specific
// Only Static supported
#define CTRFF_API
#else
#define CTRFF_API
#endif

143
include/ctrff/smdh.hpp Normal file
View File

@ -0,0 +1,143 @@
#pragma once
#include <ctrff/binutil.hpp>
#include <ctrff/helper.hpp>
#include <ctrff/pd_p_api.hpp>
#include <pd.hpp>
// Basic Info
// language_slots: 16
// valid_language_slots: 12
// rating_slots: 16
// small_icon: 24
// large_icon = 48
namespace ctrff {
// SMDH Size (Note that this needs to be declared here as
// a sizeof(SMDH) will not return the expected size due to
// use of Serializable)
constexpr PD::u32 SMDH_Size = 0x36C0;
struct CTRFF_API SMDH {
SMDH() {
std::fill_n(Magic, PD::ArraySize(Magic), 0);
std::fill_n(IconSmall, PD::ArraySize(IconSmall), 0);
std::fill_n(IconLarge, PD::ArraySize(IconLarge), 0);
}
~SMDH() = default;
static SMDH Default();
PD_SMART_CTOR(SMDH);
enum Language {
Language_Japanese,
Language_English,
Language_French,
Language_German,
Language_Italian,
Language_Spanish,
Language_Chinese_Simplified,
Language_Korean,
Language_Dutch,
Language_Portuguese,
Language_Russian,
Language_Chinese_Traditional,
// To Overrite Aall Languages
// returns japanese on get funcs
Language_All = 0x47,
};
enum Rating {
Rating_CERO = 0,
Rating_ESRB = 1,
Rating_USK = 3,
Rating_PEGI_GEN = 4,
Rating_PEGI_PTR = 6,
Rating_PEGI_BBFC = 7,
Rating_COB = 8,
Rating_GRB = 9,
Rating_CGSRR = 10,
};
enum Region {
Region_JAPAN = 1 << 0,
Region_NORTH_AMERICA = 1 << 1,
Region_EUROPE = 1 << 2,
Region_AUSTRALIA = 1 << 3,
Region_CHINA = 1 << 4,
Region_KOREA = 1 << 5,
Region_TAIWAN = 1 << 6,
// Not a bitmask, but a value.
Region_FREE = 0x7FFFFFFF,
};
enum Flag {
Flag_VISIBLE = 1 << 0,
Flag_AUTO_BOOT = 1 << 1,
Flag_ALLOW_3D = 1 << 2,
Flag_REQUIRE_EULA = 1 << 3,
Flag_AUTO_SAVE_ON_EXIT = 1 << 4,
Flag_USE_EXTENDED_BANNER = 1 << 5,
Flag_RATING_REQUIED = 1 << 6,
Flag_USE_SAVE_DATA = 1 << 7,
Flag_RECORD_USAGE = 1 << 8,
Flag_DISABLE_SAVE_BACKUPS = 1 << 10,
Flag_NEW_3DS = 1 << 12,
Flag_DEFAULT = Flag_VISIBLE | Flag_ALLOW_3D | Flag_RECORD_USAGE,
};
void Load(const std::string &path) {
std::fstream f(path, std::ios::in | std::ios::binary);
Read(f);
f.close();
}
void Save(const std::string &path) {
std::fstream f(path, std::ios::out | std::ios::binary);
Write(f);
f.close();
}
void Write(std::fstream &f) const;
void Read(std::fstream &f);
void SetIcon(const std::vector<PD::u8> &buf);
std::vector<PD::u8> GetIcon();
void SetShortTitle(const std::string &t, Language l = Language_All);
void SetLongTitle(const std::string &t, Language l = Language_All);
void SetAuthor(const std::string &t, Language l = Language_All);
std::string GetShortTitle(Language l = Language_All);
std::string GetLongTitle(Language l = Language_All);
std::string GetAuthor(Language l = Language_All);
struct CTRFF_API Title {
Title() {
std::fill_n(ShortTitle, PD::ArraySize(ShortTitle), 0);
std::fill_n(LongTitle, PD::ArraySize(LongTitle), 0);
std::fill_n(Author, PD::ArraySize(Author), 0);
};
PD::u16 ShortTitle[0x40];
PD::u16 LongTitle[0x80];
PD::u16 Author[0x40];
};
struct CTRFF_API Settings {
Settings() { std::fill_n(Ratings, PD::ArraySize(Ratings), 0); };
PD::u8 Ratings[16];
PD::u32 RegionLock = 0;
PD::u32 MatchmakerID = 0;
PD::u64 MatchmakerBitID = 0;
PD::u32 Flags = 0;
PD::u16 EulaVersion = 0;
PD::u16 Reserved = 0;
PD::u32 OptimalBannerFrame = 0;
PD::u32 StreetpassID = 0;
};
char Magic[4];
PD::u16 Version = 0;
PD::u16 Reserved = 0;
Title Titles[16];
Settings Settings;
PD::u64 Reserved1 = 0;
PD::u16 IconSmall[0x240]; // 24x24
PD::u16 IconLarge[0x900]; // 48x48
};
} // namespace ctrff

29
source/3dsx.cpp Normal file
View File

@ -0,0 +1,29 @@
#include <ctrff/3dsx.hpp>
#include <ctrff/binutil.hpp>
namespace ctrff {
CTRFF_API void _3dsx::Write(std::fstream& f) const {
// To be written
}
CTRFF_API void _3dsx::Read(std::fstream& f) {
BinUtil r(f);
r.ReadEx(Magic);
r.ReadEx(HeaderSize);
r.ReadEx(RelocHeaderSize);
r.ReadEx(FormatVersion);
r.ReadEx(Flags);
r.ReadEx(CodeSegSize);
r.ReadEx(RodataSegSize);
r.ReadEx(DataSegSize);
r.ReadEx(BssSize);
r.ReadEx(SMDHOff);
r.ReadEx(SMDHSize);
r.ReadEx(FsOff);
if (HasMeta()) {
f.seekg(SMDHOff, std::ios::beg);
Meta.Read(f);
}
}
} // namespace ctrff

189
source/bcstm.cpp Normal file
View File

@ -0,0 +1,189 @@
#include <ctrff/bcstm.hpp>
/** Using this a single time so inline it */
inline PD::u32 Swap32(PD::u32 in) {
return (in >> 24) | ((in >> 8) & 0x0000FF00) | ((in << 8) & 0x00FF0000) |
(in << 24);
}
namespace ctrff {
CTRFF_API void BCSTM::LoadFile(const std::string& path) {
CleanUp();
pFile.open(path, std::ios::in | std::ios::binary);
if (!pFile.is_open()) {
throw std::runtime_error("BCSTM Error: File not found!");
}
pReader.Read(pHeader.Magic);
pReader.Read(pHeader.Endianness);
if (pHeader.Endianness == Big) {
pHeader.Magic = Swap32(pHeader.Magic);
}
/** Check for 'CSTM' */
if (pHeader.Magic != 0x4D545343) {
throw std::runtime_error("BCSTM Error: Invalid File!");
}
pReader.Read(pHeader.HeaderSize);
pReader.Read(pHeader.Version);
pReader.Read(pHeader.FileSize);
pReader.Read(pHeader.NumBlocks);
pReader.Read(pHeader.Reserved);
for (PD::u16 i = 0; i < pHeader.NumBlocks; i++) {
SizedReference ref;
ReadSizedReference(ref);
if (ref.Ref.TypeID == Ref_InfoBlock) {
pInfoBlockRef = ref;
} else if (ref.Ref.TypeID == Ref_SeekBlock) {
pSeekBlockRef = ref;
} else if (ref.Ref.TypeID == Ref_DataBlock) {
pDataBlockRef = ref;
}
}
pFile.seekg(pInfoBlockRef.Ref.Offset);
ReadInfoBlock(pInfoBlock);
ReadGotoBeginning();
}
CTRFF_API void BCSTM::ReadReference(Reference& ref) {
pReader.Read(ref.TypeID);
pReader.Read(ref.Padding);
pReader.Read(ref.Offset);
}
CTRFF_API void BCSTM::ReadSizedReference(SizedReference& ref) {
ReadReference(ref.Ref);
pReader.Read(ref.Size);
}
CTRFF_API void BCSTM::ReadInfoBlock(InfoBlock& block) {
pReader.Read(block.Header.Magic);
pReader.Read(block.Header.Size);
ReadReference(block.StreamInfoRef);
ReadReference(block.TrackInfoTabRef);
ReadReference(block.ChannelInfoTabRef);
pReader.Read(block.StreamInfo.Encoding);
if (block.StreamInfo.Encoding != DSP_ADPCM) {
throw std::runtime_error("Only DSP ADPCM is supported yet!");
}
pReader.Read(block.StreamInfo.Loop);
pReader.Read(block.StreamInfo.ChannelCount);
pReader.Read(block.StreamInfo.Padding);
pReader.Read(block.StreamInfo.SampleRate);
pReader.Read(block.StreamInfo.LoopStartFrame);
pReader.Read(block.StreamInfo.LoopEndFrame);
pReader.Read(block.StreamInfo.SampleBlockNum);
pReader.Read(block.StreamInfo.SampleBlockSize);
pReader.Read(block.StreamInfo.SampleBlockSampleNum);
pReader.Read(block.StreamInfo.LastSampleBlockSize);
pReader.Read(block.StreamInfo.LastSampleBlockSampleNum);
pReader.Read(block.StreamInfo.LastSampleBlockPaddedSize);
pReader.Read(block.StreamInfo.SeekDataSize);
pReader.Read(block.StreamInfo.SeekIntervalSampleNum);
ReadReference(block.StreamInfo.SampleDataRef);
if (block.TrackInfoTabRef.Offset != 0xffffffff) {
pFile.seekg(pInfoBlockRef.Ref.Offset + sizeof(BlockHeader) +
block.TrackInfoTabRef.Offset,
std::ios::beg);
ReadReferenceTab(block.TrackInfoTab);
}
if (block.ChannelInfoTabRef.Offset != 0xffffffff) {
pFile.seekg(pInfoBlockRef.Ref.Offset + sizeof(BlockHeader) +
block.ChannelInfoTabRef.Offset,
std::ios::beg);
ReadReferenceTab(block.ChannelInfoTab);
}
for (auto& it : block.ChannelInfoTab.Refs) {
pFile.seekg(pInfoBlockRef.Ref.Offset + sizeof(BlockHeader) +
block.ChannelInfoTabRef.Offset + it.Offset,
std::ios::beg);
Reference r;
ReadReference(r);
block.ChannelInfoRefs.Add(r);
}
for (size_t i = 0; i < block.ChannelInfoRefs.Size(); i++) {
size_t off = pInfoBlockRef.Ref.Offset;
off += sizeof(BlockHeader);
off += block.ChannelInfoTabRef.Offset;
off += block.ChannelInfoTab.Refs[i].Offset;
off += block.ChannelInfoRefs[i].Offset;
pFile.seekg(off, std::ios::beg);
DSP_ADPCM_Info t; /** temp */
pReader.ReadEx(t); /** This Section gets read normally */
pDSP_ADPCM_Info.Add(t);
}
}
CTRFF_API void BCSTM::ReadSeekBlock(SD_Block& block) {
pReader.Read(pSeekBlock.Header.Magic);
pReader.Read(pSeekBlock.Header.Size);
if ((pSeekBlock.Header.Size % 20) != 0) {
throw std::runtime_error("BCSTM: SeekBlock Size is not 0x20 aligned!");
}
pSeekBlock.Data.Reserve(pSeekBlock.Header.Size + 1);
for (PD::u32 i = 0; i < pSeekBlock.Header.Size; i++) {
PD::u8 v;
pReader.Read(v);
pSeekBlock.Data.Add(v);
}
}
CTRFF_API void BCSTM::ReadReferenceTab(ReferenceTable& tab) {
pReader.Read(tab.Count);
tab.Refs.Reserve(tab.Count + 1);
for (PD::u32 i = 0; i < tab.Count; i++) {
Reference r;
pReader.Read(r.TypeID);
pReader.Read(r.Padding);
pReader.Read(r.Offset);
tab.Refs.Add(r);
}
}
CTRFF_API void BCSTM::ReadGotoBeginning(bool use_loop_beg) {
/**
* Go Up by 0x20 to skip header and empty section
* due to 0x20 alignment
*/
size_t off = pDataBlockRef.Ref.Offset + 0x20;
/** Shift to loop start if enabled */
if (use_loop_beg) {
off += GetNumBlocks() * GetNumChannels() * GetLoopStart();
// off += GetNumChannels() * pInfoBlock.StreamInfo.LoopStartFrame;
}
// block_size * channel_count * loop_start
try {
pFile.seekg(off, std::ios::beg);
} catch (const std::exception& e) {
throw std::runtime_error(e.what());
}
if (pFile.tellg() > pHeader.FileSize) {
throw std::runtime_error("BCSTM: Seeked Out of range!");
}
}
CTRFF_API void BCSTM::ReadBlock(PD::u32 block, PD::u8* ref) {
if (pFile.tellg() > pHeader.FileSize || block >= GetNumBlocks()) {
throw std::runtime_error("BCSTM: Decode block Out of range!");
}
pFile.read(
reinterpret_cast<char*>(ref),
(block == (GetNumBlocks() - 1) ? pInfoBlock.StreamInfo.LastSampleBlockSize
: GetBlockSize()));
}
CTRFF_API void BCSTM::CleanUp() {
if (pFile.is_open()) {
try {
pFile.close();
} catch (const std::exception& e) {
throw std::runtime_error(e.what());
}
}
pInfoBlock.ChannelInfoRefs.Clear();
pInfoBlock.ChannelInfoTab.Refs.Clear();
pInfoBlock.ChannelInfoTab.Count = 0;
pInfoBlock.TrackInfoTab.Refs.Clear();
pInfoBlock.TrackInfoTab.Count = 0;
pDSP_ADPCM_Info.Clear();
}
} // namespace ctrff

142
source/bcwav.cpp Normal file
View File

@ -0,0 +1,142 @@
#include <ctrff/bcwav.hpp>
/** Using this a single time so inline it */
inline PD::u32 Swap32(PD::u32 in) {
return (in >> 24) | ((in >> 8) & 0x0000FF00) | ((in << 8) & 0x00FF0000) |
(in << 24);
}
namespace ctrff {
CTRFF_API void BCWAV::LoadFile(const std::string& path) {
CleanUp();
pFile.open(path, std::ios::in | std::ios::binary);
if (!pFile.is_open()) {
throw std::runtime_error("BCWAV Error: File not found!");
}
pReader.Read(pHeader.Magic);
pReader.Read(pHeader.Endianness);
if (pHeader.Endianness == Big) {
pHeader.Magic = Swap32(pHeader.Magic);
}
/** Check for 'CWAV' */
if (pHeader.Magic != 0x56415743) {
throw std::runtime_error("BCWAV Error: Invalid File!");
}
pReader.Read(pHeader.HeaderSize);
pReader.Read(pHeader.Version);
pReader.Read(pHeader.FileSize);
pReader.Read(pHeader.NumBlocks);
pReader.Read(pHeader.Reserved);
for (PD::u16 i = 0; i < pHeader.NumBlocks; i++) {
SizedReference ref;
ReadSizedReference(ref);
if (ref.Ref.TypeID == Ref_InfoBlock) {
pInfoBlockRef = ref;
} else if (ref.Ref.TypeID == Ref_DataBlock) {
pDataBlockRef = ref;
}
}
pFile.seekg(pInfoBlockRef.Ref.Offset);
ReadInfoBlock(pInfoBlock);
}
CTRFF_API void BCWAV::ReadReference(Reference& ref) {
pReader.Read(ref.TypeID);
pReader.Read(ref.Padding);
pReader.Read(ref.Offset);
}
CTRFF_API void BCWAV::ReadSizedReference(SizedReference& ref) {
ReadReference(ref.Ref);
pReader.Read(ref.Size);
}
CTRFF_API void BCWAV::ReadInfoBlock(InfoBlock& block) {
pReader.Read(block.Header.Magic);
pReader.Read(block.Header.Size);
pReader.Read(block.Encoding);
pReader.Read(block.Loop);
pReader.Read(block.Padding);
pReader.Read(block.SampleRate);
pReader.Read(block.LoopStartFrame);
pReader.Read(block.LoopEndFrame);
pReader.Read(block.Reserved);
ReadReferenceTab(block.ChannelInfoTab);
for (auto& it : block.ChannelInfoTab.Refs) {
pFile.seekg(pInfoBlockRef.Ref.Offset + sizeof(BlockHeader) + it.Offset,
std::ios::beg);
Reference r;
ReadReference(r);
block.ChannelInfoRefs.Add(r);
}
for (size_t i = 0; i < block.ChannelInfoRefs.Size(); i++) {
size_t off = pInfoBlockRef.Ref.Offset;
off += sizeof(BlockHeader);
off += block.ChannelInfoTab.Refs[i].Offset;
off += block.ChannelInfoRefs[i].Offset;
pFile.seekg(off, std::ios::beg);
DSP_ADPCM_Info t; /** temp */
pReader.ReadEx(t); /** This Section gets read normally */
pDSP_ADPCM_Info.Add(t);
}
}
CTRFF_API void BCWAV::ReadReferenceTab(ReferenceTable& tab) {
pReader.Read(tab.Count);
tab.Refs.Reserve(tab.Count + 1);
for (PD::u32 i = 0; i < tab.Count; i++) {
Reference r;
pReader.Read(r.TypeID);
pReader.Read(r.Padding);
pReader.Read(r.Offset);
tab.Refs.Add(r);
}
}
CTRFF_API void BCWAV::ReadGotoBeginning(bool use_loop_beg) {
/**
* Go Up by 0x20 to skip header and empty section
* due to 0x20 alignment
*/
size_t off = pDataBlockRef.Ref.Offset + 0x20;
/** Shift to loop start if enabled */
if (use_loop_beg) {
// off += GetNumBlocks() * GetNumChannels() * GetLoopStart();
// off += GetNumChannels() * pInfoBlock.StreamInfo.LoopStartFrame;
}
// block_size * channel_count * loop_start
try {
pFile.seekg(off, std::ios::beg);
} catch (const std::exception& e) {
throw std::runtime_error(e.what());
}
if (pFile.tellg() > pHeader.FileSize) {
throw std::runtime_error("BCWAV: Seeked Out of range!");
}
}
CTRFF_API void BCWAV::ReadBlock(PD::u32 block, PD::u8* ref) {
// if (pFile.tellg() > pHeader.FileSize || block >= GetNumBlocks()) {
// throw std::runtime_error("BCWAV: Decode block Out of range!");
// }
// pFile.read(
// reinterpret_cast<char*>(ref),
// (block == (GetNumBlocks() - 1) ?
// pInfoBlock.StreamInfo.LastSampleBlockSize
// : GetBlockSize()));
}
CTRFF_API void BCWAV::CleanUp() {
if (pFile.is_open()) {
try {
pFile.close();
} catch (const std::exception& e) {
throw std::runtime_error(e.what());
}
}
pInfoBlock.ChannelInfoRefs.Clear();
pInfoBlock.ChannelInfoTab.Refs.Clear();
pInfoBlock.ChannelInfoTab.Count = 0;
pDSP_ADPCM_Info.Clear();
}
} // namespace ctrff

41
source/binutil.cpp Normal file
View File

@ -0,0 +1,41 @@
#include <ctrff/binutil.hpp>
namespace ctrff {
/** Supported reads */
template CTRFF_API void BinUtil::Read<PD::u8>(PD::u8&);
template CTRFF_API void BinUtil::Read<PD::u16>(PD::u16&);
template CTRFF_API void BinUtil::Read<PD::u32>(PD::u32&);
template CTRFF_API void BinUtil::Read<PD::u64>(PD::u64&);
/** Supported writes */
template CTRFF_API void BinUtil::Write<PD::u8>(const PD::u8&);
template CTRFF_API void BinUtil::Write<PD::u16>(const PD::u16&);
template CTRFF_API void BinUtil::Write<PD::u32>(const PD::u32&);
template CTRFF_API void BinUtil::Write<PD::u64>(const PD::u64&);
template <typename T>
void BinUtil::Read(T& v) {
// Check if Value could be Read
static_assert(std::is_integral<T>::value, "Cannot Read type T");
v = 0; // Set value to 0 (most cases a windows problem)
std::vector<PD::u8> buf(sizeof(T), 0); // declare buffer
// Read data into buffer
m_file.read(reinterpret_cast<char*>(buf.data()), sizeof(T));
// Loop or in be reverse loop and chift the values
for (int i = 0; i < sizeof(T); i++) {
v |= static_cast<T>(buf[m_big ? sizeof(T) - 1 - i : i]) << (8 * i);
}
}
template <typename T>
void BinUtil::Write(const T& v) {
// Check if Value could Write
static_assert(std::is_integral<T>::value, "Cannot Write type T");
std::vector<PD::u8> buf(sizeof(T), 0); // declare buffer
// Loop or in be reverse loop and write the values
for (size_t i = 0; i < sizeof(T); i++) {
buf[(m_big ? sizeof(T) - 1 - i : i)] = buf[m_big ? sizeof(T) - 1 - i : i] =
static_cast<PD::u8>((v >> (8 * i)) & 0xFF);
}
// Write buffer into file
m_file.write(reinterpret_cast<const char*>(buf.data()), sizeof(T));
}
} // namespace ctrff

159
source/helper.cpp Normal file
View File

@ -0,0 +1,159 @@
#include <ctrff/helper.hpp>
#include <cwchar>
#include <iostream>
void MakePixelRGBA(PD::u8 &r, PD::u8 &g, PD::u8 &b, PD::u8 &a, PD::u16 px) {
b = (px & 0x1f) << 3;
g = ((px >> 0x5) & 0x3f) << 2;
r = ((px >> 0xb) & 0x1f) << 3;
a = 0xff;
}
CTRFF_API PD::u16 MakePixel565(const PD::u8 &r, const PD::u8 &g,
const PD::u8 &b) {
PD::u16 res = 0;
res |= (r & ~0x7) << 8;
res |= (g & ~0x3) << 3;
res |= (b) >> 3;
return res;
}
CTRFF_API PD::u32 TileIndex(const int &x, const int &y, const int &w) {
return (((y >> 3) * (w >> 3) + (x >> 3)) << 6) +
((x & 1) | ((y & 1) << 1) | ((x & 2) << 1) | ((y & 2) << 2) |
((x & 4) << 2) | ((y & 4) << 3));
}
// TODO: Fix colors
CTRFF_API void ctrff::RGB565toRGBA(std::vector<PD::u8> &img, PD::u16 *icon,
const int &w, const int &h) {
if (img.size() != (48 * 48 * 4)) {
img.resize(48 * 48 * 4);
img.clear();
}
for (PD::u32 y = 0; y < h; y++) {
for (PD::u32 x = 0; x < w; x++) {
auto idx = TileIndex(x, y, w);
PD::u32 pos = (y * w + x) * 4;
MakePixelRGBA(img[pos + 0], img[pos + 1], img[pos + 2], img[pos + 3],
icon[idx]);
}
}
}
CTRFF_API void ctrff::RGBA2RGB565(PD::u16 *out, const std::vector<PD::u8> &img,
const int &w, const int &h) {
if (img.size() != size_t(w * h * 4)) return;
std::vector<PD::u8> px8 = img;
for (PD::u32 y = 0; y < h; y++) {
for (PD::u32 x = 0; x < w; x++) {
auto idx = TileIndex(x, y, w);
PD::u32 pos = (y * w + x) * 4;
out[idx] = MakePixel565(img[pos], img[pos + 1], img[pos + 2]);
}
}
}
CTRFF_API std::vector<PD::u8> ctrff::DownscaleImage(
const std::vector<PD::u8> &img, int w, int h, int scale) {
std::vector<PD::u8> res(((w / scale) * (h / scale)) * 4);
int samples = scale * scale;
for (int y = 0; y < h; y += scale) {
for (int x = 0; x < w; x += scale) {
PD::u32 r = 0;
PD::u32 g = 0;
PD::u32 b = 0;
PD::u32 a = 0;
for (int oy = 0; oy < scale; oy++) {
for (int ox = 0; ox < scale; ox++) {
int pos = ((y + oy) * w + (x + ox)) * 4;
r += img[pos++];
g += img[pos++];
b += img[pos++];
a += img[pos++];
}
}
int pos = ((y / scale) * (w / scale) + (x / scale)) * 4;
res[pos++] = (PD::u8)(r / samples);
res[pos++] = (PD::u8)(g / samples);
res[pos++] = (PD::u8)(b / samples);
res[pos++] = (PD::u8)(a / samples);
}
}
return res;
}
CTRFF_API void ctrff::String2U16(PD::u16 *res, const std::string &src,
size_t max) {
/// GOT FORCED TO REPLACE std::wstring_convert by some
/// manual work as it got removed in cxx20
/// TODO ///
/// ADD SOME ERROR API IN HERE
if (max == 0) return;
size_t len = 0;
size_t i = 0;
while (i < src.size() && len < max) {
PD::u8 c = src[i];
if (c < 0x80) {
// 1byte
res[len++] = c;
i++;
} else if ((c >> 5) == 0x6) {
// 2byte
if (i + 1 >= src.size())
throw std::invalid_argument("Invalid UTF-8 sequence");
res[len++] = ((c & 0x1F) << 6) | (src[i + 1] & 0x3F);
i += 2;
} else if ((c >> 4) == 0xE) {
// 3byte
if (i + 2 >= src.size())
throw std::invalid_argument("Invalid UTF-8 sequence");
res[len++] =
((c & 0x0F) << 12) | ((src[i + 1] & 0x3F) << 6) | (src[i + 2] & 0x3F);
i += 3;
} else if ((c >> 3) == 0x1E) {
// 4byte
if (i + 3 >= src.size())
throw std::invalid_argument("Invalid UTF-8 sequence");
PD::u32 codepoint = ((c & 0x07) << 18) | ((src[i + 1] & 0x3F) << 12) |
((src[i + 2] & 0x3F) << 6) | (src[i + 3] & 0x3F);
codepoint -= 0x10000;
res[len++] = 0xD800 | ((codepoint >> 10) & 0x3FF);
res[len++] = 0xDC00 | (codepoint & 0x3FF);
i += 4;
} else {
return;
}
}
}
CTRFF_API std::string ctrff::U16toU8(PD::u16 *in, size_t max) {
/// GOT FORCED TO REPLACE std::wstring_convert by some
/// manual work as it got removed in cxx20
if (!in || max == 0) {
return "";
}
std::string result;
result.reserve(max * 3);
for (size_t i = 0; i < max; i++) {
uint16_t c = in[i];
if (c < 0x80) {
result.push_back(static_cast<char>(c));
} else if (c < 0x800) {
result.push_back(static_cast<char>(0xC0 | (c >> 6)));
result.push_back(static_cast<char>(0x80 | (c & 0x3F)));
} else if (c < 0x10000) {
result.push_back(static_cast<char>(0xE0 | (c >> 12)));
result.push_back(static_cast<char>(0x80 | ((c >> 6) & 0x3F)));
result.push_back(static_cast<char>(0x80 | (c & 0x3F)));
} else {
continue;
}
}
return result;
}

112
source/lz11.cpp Normal file
View File

@ -0,0 +1,112 @@
#include <cstring>
#include <ctrff/lz11.hpp>
/// REWRITTEN CODE FROM BANNERTOOL !!!!!!
namespace ctrff {
namespace LZ11 {
PD::u32 GetOccurenceLength(const PD::u8* new_ptr, size_t new_len,
const PD::u8* old_ptr, size_t old_len,
PD::u32& disp) {
disp = 0;
if (new_len == 0) {
return 0;
}
PD::u32 res = 0;
if (old_len > 0) {
for (size_t i = 0; i < old_len - 1; i++) {
auto ref = old_ptr + i;
size_t len = 0;
for (size_t j = 0; j < new_len; j++) {
if (*(ref + j) != (*new_ptr + j)) {
break;
}
len++;
}
if (len > res) {
res = len;
disp = old_len - i;
if (res == new_len) {
break;
}
}
}
}
return res;
}
CTRFF_API std::vector<PD::u8> Compress(const std::vector<PD::u8>& in) {
if (in.size() > 0xFFFFFF) {
std::cout << "ERROR: LZ11 input is too large!" << std::endl;
return std::vector<PD::u8>();
}
std::stringstream s;
// SETUP HEADER //
s << static_cast<PD::u8>(0x11);
s << static_cast<PD::u8>(in.size() & 0xFF);
s << static_cast<PD::u8>((in.size() >> 8) & 0xFF);
s << static_cast<PD::u8>((in.size() >> 16) & 0xFF);
size_t res_len = 4; // 4-byte header
PD::u8 out_buf[0x21]; // 33 bytes
out_buf[0] = 0;
size_t obl = 1; // out_buf_len
size_t buf_blocks = 0;
size_t rb = 0;
while (rb < in.size()) {
if (buf_blocks == 8 || obl >= sizeof(out_buf) - 3) {
s.write(reinterpret_cast<const char*>(out_buf), obl);
res_len += obl;
out_buf[0] = 0;
obl = 1;
buf_blocks = 0;
}
PD::u32 disp = 0;
size_t old_len = std::min(rb, static_cast<size_t>(0x1000));
size_t len = LZ11::GetOccurenceLength(
in.data() + rb, std::min(in.size() - rb, static_cast<size_t>(0x10110)),
in.data() + rb - old_len, old_len, disp);
if (len < 3) {
out_buf[obl++] = in[rb++];
} else {
rb += len;
out_buf[0] |= static_cast<PD::u8>(1 << (7 - buf_blocks));
if (len > 0x110) {
out_buf[obl++] =
0x10 | static_cast<PD::u8>(((len - 0x111) >> 12) & 0x0F);
out_buf[obl++] = static_cast<PD::u8>(((len - 0x111) >> 4) & 0xFF);
out_buf[obl] = static_cast<PD::u8>(((len - 0x111) << 4) & 0xF0);
} else if (len > 0x10) {
out_buf[obl++] = 0x00 | static_cast<PD::u8>(((len - 0x11) >> 4) & 0x0F);
out_buf[obl] = static_cast<PD::u8>(((len - 0x11) << 4) & 0xF0);
} else {
out_buf[obl] |= static_cast<PD::u8>(((len - 1) << 4) & 0xF0);
}
obl++;
out_buf[obl++] = static_cast<PD::u8>(((disp - 1) >> 8) & 0x0F);
out_buf[obl++] = static_cast<PD::u8>((disp - 1) & 0xFF);
}
buf_blocks++;
}
if (buf_blocks > 0) {
s.write(reinterpret_cast<const char*>(out_buf), obl);
res_len += obl;
}
if (res_len % 4 != 0) {
PD::u32 pad_len = 4 - (res_len % 4);
PD::u8 pad[4] = {0};
s.write(reinterpret_cast<const char*>(pad), pad_len);
res_len += pad_len;
}
std::vector<PD::u8> res(res_len);
s.read(reinterpret_cast<char*>(res.data()), res.size());
return res;
}
} // namespace LZ11
} // namespace ctrff

127
source/smdh.cpp Normal file
View File

@ -0,0 +1,127 @@
#include <cstring>
#include <ctrff/smdh.hpp>
// magic
static const std::string smdh_magic = "SMDH";
CTRFF_API void ctrff::SMDH::Write(std::fstream &f) const {
f.write(reinterpret_cast<const char *>(Magic), sizeof(Magic));
f.write(reinterpret_cast<const char *>(&Version), sizeof(Version));
f.write(reinterpret_cast<const char *>(&Reserved), sizeof(Reserved));
for (size_t i = 0; i < 16; i++) {
f.write(reinterpret_cast<const char *>(&Titles[i]), sizeof(Title));
}
f.write(reinterpret_cast<const char *>(&Settings), sizeof(Settings));
f.write(reinterpret_cast<const char *>(&Reserved1), sizeof(Reserved1));
f.write(reinterpret_cast<const char *>(&IconSmall), sizeof(IconSmall));
f.write(reinterpret_cast<const char *>(&IconLarge), sizeof(IconLarge));
}
CTRFF_API void ctrff::SMDH::Read(std::fstream &f) {
f.seekg(0, std::ios::end);
if (f.tellg() != SMDH_Size) {
throw std::runtime_error(
"SMDH: File size does not match the SMDH Header size!");
}
f.seekg(0, std::ios::beg);
f.read(reinterpret_cast<char *>(Magic), sizeof(Magic));
if (std::string(Magic) != smdh_magic) {
throw std::runtime_error("SMDH: Invalid SMDH file!");
}
f.read(reinterpret_cast<char *>(&Version), sizeof(Version));
f.read(reinterpret_cast<char *>(&Reserved), sizeof(Reserved));
for (size_t i = 0; i < 16; i++) {
f.read(reinterpret_cast<char *>(&Titles[i]), sizeof(Title));
}
f.read(reinterpret_cast<char *>(&Settings), sizeof(Settings));
f.read(reinterpret_cast<char *>(&Reserved1), sizeof(Reserved1));
f.read(reinterpret_cast<char *>(&IconSmall), sizeof(IconSmall));
f.read(reinterpret_cast<char *>(&IconLarge), sizeof(IconLarge));
}
CTRFF_API void ctrff::SMDH::SetIcon(const std::vector<PD::u8> &buf) {
RGBA2RGB565(IconLarge, buf, 48, 48);
auto small_icon = DownscaleImage(buf, 48, 48, 2);
RGBA2RGB565(IconSmall, small_icon, 24, 24);
}
CTRFF_API std::vector<PD::u8> ctrff::SMDH::GetIcon() {
std::vector<PD::u8> res(48 * 48 * 4);
ctrff::RGB565toRGBA(res, IconLarge, 48, 48);
return res;
}
CTRFF_API void ctrff::SMDH::SetShortTitle(const std::string &t, Language l) {
if (l == Language_All) {
for (int i = 0; i < 16; i++) {
ctrff::String2U16(Titles[i].ShortTitle, t, 0x40);
}
return;
}
ctrff::String2U16(Titles[l].ShortTitle, t, 0x40);
}
CTRFF_API void ctrff::SMDH::SetLongTitle(const std::string &t, Language l) {
if (l == Language_All) {
for (int i = 0; i < 16; i++) {
ctrff::String2U16(Titles[i].LongTitle, t, 0x80);
}
return;
}
ctrff::String2U16(Titles[l].LongTitle, t, 0x80);
}
CTRFF_API void ctrff::SMDH::SetAuthor(const std::string &t, Language l) {
if (l == Language_All) {
for (int i = 0; i < 16; i++) {
ctrff::String2U16(Titles[i].Author, t, 0x40);
}
return;
}
ctrff::String2U16(Titles[l].Author, t, 0x40);
}
CTRFF_API std::string ctrff::SMDH::GetShortTitle(Language l) {
if (l == Language_All) {
return ctrff::U16toU8(Titles[0].ShortTitle, 0x40);
}
return ctrff::U16toU8(Titles[l].ShortTitle, 0x40);
}
CTRFF_API std::string ctrff::SMDH::GetLongTitle(Language l) {
if (l == Language_All) {
return ctrff::U16toU8(Titles[0].LongTitle, 0x80);
}
return ctrff::U16toU8(Titles[l].LongTitle, 0x80);
}
CTRFF_API std::string ctrff::SMDH::GetAuthor(Language l) {
if (l == Language_All) {
return ctrff::U16toU8(Titles[0].Author, 0x40);
}
return ctrff::U16toU8(Titles[l].Author, 0x40);
}
CTRFF_API ctrff::SMDH ctrff::SMDH::Default() {
SMDH n3w; /** new */
for (int i = 0; i < 4; i++) {
n3w.Magic[i] = smdh_magic[i];
}
// Set Defaults
n3w.Settings.MatchmakerID = 0;
n3w.Settings.MatchmakerBitID = 0;
n3w.Settings.EulaVersion = 0;
n3w.Settings.OptimalBannerFrame = 0;
n3w.Settings.StreetpassID = 0;
for (size_t i = 0; i < 16; i++) {
n3w.Settings.Ratings[i] = 0;
}
n3w.Version = 0;
n3w.Reserved = 0;
n3w.Reserved1 = 0;
n3w.Settings.Flags = Flag_DEFAULT;
n3w.Settings.RegionLock = Region_FREE;
std::fill_n(n3w.IconSmall, 0x240, 0);
std::fill_n(n3w.IconLarge, 0x900, 0);
return n3w;
}

502
tool/main.cpp Normal file
View File

@ -0,0 +1,502 @@
#include <cli-fancy.hpp>
#include <cmath>
#include <ctrff.hpp>
#include <format>
#include <fstream>
#include <iomanip>
#include <map>
#include <string>
#include <utility>
/** Import palladium stb image */
#define STB_IMAGE_IMPLEMENTATION
#include <pd/external/stb_image.h>
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <stb_image_write.h>
/** Could not use the palladium func due to only using headers */
const std::string FormatBytes(unsigned long long bytes) {
if (bytes == 1) {
// Only one Byte
return std::format("{} Byte", bytes);
} else if (bytes < 1024) {
// less than one kilobyte
return std::format("{} Bytes", bytes);
} else if (bytes < 1048576) {
// less than one megabyte
return std::format("{:.1f} KB", (float)bytes / 1024.f);
} else if (bytes < 1073741824) {
// less than one gigabyte
return std::format("{:.1f} MB", (float)bytes / 1048576.f);
} else {
// gigabyte
return std::format("{:.1f} GB", (float)bytes / 1073741824.f);
}
}
const std::map<ctrff::SMDH::Language, std::string> smdh_lang_table = {
{ctrff::SMDH::Language_Japanese, "Japanese"},
{ctrff::SMDH::Language_English, "English"},
{ctrff::SMDH::Language_French, "French"},
{ctrff::SMDH::Language_German, "German"},
{ctrff::SMDH::Language_Italian, "Italian"},
{ctrff::SMDH::Language_Spanish, "Spanish"},
{ctrff::SMDH::Language_Chinese_Simplified, "Chinese Simplified"},
{ctrff::SMDH::Language_Korean, "Korean"},
{ctrff::SMDH::Language_Dutch, "Dutch"},
{ctrff::SMDH::Language_Portuguese, "Portuguese"},
{ctrff::SMDH::Language_Russian, "Russian"},
{ctrff::SMDH::Language_Chinese_Traditional, "Chinese Traditional"},
};
const std::map<ctrff::SMDH::Rating, std::string> smdh_rating_table = {
{ctrff::SMDH::Rating_CERO, "CERO"},
{ctrff::SMDH::Rating_ESRB, "ESRB"},
{ctrff::SMDH::Rating_USK, "USK"},
{ctrff::SMDH::Rating_PEGI_GEN, "PEGI_GEN"},
{ctrff::SMDH::Rating_PEGI_PTR, "PEGI_PTR"},
{ctrff::SMDH::Rating_PEGI_BBFC, "PEGI_BBFC"},
{ctrff::SMDH::Rating_COB, "COB"},
{ctrff::SMDH::Rating_GRB, "GRB"},
{ctrff::SMDH::Rating_CGSRR, "CGSRR"},
};
const std::map<ctrff::SMDH::Region, std::string> smdh_region_table = {
{ctrff::SMDH::Region_JAPAN, "Japan"},
{ctrff::SMDH::Region_NORTH_AMERICA, "North America"},
{ctrff::SMDH::Region_EUROPE, "Europe"},
{ctrff::SMDH::Region_AUSTRALIA, "Australia"},
{ctrff::SMDH::Region_CHINA, "China"},
{ctrff::SMDH::Region_KOREA, "Korea"},
{ctrff::SMDH::Region_TAIWAN, "Taiwan"},
{ctrff::SMDH::Region_FREE, "Free"},
};
const std::map<ctrff::SMDH::Flag, std::string> smdh_flag_table{
{ctrff::SMDH::Flag_VISIBLE, "Visible"},
{ctrff::SMDH::Flag_AUTO_BOOT, "Auto Boot"},
{ctrff::SMDH::Flag_ALLOW_3D, "Allow 3D"},
{ctrff::SMDH::Flag_REQUIRE_EULA, "Require EULA"},
{ctrff::SMDH::Flag_AUTO_SAVE_ON_EXIT, "Auto Save on Exit"},
{ctrff::SMDH::Flag_USE_EXTENDED_BANNER, "Use Extended Banner"},
{ctrff::SMDH::Flag_RATING_REQUIED, "Rating required"},
{ctrff::SMDH::Flag_USE_SAVE_DATA, "Use Save Data"},
{ctrff::SMDH::Flag_RECORD_USAGE, "Record usage"},
{ctrff::SMDH::Flag_DISABLE_SAVE_BACKUPS, "Disable Save backups"},
{ctrff::SMDH::Flag_NEW_3DS, "New 3DS"},
};
const std::map<std::string, ctrff::SMDH::Language> smdh_lang_stropts = {
{"", ctrff::SMDH::Language_All},
{"jp", ctrff::SMDH::Language_Japanese},
{"en", ctrff::SMDH::Language_English},
{"fr", ctrff::SMDH::Language_French},
{"de", ctrff::SMDH::Language_German},
{"it", ctrff::SMDH::Language_Italian},
{"es", ctrff::SMDH::Language_Spanish},
{"cs", ctrff::SMDH::Language_Chinese_Simplified},
{"ko", ctrff::SMDH::Language_Korean},
{"du", ctrff::SMDH::Language_Dutch},
{"po", ctrff::SMDH::Language_Portuguese},
{"ru", ctrff::SMDH::Language_Russian},
{"ct", ctrff::SMDH::Language_Chinese_Traditional},
};
void MakeSMDH(const cf7::command::ArgumentList &data) {
std::string l = cf7::command::GetArg(data, "long");
std::string s = cf7::command::GetArg(data, "short");
std::string a = cf7::command::GetArg(data, "author");
std::string i = cf7::command::GetArg(data, "icon");
std::string o = cf7::command::GetArg(data, "output");
if (l.empty() || s.empty() || a.empty() || i.empty() || o.empty()) {
cf7::PrintFancy({
std::make_pair("Error", cf7::col(190, 0, 0)),
std::make_pair("One or more Arguments are not set!",
cf7::col(130, 0, 0)),
});
return;
}
ctrff::SMDH smdh = ctrff::SMDH::Default();
smdh.SetLongTitle(l);
smdh.SetShortTitle(s);
smdh.SetAuthor(a);
std::vector<unsigned char> img;
int w, h, c;
PD::u8 *buf = stbi_load(i.c_str(), &w, &h, &c, 4);
if (buf == nullptr) {
cf7::PrintFancy({
std::make_pair("Error", cf7::col(190, 0, 0)),
std::make_pair("Can't open icon File!", cf7::col(130, 0, 0)),
});
return;
}
if (w != 48 || h != 48) {
cf7::PrintFancy({
std::make_pair("Error", cf7::col(190, 0, 0)),
std::make_pair("Icon is not 48x48 pixels!", cf7::col(130, 0, 0)),
});
return;
}
img.assign(buf, buf + (w * h * 4));
smdh.SetIcon(img);
std::fstream f(o, std::ios::out | std::ios::binary);
smdh.Write(f);
cf7::PrintFancy({
std::make_pair("File Generated", cf7::col(0, 190, 0)),
});
}
void ReadSMDH(const cf7::command::ArgumentList &data) {
ctrff::SMDH smdh;
if (cf7::command::GetArg(data, "input").empty()) {
cf7::PrintFancy({
std::make_pair("Error", cf7::col(190, 0, 0)),
std::make_pair("Input Argument not found!", cf7::col(130, 0, 0)),
});
return;
}
try {
smdh.Load(cf7::command::GetArg(data, "input"));
} catch (const std::exception &e) {
cf7::PrintFancy({
std::make_pair("Error", cf7::col(190, 0, 0)),
std::make_pair(e.what(), cf7::col(130, 0, 0)),
});
return;
}
cf7::PrintFancy({
std::make_pair("CTRFF", cf7::col(220, 160, 0)),
std::make_pair("SMDH-Parser", cf7::col(240, 200, 0)),
});
cf7::PrintFancy({
std::make_pair("Language", cf7::col(220, 160, 0)),
std::make_pair("Short", cf7::col(240, 200, 0)),
std::make_pair("Long", cf7::col(255, 230, 0)),
std::make_pair("Author", cf7::col(255, 255, 0)),
});
for (auto &e : smdh_lang_table) {
cf7::PrintFancy({
std::make_pair(" " + e.second, cf7::col(220, 160, 0)),
std::make_pair(smdh.GetShortTitle(e.first), cf7::col(240, 200, 0)),
std::make_pair(smdh.GetLongTitle(e.first), cf7::col(255, 230, 0)),
std::make_pair(smdh.GetAuthor(e.first), cf7::col(255, 255, 0)),
});
}
cf7::PrintFancy({
std::make_pair("Version", cf7::col(220, 160, 0)),
std::make_pair(std::to_string(smdh.Version), cf7::col(240, 200, 0)),
});
cf7::PrintFancy({
std::make_pair("Ratings", cf7::col(220, 160, 0)),
});
cf7::PrintFancy({
std::make_pair("Flags", cf7::col(220, 160, 0)),
});
for (auto &e : smdh_flag_table) {
cf7::PrintFancy({
std::make_pair(" " + e.second, cf7::col(220, 160, 0)),
std::make_pair(
std::string((smdh.Settings.Flags & e.first) ? "true" : "false"),
(smdh.Settings.Flags & e.first) ? cf7::col(0, 190, 0)
: cf7::col(190, 0, 0)),
});
}
if (smdh.Settings.RegionLock == ctrff::SMDH::Region_FREE) {
cf7::PrintFancy({
std::make_pair("Region", cf7::col(220, 160, 0)),
std::make_pair("Free", cf7::col(240, 200, 0)),
});
} else {
for (auto &e : smdh_region_table) {
cf7::PrintFancy({
std::make_pair("Regions", cf7::col(220, 160, 0)),
});
cf7::PrintFancy({
std::make_pair(e.second, cf7::col(220, 160, 0)),
std::make_pair(
std::string(smdh.Settings.RegionLock & e.first ? "true"
: "false"),
smdh.Settings.RegionLock & e.first ? cf7::col(0, 190, 0)
: cf7::col(190, 0, 0)),
});
}
}
cf7::PrintFancy({
std::make_pair("MatchmakerID", cf7::col(220, 160, 0)),
std::make_pair(std::to_string(smdh.Settings.MatchmakerID),
cf7::col(240, 200, 0)),
});
std::string icon_path = cf7::command::GetArg(data, "extract-icon");
if (!icon_path.empty()) {
auto icon = smdh.GetIcon();
stbi_write_png(icon_path.c_str(), 48, 48, 4, icon.data(), 48 * 4);
/* if (res) {
// Error
} else {*/
cf7::PrintFancy({
std::make_pair("Export", cf7::col(0, 190, 0)),
std::make_pair(icon_path, cf7::col(0, 130, 0)),
std::make_pair("Success", cf7::col(0, 80, 0)),
});
// }
}
}
void Read3DSX(const cf7::command::ArgumentList &data) {
ctrff::_3dsx _3dsx;
if (cf7::command::GetArg(data, "input").empty()) {
cf7::PrintFancy({
std::make_pair("Error", cf7::col(190, 0, 0)),
std::make_pair("Input Argument not found!", cf7::col(130, 0, 0)),
});
return;
}
/*if (!*/ _3dsx.Load(cf7::command::GetArg(data, "input")); /*) {
cf7::PrintFancy({
std::make_pair("Error", cf7::col(190, 0, 0)),
std::make_pair("Could not load 3dsx!", cf7::col(130, 0, 0)),
});
return;
}*/
if (!_3dsx.HasMeta()) {
cf7::PrintFancy({
std::make_pair("Error", cf7::col(190, 0, 0)),
std::make_pair("3dsx has no meta!", cf7::col(130, 0, 0)),
});
return;
}
ctrff::SMDH smdh = _3dsx.Meta;
cf7::PrintFancy({
std::make_pair("CTRFF", cf7::col(220, 160, 0)),
std::make_pair("SMDH-Parser", cf7::col(240, 200, 0)),
});
cf7::PrintFancy({
std::make_pair("Language", cf7::col(220, 160, 0)),
std::make_pair("Short", cf7::col(240, 200, 0)),
std::make_pair("Long", cf7::col(255, 230, 0)),
std::make_pair("Author", cf7::col(255, 255, 0)),
});
for (auto &e : smdh_lang_table) {
cf7::PrintFancy({
std::make_pair(" " + e.second, cf7::col(220, 160, 0)),
std::make_pair(smdh.GetShortTitle(e.first), cf7::col(240, 200, 0)),
std::make_pair(smdh.GetLongTitle(e.first), cf7::col(255, 230, 0)),
std::make_pair(smdh.GetAuthor(e.first), cf7::col(255, 255, 0)),
});
}
cf7::PrintFancy({
std::make_pair("Version", cf7::col(220, 160, 0)),
std::make_pair(std::to_string(smdh.Version), cf7::col(240, 200, 0)),
});
cf7::PrintFancy({
std::make_pair("Ratings", cf7::col(220, 160, 0)),
});
cf7::PrintFancy({
std::make_pair("Flags", cf7::col(220, 160, 0)),
});
for (auto &e : smdh_flag_table) {
cf7::PrintFancy({
std::make_pair(" " + e.second, cf7::col(220, 160, 0)),
std::make_pair(
std::string((smdh.Settings.Flags & e.first) ? "true" : "false"),
(smdh.Settings.Flags & e.first) ? cf7::col(0, 190, 0)
: cf7::col(190, 0, 0)),
});
}
if (smdh.Settings.RegionLock == ctrff::SMDH::Region_FREE) {
cf7::PrintFancy({
std::make_pair("Region", cf7::col(220, 160, 0)),
std::make_pair("Free", cf7::col(240, 200, 0)),
});
} else {
for (auto &e : smdh_region_table) {
cf7::PrintFancy({
std::make_pair("Regions", cf7::col(220, 160, 0)),
});
cf7::PrintFancy({
std::make_pair(e.second, cf7::col(220, 160, 0)),
std::make_pair(
std::string(smdh.Settings.RegionLock & e.first ? "true"
: "false"),
smdh.Settings.RegionLock & e.first ? cf7::col(0, 190, 0)
: cf7::col(190, 0, 0)),
});
}
}
cf7::PrintFancy({
std::make_pair("MatchmakerID", cf7::col(220, 160, 0)),
std::make_pair(std::to_string(smdh.Settings.MatchmakerID),
cf7::col(240, 200, 0)),
});
std::string icon_path = cf7::command::GetArg(data, "extract-icon");
if (!icon_path.empty()) {
auto icon = smdh.GetIcon();
stbi_write_png(icon_path.c_str(), 48, 48, 4, icon.data(), 48 * 4);
/* if (res) {
// Error
} else {*/
cf7::PrintFancy({
std::make_pair("Export", cf7::col(0, 190, 0)),
std::make_pair(icon_path, cf7::col(0, 130, 0)),
std::make_pair("Success", cf7::col(0, 80, 0)),
});
// }
}
}
void Hex(const cf7::command::ArgumentList &data) {
if (cf7::command::GetArg(data, "input").empty()) {
cf7::PrintFancy({
std::make_pair("Error", cf7::col(190, 0, 0)),
std::make_pair("Input Argument not found!", cf7::col(130, 0, 0)),
});
return;
}
std::ifstream fr(data[0].second, std::ios::binary);
if (!fr.is_open()) {
cf7::PrintFancy({
std::make_pair("Error", cf7::col(190, 0, 0)),
std::make_pair("Failed to open Input File!", cf7::col(130, 0, 0)),
});
return;
}
std::vector<unsigned char> buf(std::istreambuf_iterator<char>(fr), {});
fr.close();
cf7::PrintFancy({
std::make_pair("CTRFF HEXDUMP", cf7::col(255, 165, 0)),
std::make_pair(data[0].second, cf7::col(255, 210, 0)),
std::make_pair(FormatBytes(buf.size()), cf7::col(255, 255, 0)),
});
std::cout << "+----------+-------------------------------------------------+-"
"-----------------+"
<< std::endl;
std::cout << "| Adress | 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F | "
"ASCII |"
<< std::endl;
std::cout << "+----------+-------------------------------------------------+-"
"-----------------+"
<< std::endl;
std::cout << "| " << std::hex << std::setw(8) << std::setfill('0')
<< (unsigned int)0 << " | ";
std::vector<char> ascii;
int lidx = 0;
for (size_t i = 0; i < buf.size(); i++) {
lidx++;
ascii.push_back((char)buf[i]);
std::cout << std::hex << std::setw(2) << std::setfill('0') << std::uppercase
<< (int)buf[i] << " ";
if (((i + 1) % 0x10) == 0 && i > 14) {
std::cout << "| ";
for (auto &it : ascii)
std::cout << std::dec
<< (((int)it >= 32) && ((int)it <= 126) ? it : '.');
ascii.clear();
std::cout << " |" << std::endl;
std::cout << "| " << std::hex << std::setw(8) << std::setfill('0')
<< (unsigned int)i + 1 << " | ";
lidx = 0;
}
}
if (lidx < 16) {
for (int i = lidx; i < 16; i++) {
ascii.push_back('X');
std::cout << "XX ";
if (i == 15) {
std::cout << "| ";
for (auto &it : ascii)
std::cout << std::dec
<< (((int)it >= 32) && ((int)it <= 126) ? it : '.');
ascii.clear();
std::cout << " |" << std::endl;
}
}
std::cout
<< "+----------+-------------------------------------------------+-"
"-----------------+"
<< std::endl;
}
}
void LZ11Compress(const cf7::command::ArgumentList &data) {
std::string i = cf7::command::GetArg(data, "input");
std::string o = cf7::command::GetArg(data, "output");
std::fstream in(i, std::ios::in | std::ios::binary);
in.seekg(0, std::ios::end);
size_t s = in.tellg();
in.seekg(0, std::ios::beg);
std::vector<unsigned char> buf(s);
in.read(reinterpret_cast<char *>(buf.data()), s);
in.close();
cf7::PrintFancy({
std::make_pair("Input", cf7::col(255, 165, 0)),
std::make_pair(i, cf7::col(255, 210, 0)),
std::make_pair(FormatBytes(buf.size()), cf7::col(255, 255, 0)),
});
auto res = ctrff::LZ11::Compress(buf);
cf7::PrintFancy({
std::make_pair("Output", cf7::col(255, 165, 0)),
std::make_pair(o, cf7::col(255, 210, 0)),
std::make_pair(FormatBytes(res.size()), cf7::col(255, 255, 0)),
});
std::fstream out(o, std::ios::out | std::ios::binary);
out.write(reinterpret_cast<const char *>(res.data()), res.size());
out.close();
}
int main(int argc, char *argv[]) {
cf7::fancy_print = true;
cf7::colors_supported = true;
cf7::arg_mgr mgr(argc, argv);
mgr.SetAppInfo("ctrff", "1.0.0");
auto makesmdh_cmd = cf7::command("makesmdh", "Create a SMDH File");
makesmdh_cmd.AddSubEntry(
cf7::command::sub("i", "icon", "Icon file path (48x48)", true));
makesmdh_cmd.AddSubEntry(
cf7::command::sub("o", "output", "Output smdh path", true));
for (auto &it : smdh_lang_stropts) {
if (it.first.empty()) {
makesmdh_cmd.AddSubEntry(cf7::command::sub(
"s", "short", "Short Title (All Languages)", false));
makesmdh_cmd.AddSubEntry(
cf7::command::sub("l", "long", "Long Title (All Languages)", false));
makesmdh_cmd.AddSubEntry(
cf7::command::sub("a", "author", "Author (All Languages)", false));
} else {
makesmdh_cmd.AddSubEntry(cf7::command::sub(
"s-" + it.first, "short-" + it.first,
"Short Title " + smdh_lang_table.at(it.second), false));
makesmdh_cmd.AddSubEntry(cf7::command::sub(
"l-" + it.first, "long-" + it.first,
"Long Title " + smdh_lang_table.at(it.second), false));
makesmdh_cmd.AddSubEntry(
cf7::command::sub("a-" + it.first, "author-" + it.first,
"Author " + smdh_lang_table.at(it.second), false));
}
}
makesmdh_cmd.SetFunction(MakeSMDH);
mgr.AddCommand(makesmdh_cmd);
mgr.AddCommand(
cf7::command("readsmdh", "Reads a smdh file and can extract icon")
.AddSubEntry(
cf7::command::sub("i", "input", "Input smdh path!", true))
.AddSubEntry(cf7::command::sub("e", "extract-icon",
"Path to Extract Icon to", false))
.SetFunction(ReadSMDH));
mgr.AddCommand(cf7::command("read3dsx", "Read Data of a 3dsx file")
.AddSubEntry(cf7::command::sub("i", "input",
"Input 3dsx path!", true))
.AddSubEntry(cf7::command::sub("e", "extract-icon",
"Output icon path!", false))
.SetFunction(Read3DSX));
mgr.AddCommand(
cf7::command("lz11", "Creates a LZ11 Compressed File")
.AddSubEntry(cf7::command::sub("i", "input", "Input file path", true))
.AddSubEntry(
cf7::command::sub("o", "output", "Output file path", true))
.SetFunction(LZ11Compress));
mgr.AddCommand(
cf7::command("hex", "Show Hex view of a File")
.AddSubEntry(cf7::command::sub("i", "input", "Input File path", true))
.SetFunction(Hex));
mgr.Execute();
return 0;
}

48
tool/test.cpp Normal file
View File

@ -0,0 +1,48 @@
#include <ctrff.hpp>
#include <iomanip>
template <typename T>
void Result(const std::string& name, T a, T b) {
std::cout << "Test: " << typeid(a).name() << " > ";
std::cout << "0x" << std::hex << std::setw(sizeof(a) * 2) << std::setfill('0')
<< a;
std::cout << std::dec << " - ";
std::cout << "0x" << std::hex << std::setw(sizeof(a) * 2) << std::setfill('0')
<< b;
std::cout << std::dec << std::endl;
std::cout << "Test " << name << " " << (a == b ? "passed" : "failed") << "!"
<< std::endl;
}
void BinTest(bool be) {
std::fstream s("test.bin", std::ios::out | std::ios::binary);
PD::u8 t8 = 0xdf;
PD::u16 t16 = 0x4564;
PD::u32 t32 = 0x58464743;
PD::u64 t64 = 1234567890123456789ULL;
ctrff::BinUtil u(s, be);
u.Write(t8);
u.Write(t16);
u.Write(t32);
u.Write(t64);
s.close();
PD::u8 r8 = 0;
PD::u16 r16 = 0;
PD::u32 r32 = 0;
PD::u64 r64 = 0;
s.open("test.bin", std::ios::in | std::ios::binary);
u.Read(r8);
u.Read(r16);
u.Read(r32);
u.Read(r64);
Result("u8 " + std::string(be ? "big" : "little"), r8, t8);
Result("u16 " + std::string(be ? "big" : "little"), r16, t16);
Result("u32 " + std::string(be ? "big" : "little"), r32, t32);
Result("u64 " + std::string(be ? "big" : "little"), r64, t64);
}
int main() {
BinTest(false);
BinTest(true);
return 0;
}

1
vendor/cli-fancy vendored Submodule

@ -0,0 +1 @@
Subproject commit fa035daae1074a5ce808fdb746de00f3727a49b0

1
vendor/palladium vendored Submodule

@ -0,0 +1 @@
Subproject commit f75f7067ffe44ffafc852691b1bb56867842bb6f

1724
vendor/stb/stb_image_write.h vendored Normal file

File diff suppressed because it is too large Load Diff