Skip to content

Commit 7aac195

Browse files
Add files via upload
1 parent ca53115 commit 7aac195

2 files changed

Lines changed: 919 additions & 0 deletions

File tree

FB2SCI.cpp

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
/********************************************************************
2+
* FB2SCI conversion utility v1.00 *
3+
* by Brandon Blume *
4+
* shine62@gmail.com *
5+
* February 25, 2023 *
6+
* *
7+
* Command line tool to convert 2 FB-01 sysex bank files into *
8+
* Sierra's IMF/FB-01 patch resource format for SCI0 games. *
9+
* *
10+
* You're free to do with it as you please. This program could *
11+
* probably be vastly improved to be more efficient, but it works. *
12+
********************************************************************/
13+
14+
#include <fstream>
15+
#include <iostream>
16+
#include <vector>
17+
#include <iomanip>
18+
19+
using namespace std;
20+
21+
float nVersion = 1.00;
22+
23+
void read_files(ifstream& file1, ifstream& file2, vector<char>& data1, vector<char>& data2);
24+
void reorganize_data(vector<char>& data1, vector<char>& data2);
25+
void write_to_file(std::vector<char> data1, std::vector<char> data2, const char* output_filename);
26+
bool check_file_exists(const char* filename);
27+
void check_output_file(string output_filename);
28+
29+
int main(int argc, char* argv[]) {
30+
// Check if the user provided exactly three arguments
31+
32+
std::cout << std::fixed;
33+
std::cout << std::setprecision(2);
34+
cout << "\nFB2SCI v" << nVersion << " by Brandon Blume February 25, 2023" << endl;
35+
36+
if (argc != 4) {
37+
cout << " usage: " << argv[0] << " bankfile1 bankfile2 patfile\n";
38+
return 1;
39+
}
40+
cout << endl;
41+
42+
// Get the filenames from the command line arguments
43+
char* input_filename1 = argv[1];
44+
char* input_filename2 = argv[2];
45+
char* output_filename = argv[3];
46+
47+
// Open the first input bank file (Bank A)
48+
ifstream input_file1(input_filename1, ios::binary);
49+
50+
// Check if bankfile1 exists
51+
if (!check_file_exists(input_filename1)) {
52+
cout << "Error: file " << input_filename1 << " not found" << endl;
53+
exit(EXIT_FAILURE);
54+
}
55+
56+
// Read the first 7 bytes from the file
57+
char header[7];
58+
input_file1.read(header, 7);
59+
60+
// Check if the header for bankfile1 matches the expected value for the FB-01's send Bank A sysex code
61+
if (memcmp(header, "\xF0\x43\x75\x00\x00\x00\x00", 7) != 0) {
62+
cout << "Error: " << input_filename1 << " is not a valid FB-01 sysex bank file (missing expected sysex header)." << endl;
63+
exit(EXIT_FAILURE);
64+
}
65+
66+
// Get the length of the file
67+
input_file1.seekg(0, ios::end);
68+
std::streamoff length = input_file1.tellg();
69+
input_file1.seekg(0, ios::beg);
70+
71+
// Check if the length is 6363 bytes (must be no larger or smaller
72+
if (length != 6363) {
73+
cout << input_filename1 << " is not the expected size (6363 bytes). Not a valid FB-01 sysex bank file." << endl << "Actual size: " << length << endl;
74+
exit(EXIT_FAILURE);
75+
}
76+
77+
// Open the second input bank file (Bank B)
78+
ifstream input_file2(input_filename2, ios::binary);
79+
80+
// Check if bankfile2 exists
81+
if (!check_file_exists(input_filename1)) {
82+
cout << "Error: file " << input_filename2 << " not found" << endl;
83+
exit(EXIT_FAILURE);
84+
}
85+
86+
// Read the first 7 bytes from the file
87+
input_file2.read(header, 7);
88+
89+
// Check if the header for bankfile2 matches the expected value for the FB-01's send Bank B sysex code
90+
if (memcmp(header, "\xF0\x43\x75\x00\x00\x00\x01", 7) != 0) {
91+
cout << "Error: " << input_filename2 << " is not a valid FB-01 sysex bank file (missing expected sysex header)." << endl;
92+
exit(EXIT_FAILURE);
93+
}
94+
95+
// Get the length of the file
96+
input_file2.seekg(0, ios::end);
97+
length = input_file2.tellg();
98+
input_file2.seekg(0, ios::beg);
99+
100+
// Check if the length is 6363 bytes (no larger or smaller)
101+
if (length != 6363) {
102+
cout << input_filename2 << " is not the expected size (6363 bytes). Not a valid FB-01 sysex bank file." << endl << "Actual size: " << length << endl;
103+
exit(EXIT_FAILURE);
104+
}
105+
106+
// Check if output file already exists. If it does, ask user whether to overwrite or abort.
107+
check_output_file(output_filename);
108+
109+
// Read the files into memory
110+
vector<char> data1, data2;
111+
read_files(input_file1, input_file2, data1, data2);
112+
113+
// Close the input files
114+
input_file1.close();
115+
input_file2.close();
116+
117+
118+
// Byte-swap then nibble-merge the data, overwriting and truncating the vectors by half
119+
reorganize_data(data1, data2);
120+
// Create the patch file with the new "denibbled" data
121+
write_to_file(data1, data2, output_filename);
122+
123+
cout << "SCI FB-01 Patch created successfully!" << endl;
124+
125+
return 0;
126+
}
127+
128+
void read_files(ifstream& file1, ifstream& file2, vector<char>& data1, vector<char>& data2) {
129+
// Set the initial read position for each bank file to address 0x4C (this is where the first instrument packet's patch data is located)
130+
streampos pos1 = 0x4c;
131+
streampos pos2 = 0x4C;
132+
133+
// Read the files one at a time, skipping 3 bytes between each 128-byte instrument patch block
134+
// (the last byte in a packet is that packet's checksum and the first two bytes of the next packet are the next packet's
135+
// size identifier. we already skipped the two packet size indentifier bytes in the first packet by jumping straight to address 0x4C)
136+
for (int i = 0; i < 48; i++) {
137+
// Read 128 bytes from bankfile1 and bankfile2
138+
file1.seekg(pos1);
139+
file2.seekg(pos2);
140+
char buffer1[128];
141+
char buffer2[128];
142+
file1.read(buffer1, 128);
143+
file2.read(buffer2, 128);
144+
data1.insert(data1.end(), buffer1, buffer1 + 128);
145+
data2.insert(data2.end(), buffer2, buffer2 + 128);
146+
147+
// Skip 3 bytes (the current packet's checksum and the following packet's size identifier bytes) to get to the next instrument's patch data
148+
pos1 += 131;
149+
pos2 += 131;
150+
}
151+
}
152+
153+
void reorganize_data(vector<char>& data1, vector<char>& data2) {
154+
// Check if the data vectors have the same size. (really, if the input files passed the format checks, this should never error)
155+
if (data1.size() != data2.size()) {
156+
cout << "Error: data vectors have different sizes" << endl;
157+
cout << "data1 size = " << data1.size() << endl << "data2 size = " << data2.size() << endl;
158+
return;
159+
}
160+
// Double check to ensure that both sets of instrument packets equal 6144 bytes in length (128 bytes per 48 instruments per bank file).
161+
// Again, this should never error. Probably superfluous.
162+
else if (data1.size() != 6144) {
163+
cout << "Error: data vectors not the expected size (6144)" << endl;
164+
cout << "data1 size = " << data1.size() << endl << "data2 size = " << data2.size() << endl;
165+
}
166+
167+
//////////////////////////////////////////////////////////////////////////////////////////////
168+
// Now we must byte-swap and nibble-merge each byte pair in every instrument packet. //
169+
// This will extract the raw patch data that SCI's patch format needs. This will reduce //
170+
// the packet size for each instrument from 128 bytes to 64 bytes so after the bytes //
171+
// are "denibblized", we will halve the data vectors sizes //
172+
//////////////////////////////////////////////////////////////////////////////////////////////
173+
174+
// Iterate over each byte pair of the data vectors
175+
for (int i = 0; i < static_cast<int>(data1.size()); i += 2) {
176+
// Extract the high and low bytes for the first byte pairs
177+
char high_byte1 = data1[i];
178+
char low_byte1 = data1[i + 1];
179+
char high_byte2 = data2[i];
180+
char low_byte2 = data2[i + 1];
181+
182+
// Merge the byte pairs by shifting the low byte's low nibble to its high nibble and OR-ing the high byte's low nibble together
183+
// with the low byte's new high nibble
184+
unsigned char merged_byte1 = ((low_byte1 & 0x0F) << 4) | (high_byte1 & 0x0F);
185+
unsigned char merged_byte2 = ((low_byte2 & 0x0F) << 4) | (high_byte2 & 0x0F);
186+
187+
// Store the reorganized data back to the data vector at the beginning where it left off
188+
data1[i / 2] = merged_byte1;
189+
data1[(i / 2) + 1] = 0x00;
190+
data2[i / 2] = merged_byte2;
191+
data2[(i / 2) + 1] = 0x00;
192+
}
193+
194+
// Halve the size of both data vectors now that the "denibblized" data is half the original size
195+
data1.resize(data1.size() / 2);
196+
data2.resize(data2.size() / 2);
197+
}
198+
199+
void write_to_file(std::vector<char> data1, std::vector<char> data2, const char* output_filename) {
200+
// Open the output file in binary mode for writing
201+
std::ofstream out_file(output_filename, std::ios::binary);
202+
out_file.seekp(0, std::ios::beg);
203+
204+
//////////////////////////////////////////////////////////////////////////////////////////
205+
// The FB-01 SCI Patch file format we must create is structured like so: //
206+
// //
207+
// $00 : 8900h.......................SCI's resource type identifier header //
208+
// $02 : Bank 1 data.................First 48 instrument patches (64 bytes each) //
209+
// $C02: ABCDh.......................Seperator bytes between the two banks //
210+
// $C04: Bank 2 data.................Last 48 instrument patches (64 bytes each) //
211+
// //
212+
// The resulting file will be exactly 6148 bytes long. //
213+
//////////////////////////////////////////////////////////////////////////////////////////
214+
215+
char sciPatchHeader[2] = { '\x89', '\x00' };
216+
out_file.write(reinterpret_cast<char*>(&sciPatchHeader), sizeof(sciPatchHeader));
217+
out_file.write(data1.data(), data1.size());
218+
char bankSeparator[2] = { '\xAB', '\xCD' };
219+
out_file.write(reinterpret_cast<char*>(&bankSeparator), sizeof(bankSeparator));
220+
out_file.write(data2.data(), data2.size());
221+
out_file.close();
222+
}
223+
224+
bool check_file_exists(const char* filename) {
225+
std::ifstream infile(filename);
226+
return infile.good();
227+
}
228+
229+
void check_output_file(string output_filename) {
230+
ifstream file(output_filename);
231+
if (file.good()) {
232+
cout << "Output file already exists. Do you want to overwrite it? (Y/N): ";
233+
string answer;
234+
cin >> answer;
235+
if (answer == "Y" || answer == "y") {
236+
ofstream file(output_filename, ios::trunc);
237+
cout << "\nFile " << output_filename << " successfully wiped.\n" << endl;
238+
file.close();
239+
}
240+
else {
241+
cout << "Aborting operation..." << endl;
242+
exit(EXIT_FAILURE);
243+
}
244+
}
245+
}

0 commit comments

Comments
 (0)