├── .gitignore ├── README.md ├── makefile └── s3p_extract.c /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.out 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | quickly hacked together, but it works. 2 | 3 | Now with repacking! 4 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | s3p_extract.exe: s3p_extract.c 2 | gcc -Wall -Werror s3p_extract.c -o s3p_extract.exe -------------------------------------------------------------------------------- /s3p_extract.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #if defined(__linux__) || defined(__APPLE__) 9 | #define make_dir(path) mkdir(path, 0755) 10 | #else 11 | #include 12 | #define make_dir(path) _mkdir(path) 13 | #endif 14 | 15 | struct header { 16 | char magic[4]; // S3P0 17 | uint32_t entries; 18 | }; 19 | 20 | struct entry { 21 | uint32_t offset; 22 | uint32_t length; 23 | }; 24 | 25 | // actually 64 bytes but we don't care about the other fields (yet!) 26 | struct s3v0 { 27 | char magic[4]; // S3V0 28 | uint32_t filestart; 29 | uint32_t length; 30 | 31 | uint8_t unknown[20]; 32 | }; 33 | 34 | 35 | void pack(int infile_count, char** infiles, char* out_filename){ 36 | printf("Packing %d files\n", infile_count); 37 | 38 | FILE* out_f = fopen(out_filename, "wb"); 39 | if (!out_f){ 40 | printf("Couldn't create output file %s\n", out_filename); 41 | return; 42 | } 43 | 44 | struct header h = { 45 | .magic = "S3P0", 46 | .entries = infile_count 47 | }; 48 | 49 | fwrite(&h, sizeof(h), 1, out_f); 50 | 51 | struct entry *entries = malloc(sizeof(struct entry) * infile_count); 52 | memset(entries, 0, sizeof(struct entry) * infile_count); 53 | fwrite(entries, sizeof(struct entry), infile_count, out_f); 54 | 55 | for(int i = 0; i < infile_count; i++){ 56 | printf("Packing %s\n", infiles[i]); 57 | 58 | entries[i].offset = ftell(out_f); 59 | FILE* audio = fopen(infiles[i], "rb"); 60 | if (!audio){ 61 | printf("Error opening %s\n", infiles[i]); 62 | fclose(out_f); 63 | return; 64 | } 65 | 66 | int length = 0; 67 | fseek(audio, 0, SEEK_END); 68 | length = ftell(audio); 69 | rewind(audio); 70 | 71 | struct s3v0 audio_header= { 72 | .magic = "S3V0", 73 | .filestart = 0x20, 74 | .length = length 75 | }; 76 | 77 | fwrite(&audio_header, sizeof(struct s3v0), 1, out_f); 78 | 79 | uint8_t* buffer = malloc(length); 80 | fread(buffer, 1, length, audio); 81 | fwrite(buffer, 1, length, out_f); 82 | 83 | entries[i].length = ftell(out_f) - entries[i].offset; 84 | 85 | free(buffer); 86 | fclose(audio); 87 | } 88 | 89 | uint32_t endbytes = 0x12345678; //Unclear what this value is for 90 | fwrite(&endbytes, sizeof(uint32_t), 1, out_f); 91 | 92 | //Repopulate the entries now that they have relevant offsets 93 | fseek(out_f, sizeof(struct header), 0); 94 | fwrite(entries, sizeof(struct entry), infile_count, out_f); 95 | 96 | fclose(out_f); 97 | } 98 | 99 | void convert(const char* path) { 100 | printf("%s\n", path); 101 | 102 | FILE* f = fopen(path, "rb"); 103 | if(!f) { 104 | printf("Couldn't open!\n"); 105 | return; 106 | } 107 | 108 | char* out = malloc(strlen(path) + strlen(".out") + 1); 109 | sprintf(out, "%s.out", path); 110 | 111 | if(make_dir(out) && errno != EEXIST) { 112 | printf("Couldn't make out dir\n"); 113 | goto CLEANUP; 114 | } 115 | 116 | struct header h; 117 | fread(&h, sizeof(h), 1, f); 118 | if(memcmp(h.magic, "S3P0", 4)) { 119 | printf("Bad magic!\n"); 120 | goto CLEANUP; 121 | } 122 | 123 | struct entry *entries = malloc(sizeof(struct entry) * h.entries); 124 | fread(entries, sizeof(struct entry), h.entries, f); 125 | 126 | for(uint32_t i = 0; i < h.entries; i++) { 127 | printf("%f%%\n", (float)(i+1)/(float)h.entries * 100); 128 | char* out_file = malloc(strlen(out) + 100); 129 | sprintf(out_file, "%s/%d.wma", out, i); 130 | FILE* out_f = fopen(out_file, "wb"); 131 | free(out_file); 132 | if(!out_f) { 133 | printf("Couldn't open output %s\n", out_file); 134 | goto CLEANUP; 135 | } 136 | 137 | fseek(f, entries[i].offset, SEEK_SET); 138 | void* buffer = malloc(entries[i].length); 139 | fread(buffer, 1, entries[i].length, f); 140 | 141 | struct s3v0 *file_header = (struct s3v0*)buffer; 142 | if(memcmp(file_header->magic, "S3V0", 4)) { 143 | printf("Bad magic! Need S3V0 got %c%c%c%c\n", 144 | file_header->magic[0], 145 | file_header->magic[1], 146 | file_header->magic[2], 147 | file_header->magic[3] 148 | ); 149 | free(buffer); 150 | goto CLEANUP; 151 | } 152 | 153 | fwrite(buffer + file_header->filestart, 1, entries[i].length - file_header->filestart, out_f); 154 | free(buffer); 155 | fclose(out_f); 156 | } 157 | 158 | CLEANUP: 159 | fclose(f); 160 | free(out); 161 | } 162 | 163 | int main(int argc, char** argv) { 164 | if(argc < 2 || strcmp(argv[argc - 1], "-o") == 0 || strcmp(argv[argc - 1], "-pack") == 0) { 165 | printf("Usage:\n"); 166 | printf("Unpack: s3p_extract file.s3p [file2.s3p] [file3.s3p]\n"); 167 | printf("Repack: s3p_extract -pack [-o filename.s3p] file.wma [file2.wma] [file3.wma]\n"); 168 | return 1; 169 | } 170 | 171 | if (strcmp(argv[1], "-pack") == 0){ 172 | if (strcmp(argv[2], "-o") == 0){ 173 | if (argc < 5){ 174 | printf("Enter at least one input file\n"); 175 | return 1; 176 | } 177 | pack(argc - 4, &argv[4], argv[3]); 178 | } 179 | else{ 180 | pack(argc-2, &argv[2], "out.s3p"); 181 | } 182 | return 0; 183 | } 184 | 185 | for(int i = 1; i < argc; i++) { 186 | convert(argv[i]); 187 | } 188 | return 0; 189 | } 190 | --------------------------------------------------------------------------------