Skip to content

Commit 163e1af

Browse files
authored
Merge pull request #1570 from nicolasnoble/modconv
Adding modconv tool.
2 parents bed8be6 + 06bfd2d commit 163e1af

File tree

12 files changed

+560
-28
lines changed

12 files changed

+560
-28
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ SUPPORT_SRCS += third_party/ucl/src/n2e_99.c third_party/ucl/src/alloc.c
169169
SUPPORT_SRCS += $(wildcard third_party/iec-60908b/*.c)
170170
OBJECTS := third_party/luajit/src/libluajit.a
171171

172-
TOOLS = exe2elf exe2iso ps1-packer psyq-obj-parser
172+
TOOLS = exe2elf exe2iso modconv ps1-packer psyq-obj-parser
173173

174174
##############################################################################
175175

azure-pipelines-cli.yml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,9 @@ steps:
6464
vsprojects/x64/ReleaseCLI/crashpad_handler.exe
6565
vsprojects/x64/ReleaseCLI/exe2elf.exe
6666
vsprojects/x64/ReleaseCLI/exe2iso.exe
67-
vsprojects/x64/ReleaseCLI/psyq-obj-parser.exe
67+
vsprojects/x64/ReleaseCLI/modconv.exe
6868
vsprojects/x64/ReleaseCLI/ps1-packer.exe
69+
vsprojects/x64/ReleaseCLI/psyq-obj-parser.exe
6970
vsprojects/x64/ReleaseCLI/*.dll
7071
TargetFolder: '$(build.artifactStagingDirectory)/binaries'
7172

@@ -83,11 +84,12 @@ steps:
8384
!**\crashpad_handler.exe
8485
!**\exe2elf.exe
8586
!**\exe2iso.exe
86-
!**\pcsxrunner.exe
87-
!**\pcsx-wrapper.exe
87+
!**\modconv.exe
8888
!**\pcsx-redux.exe
89-
!**\psyq-obj-parser.exe
89+
!**\pcsx-wrapper.exe
90+
!**\pcsxrunner.exe
9091
!**\ps1-packer.exe
92+
!**\psyq-obj-parser.exe
9193
!third_party\**\*.exe
9294
searchFolder: '$(System.DefaultWorkingDirectory)'
9395
pathtoCustomTestAdapters: 'GoogleTestAdapter'

azure-pipelines.yml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,9 @@ steps:
7272
vsprojects/x64/ReleaseWithClangCL/crashpad_handler.exe
7373
vsprojects/x64/ReleaseWithClangCL/exe2elf.exe
7474
vsprojects/x64/ReleaseWithClangCL/exe2iso.exe
75-
vsprojects/x64/ReleaseWithClangCL/psyq-obj-parser.exe
75+
vsprojects/x64/ReleaseWithClangCL/modconv.exe
7676
vsprojects/x64/ReleaseWithClangCL/ps1-packer.exe
77+
vsprojects/x64/ReleaseWithClangCL/psyq-obj-parser.exe
7778
vsprojects/x64/ReleaseWithClangCL/*.dll
7879
TargetFolder: '$(build.artifactStagingDirectory)/binaries'
7980

@@ -91,11 +92,12 @@ steps:
9192
!**\crashpad_handler.exe
9293
!**\exe2elf.exe
9394
!**\exe2iso.exe
94-
!**\pcsxrunner.exe
95+
!**\modconv.exe
9596
!**\pcsx-redux.main
9697
!**\pcsx-redux.exe
97-
!**\psyq-obj-parser.exe
98+
!**\pcsxrunner.exe
9899
!**\ps1-packer.exe
100+
!**\psyq-obj-parser.exe
99101
!third_party\**\*.exe
100102
searchFolder: '$(System.DefaultWorkingDirectory)'
101103
pathtoCustomTestAdapters: 'GoogleTestAdapter'

src/mips/modplayer/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@ This code is a reverse engineering of the file MODPLAY.BIN, located in the zip f
55
The current API behaves roughly the same the original one. The code should provide information about the alterations made to it.
66

77
The demo song, "timewarped", written by [Jesster](https://modarchive.org/index.php?request=view_profile&query=69138), comes from [the modarchive](https://modarchive.org/index.php?request=view_by_moduleid&query=106481) with a [CC BY-NC-SA 3.0](https://creativecommons.org/licenses/by-nc-sa/3.0/) license, allowing non-commercial adaptations. The file has been converted from MOD to HIT using the MODCONV.EXE software provided by Hitmen.
8+
9+
A modern recreation of the MODCONV.EXE software is available within the PCSX-Redux project itself. See the [modconv](https://github.com/grumpycoders/pcsx-redux/tree/main/tools/modconv) folder for more information.

src/mips/modplayer/modplayer.c

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ static void MOD_SetBPM(unsigned bpm) {
218218

219219
static struct SPUChannelData s_channelData[24];
220220

221-
uint32_t MOD_Load(const struct MODFileFormat* module) {
221+
static uint32_t loadInternal(const struct MODFileFormat* module, const uint8_t* sampleData) {
222222
SPUInit();
223223
MOD_Channels = MOD_Check(module);
224224

@@ -241,8 +241,12 @@ uint32_t MOD_Load(const struct MODFileFormat* module) {
241241

242242
MOD_ModuleData = (const uint8_t*)&module->patternTable[0];
243243

244-
SPUUploadInstruments(0x1010, MOD_ModuleData + 4 + 128 + MOD_Channels * 0x100 * (maxPatternID + 1),
245-
currentSpuAddress - 0x1010);
244+
if (sampleData) {
245+
SPUUploadInstruments(0x1010, sampleData, currentSpuAddress - 0x1010);
246+
} else {
247+
SPUUploadInstruments(0x1010, MOD_ModuleData + 4 + 128 + MOD_Channels * 0x100 * (maxPatternID + 1),
248+
currentSpuAddress - 0x1010);
249+
}
246250

247251
MOD_CurrentOrder = 0;
248252
MOD_CurrentPattern = module->patternTable[0];
@@ -274,6 +278,13 @@ uint32_t MOD_Load(const struct MODFileFormat* module) {
274278
return 4 + 128 + MOD_Channels * 0x100 * (maxPatternID + 1);
275279
}
276280

281+
uint32_t MOD_Load(const struct MODFileFormat* module) { return loadInternal(module, NULL); }
282+
283+
unsigned MOD_LoadEx(const struct MODFileFormat* module, const uint8_t* sampleData) {
284+
loadInternal(module, sampleData);
285+
return MOD_Channels;
286+
}
287+
277288
void MOD_Silence() {
278289
SPUInit();
279290
for (unsigned i = 0; i < 24; i++) {

src/mips/modplayer/modplayer.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,17 @@ unsigned MOD_Check(const struct MODFileFormat* module);
7676
// the SPU.
7777
uint32_t MOD_Load(const struct MODFileFormat* module);
7878

79+
// Loads the specified module and gets it ready for
80+
// playback. The pointers have to be aligned to a
81+
// 4-bytes boundary. Will also setup the SPU. This
82+
// call is meant to be used with the separate .smp
83+
// file, which the new modconv.exe tool can generate.
84+
// Returns the number of channels from the module,
85+
// or 0 if the module is invalid. No relocation is
86+
// needed, and the sampleData pointer can simply be
87+
// freed after this call.
88+
unsigned MOD_LoadEx(const struct MODFileFormat* module, const uint8_t* sampleData);
89+
7990
// Call this function periodically to play sound. The
8091
// frequency at which this is called will determine the
8192
// actual playback speed of the module. Most modules will

src/support/binstruct.h

Lines changed: 77 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ SOFTWARE.
2929
#include <stdint.h>
3030

3131
#include <bit>
32+
#include <stdexcept>
3233
#include <string>
3334
#include <tuple>
3435
#include <type_traits>
@@ -91,10 +92,6 @@ struct UInt64 : public BasicFieldType<uint64_t, std::endian::little> {
9192
static constexpr char const typeName[] = "uint64_t";
9293
};
9394

94-
struct BEInt8 : public BasicFieldType<int8_t, std::endian::big> {
95-
static constexpr char const typeName[] = "int8_t";
96-
};
97-
9895
struct BEInt16 : public BasicFieldType<int16_t, std::endian::big> {
9996
static constexpr char const typeName[] = "int16_t";
10097
};
@@ -107,10 +104,6 @@ struct BEInt64 : public BasicFieldType<int64_t, std::endian::big> {
107104
static constexpr char const typeName[] = "int64_t";
108105
};
109106

110-
struct BEUInt8 : public BasicFieldType<uint8_t, std::endian::big> {
111-
static constexpr char const typeName[] = "uint8_t";
112-
};
113-
114107
struct BEUInt16 : public BasicFieldType<uint16_t, std::endian::big> {
115108
static constexpr char const typeName[] = "uint16_t";
116109
};
@@ -160,11 +153,17 @@ struct CString {
160153
memcpy(value, v.data(), S);
161154
return *this;
162155
}
163-
void set(const type &v) { memcpy(value, v.data(), S); }
156+
void set(const type &v) {
157+
value[S] = 0;
158+
memcpy(value, v.data(), S);
159+
}
164160
void serialize(IO<File> f) const { f->write(value, S); }
165-
void deserialize(IO<File> f) { f->read(value, S); }
166-
void reset() { memset(value, 0, S); }
167-
char value[S];
161+
void deserialize(IO<File> f) {
162+
value[S] = 0;
163+
f->read(value, S);
164+
}
165+
void reset() { memset(value, 0, S + 1); }
166+
char value[S + 1];
168167
};
169168

170169
template <typename FieldType, typename name>
@@ -183,6 +182,70 @@ struct StructField<StructType, irqus::typestring<C...>> : public StructType {
183182
typedef irqus::typestring<C...> fieldName;
184183
};
185184

185+
template <typename FieldType, typename name, size_t N>
186+
struct RepeatedField;
187+
template <typename FieldType, char... C, size_t N>
188+
struct RepeatedField<FieldType, irqus::typestring<C...>, N> {
189+
RepeatedField() {}
190+
typedef irqus::typestring<C...> fieldName;
191+
FieldType value[N];
192+
FieldType &operator[](size_t i) {
193+
if (i >= N) throw std::out_of_range("Index out of range");
194+
return value[i];
195+
}
196+
const FieldType &operator[](size_t i) const {
197+
if (i >= N) throw std::out_of_range("Index out of range");
198+
return value[i];
199+
}
200+
void serialize(IO<File> f) const {
201+
for (size_t i = 0; i < N; i++) {
202+
value[i].serialize(f);
203+
}
204+
}
205+
void deserialize(IO<File> f) {
206+
for (size_t i = 0; i < N; i++) {
207+
value[i].deserialize(f);
208+
}
209+
}
210+
void reset() {
211+
for (size_t i = 0; i < N; i++) {
212+
value[i].reset();
213+
}
214+
}
215+
};
216+
217+
template <typename FieldType, typename name, size_t N>
218+
struct RepeatedStruct;
219+
template <typename FieldType, char... C, size_t N>
220+
struct RepeatedStruct<FieldType, irqus::typestring<C...>, N> {
221+
RepeatedStruct() {}
222+
typedef irqus::typestring<C...> fieldName;
223+
FieldType value[N];
224+
FieldType &operator[](size_t i) {
225+
if (i >= N) throw std::out_of_range("Index out of range");
226+
return value[i];
227+
}
228+
const FieldType &operator[](size_t i) const {
229+
if (i >= N) throw std::out_of_range("Index out of range");
230+
return value[i];
231+
}
232+
void serialize(IO<File> f) const {
233+
for (size_t i = 0; i < N; i++) {
234+
value[i].serialize(f);
235+
}
236+
}
237+
void deserialize(IO<File> f) {
238+
for (size_t i = 0; i < N; i++) {
239+
value[i].deserialize(f);
240+
}
241+
}
242+
void reset() {
243+
for (size_t i = 0; i < N; i++) {
244+
value[i].reset();
245+
}
246+
}
247+
};
248+
186249
template <typename name, typename... fields>
187250
class Struct;
188251
template <char... C, typename... fields>
@@ -208,8 +271,8 @@ class Struct<irqus::typestring<C...>, fields...> : private std::tuple<fields...>
208271
constexpr void reset() {}
209272
template <size_t index, typename FieldType, typename... nestedFields>
210273
constexpr void reset() {
211-
FieldType &setting = std::get<index>(*this);
212-
setting.reset();
274+
FieldType &field = std::get<index>(*this);
275+
field.reset();
213276
reset<index + 1, nestedFields...>();
214277
}
215278
template <size_t index>

tools/modconv/README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# MODCONV
2+
This folder contains a modern recreation of the MODCONV.EXE software, which is used to convert MOD files to HIT files. The original software was [provided by Hitmen](http://hitmen.c02.at/html/psx_tools.html), without source code.
3+
4+
This version has been written from scratch, without reverse engineering, as the file format is fairly simple to understand. This means that the output files will be slightly different from the original software.
5+
6+
Its purpose is to convert [MOD files](https://en.wikipedia.org/wiki/Module_file) to HIT files, which can then be played by the [modplayer library available](https://github.com/grumpycoders/pcsx-redux/tree/main/src/mips/modplayer) in the PCSX-Redux project.
7+
8+
## Usage
9+
```sh
10+
modconv input.mod [-s output.smp] [-a amp] -o output.hit
11+
```
12+
13+
## Arguments
14+
| Argument | Type | Description |
15+
|-|-|-|
16+
| input.mod | mandatory | Specify the input mod file. |
17+
| -o output.hit | mandatory | Name of the output file. |
18+
| -s output.smp | optional | Name of the output samples data file. |
19+
| -a amp | optional | Amplification factor. Default is 175 |
20+
| -h | optional | Show help. |
21+
22+
If the `-i` argument is not provided, the sample data will be written to the .hit file itself, and can be loaded with the `MOD_Load` function from the modplayer library or the old Hitmen implementation. If the `-i` argument is provided, the sample data will be written into a separate file. Both will need to be loaded with the `MOD_LoadEx` function from the modplayer library, and is not backwards compatible with the old Hitmen implementation. This allows the user to have a simple way to unload the samples data from the main ram after the call to `MOD_LoadEx`, only keeping the .hit file in memory.

0 commit comments

Comments
 (0)