Skip to content

Commit 067820b

Browse files
add directory index format
1 parent bd20637 commit 067820b

9 files changed

Lines changed: 249 additions & 75 deletions

File tree

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ add_library(resource_file
5555
src/IndexFormats/AppleSingle-AppleDouble.cc
5656
src/IndexFormats/CBag.cc
5757
src/IndexFormats/DCData.cc
58+
src/IndexFormats/Directory.cc
5859
src/IndexFormats/HIRF.cc
5960
src/IndexFormats/MacBinary.cc
6061
src/IndexFormats/Mohawk.cc

src/Audio/MODSynthesizer.cc

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1174,7 +1174,7 @@ void MODSynthesizer::run_one() {
11741174
void MODSynthesizer::run_all() {
11751175
bool changed_partition = false;
11761176
this->max_output_samples = this->opts->sample_rate * this->opts->max_output_seconds * 2;
1177-
while (this->pos.partition_index < this->mod->partition_count && !this->exceeded_time_limit()) {
1177+
while (!this->done()) {
11781178
this->execute_current_division_commands();
11791179
// Note: We print the partition after executing its commands so that the
11801180
// timing information will be consistent if any Fxx commands were run.
@@ -1195,6 +1195,10 @@ void MODSynthesizer::run_all() {
11951195
}
11961196
}
11971197

1198+
bool MODSynthesizer::done() const {
1199+
return (this->pos.partition_index >= this->mod->partition_count || this->exceeded_time_limit());
1200+
}
1201+
11981202
MODRenderer::MODRenderer(shared_ptr<const Module> mod, shared_ptr<const Options> opts)
11991203
: MODSynthesizer(mod, opts) {}
12001204

src/Audio/MODSynthesizer.hh

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ public:
110110
// If not empty, audio is muted for all tracks except those specified in
111111
// this set
112112
std::unordered_set<size_t> solo_tracks;
113-
// Factor by which to spees up or slow down the entire song
113+
// Factor by which to speed up or slow down the entire song
114114
float tempo_bias = 1.0;
115115
// Number of full arpeggio cycles per division. If set to zero, arpeggios
116116
// use tick boundaries instead of being evenly spaced across the division
@@ -133,6 +133,15 @@ public:
133133
void run_one();
134134
void run_all();
135135

136+
bool done() const;
137+
138+
inline std::shared_ptr<const Module> get_module() const {
139+
return this->mod;
140+
}
141+
inline std::shared_ptr<const Options> get_options() const {
142+
return this->opts;
143+
}
144+
136145
protected:
137146
struct Timing {
138147
size_t sample_rate;

src/IndexFormats/Directory.cc

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
#include "Formats.hh"
2+
3+
#include <stdint.h>
4+
5+
#include <filesystem>
6+
#include <phosg/Encoding.hh>
7+
#include <phosg/Strings.hh>
8+
#include <string>
9+
10+
#include "../ResourceFile.hh"
11+
#include "../TextCodecs.hh"
12+
13+
using namespace std;
14+
using namespace phosg;
15+
16+
namespace ResourceDASM {
17+
18+
ResourceFile load_resource_file_from_directory(const string& dir_path) {
19+
ResourceFile ret;
20+
for (const auto& type_item : std::filesystem::directory_iterator(dir_path)) {
21+
if (!type_item.is_directory()) {
22+
continue;
23+
}
24+
25+
string type_item_name = type_item.path().filename();
26+
uint32_t type = resource_type_for_raw_string(unescape_hex_bytes_for_filename(type_item_name));
27+
28+
for (const auto& res_item : std::filesystem::directory_iterator(std::filesystem::path(dir_path) / type_item_name)) {
29+
if (!res_item.is_regular_file()) {
30+
continue;
31+
}
32+
33+
string res_item_name = res_item.path().filename();
34+
if (!res_item_name.ends_with(".bin")) {
35+
continue;
36+
}
37+
38+
// Filename is eiher like 20.bin (ID only) or 20_Resource_name.bin (ID +
39+
// name; name has _XX => escaped byte). Trim off the .bin first
40+
res_item_name.resize(res_item_name.size() - 4);
41+
42+
size_t offset = 0;
43+
int32_t res_id = stol(res_item_name, &offset, 10);
44+
if (res_id < -0x8000 || res_id > 0x7FFF) {
45+
throw std::runtime_error(std::format("Invalid resource ID: {}/{}.bin", type_item_name, res_item_name));
46+
}
47+
if (offset > res_item_name.size()) {
48+
throw std::runtime_error(std::format("Invalid resource filename (parse error): {}/{}.bin", type_item_name, res_item_name));
49+
}
50+
51+
string res_name;
52+
if (offset < res_item_name.size()) {
53+
// Has resource name
54+
if (res_item_name[offset] != '_') {
55+
throw std::runtime_error(std::format("Invalid resource filename (missing separator): {}/{}.bin", type_item_name, res_item_name));
56+
}
57+
res_name = unescape_hex_bytes_for_filename(res_item_name.substr(offset + 1));
58+
}
59+
60+
auto res = make_shared<ResourceFile::Resource>();
61+
res->type = type;
62+
res->id = res_id;
63+
res->flags = 0;
64+
res->name = res_name;
65+
res->data = phosg::load_file(res_item.path().string());
66+
ret.add(res);
67+
}
68+
}
69+
70+
return ret;
71+
}
72+
73+
void save_resource_file_to_directory(const ResourceFile& rf, const std::string& dir_path) {
74+
std::filesystem::path base_path = dir_path;
75+
// TODO: This is kinda dumb. It'd be nice if we could use a generator to just
76+
// iterate the shared_ptr<const Resource> objects directly
77+
for (auto [res_type, res_id] : rf.all_resources()) {
78+
auto res = rf.get_resource(res_type, res_id);
79+
string type_item_name = escape_hex_bytes_for_filename(raw_string_for_resource_type(res_type));
80+
std::filesystem::create_directories(base_path / type_item_name);
81+
string res_item_name = res->name.empty()
82+
? std::format("{}.bin", res->id)
83+
: std::format("{}_{}.bin", res->id, escape_hex_bytes_for_filename(res->name));
84+
phosg::save_file((base_path / type_item_name / res_item_name).string(), res->data);
85+
}
86+
}
87+
88+
} // namespace ResourceDASM

src/IndexFormats/Formats.hh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ ResourceFile parse_cbag(const std::string& data);
4040
// DCData.cc
4141
ResourceFile parse_dc_data(const std::string& data);
4242

43+
// Directory.cc
44+
ResourceFile load_resource_file_from_directory(const std::string& dir_path);
45+
void save_resource_file_to_directory(const ResourceFile& rf, const std::string& dir_path);
46+
4347
// HIRF.cc
4448
ResourceFile parse_hirf(const std::string& data);
4549

src/ResourceFile.hh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ using namespace phosg;
2424
enum class IndexFormat {
2525
NONE = 0, // For ResourceFiles constructed in memory
2626
RESOURCE_FORK,
27+
DIRECTORY,
2728
APPLESINGLE_APPLEDOUBLE,
2829
MACBINARY,
2930
MOHAWK,

src/TextCodecs.cc

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,4 +123,57 @@ string raw_string_for_resource_type(uint32_t type) {
123123
return result;
124124
}
125125

126+
uint32_t resource_type_for_raw_string(const std::string& s) {
127+
switch (s.size()) {
128+
case 0:
129+
return 0x20202020;
130+
case 1:
131+
return ((static_cast<uint32_t>(s[0]) & 0xFF) << 24) | 0x00202020;
132+
case 2:
133+
return ((static_cast<uint32_t>(s[0]) & 0xFF) << 24) |
134+
((static_cast<uint32_t>(s[1]) & 0xFF) << 16) |
135+
0x00002020;
136+
case 3:
137+
return ((static_cast<uint32_t>(s[0]) & 0xFF) << 24) |
138+
((static_cast<uint32_t>(s[1]) & 0xFF) << 16) |
139+
((static_cast<uint32_t>(s[2]) & 0xFF) << 8) |
140+
0x00000020;
141+
case 4:
142+
return ((static_cast<uint32_t>(s[0]) & 0xFF) << 24) |
143+
((static_cast<uint32_t>(s[1]) & 0xFF) << 16) |
144+
((static_cast<uint32_t>(s[2]) & 0xFF) << 8) |
145+
(static_cast<uint32_t>(s[3]) & 0xFF);
146+
default:
147+
throw std::runtime_error(std::format("Invalid resource type name: {}", s));
148+
}
149+
}
150+
151+
string escape_hex_bytes_for_filename(const string& s) {
152+
string ret;
153+
for (size_t z = 0; z < s.size(); z++) {
154+
if (s[z] == '_' || s[z] == '/' || s[z] == ':' || s[z] < 0x20 || s[z] > 0x7E) {
155+
ret += std::format("_{:02X}", static_cast<uint8_t>(s[z]));
156+
} else {
157+
ret.push_back(s[z]);
158+
}
159+
}
160+
return ret;
161+
}
162+
163+
string unescape_hex_bytes_for_filename(const string& s) {
164+
string ret;
165+
for (size_t z = 0; z < s.size(); z++) {
166+
if (s[z] == '_') {
167+
if (z > s.size() - 3) {
168+
throw std::runtime_error(std::format("Invalid escape sequence: {}", s));
169+
}
170+
ret.push_back((phosg::value_for_hex_char(s[z + 1]) << 4) | phosg::value_for_hex_char(s[z + 2]));
171+
z += 2;
172+
} else {
173+
ret.push_back(s[z]);
174+
}
175+
}
176+
return ret;
177+
}
178+
126179
} // namespace ResourceDASM

src/TextCodecs.hh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,13 @@ std::string decode_mac_roman(char data, bool for_filename = false);
1313

1414
std::string string_for_resource_type(uint32_t type, bool for_filename = false);
1515
std::string raw_string_for_resource_type(uint32_t type);
16+
uint32_t resource_type_for_raw_string(const std::string& s);
1617

1718
constexpr bool should_escape_mac_roman_filename_char(char ch) {
1819
return (static_cast<uint8_t>(ch) < 0x20) || (ch == '/') || (ch == ':');
1920
}
2021

22+
std::string escape_hex_bytes_for_filename(const std::string& s);
23+
std::string unescape_hex_bytes_for_filename(const std::string& s);
24+
2125
} // namespace ResourceDASM

0 commit comments

Comments
 (0)