Unpacking/Extracting .arc files

Zhongtiao1

Well-Known Member
OP
Member
Joined
Feb 24, 2015
Messages
831
Trophies
0
Age
26
XP
2,766
Country
United States
I've noticed recently (while working on another project) that a decent amount of ps vita games compress their files in PSARC format. That's easy enough to extract, but more often than not, it results in a .arc file containing the script/text.

Are there any tools specifically that support the general .arc format? I've tried arc_unpacker and Garbro, but both don't know what to do with it.

Note: Most of the games with this format seem to be entergram games
 

Spazzery

Well-Known Member
Newcomer
Joined
Jun 30, 2019
Messages
88
Trophies
0
Age
23
XP
1,381
Country
Estonia
It's a custom archive type, and I didn't know about this archive, until I worked on Making * Lovers and one of my friends figured out this archive format.

What do you want to do with it?
 

Zhongtiao1

Well-Known Member
OP
Member
Joined
Feb 24, 2015
Messages
831
Trophies
0
Age
26
XP
2,766
Country
United States
It's a custom archive type, and I didn't know about this archive, until I worked on Making * Lovers and one of my friends figured out this archive format.

What do you want to do with it?

I'd like to unpack/repack it so that the text can be translated.
 

Zhongtiao1

Well-Known Member
OP
Member
Joined
Feb 24, 2015
Messages
831
Trophies
0
Age
26
XP
2,766
Country
United States
I'm curious to which game? Maybe I can help. If it's Dal Segno, then that game's scripts didn't match at all with the English translation scripts.
Originally it was just Floral Flowlove, but Kin’iro Loveriche also has the same structure. Seems like all Entergram games developed by SAGA PLANETS may have the same structure, actually. Everything is contained within a data_00.psarc file, which has ext, gxt, at9, and arc files.

Giga Entergram games, for example, seem to put everything in PSARCs as well, but it's split across about twenty of them, with the script files in binu8 instead of arc. I think the PSARC+ARC structure is just limited to SAGA PLANETS Entergram games.

Presumably, if the arc format could be figured out on one SAGA PLANETS Entergram game, then the others should work as well.
 

Zhongtiao1

Well-Known Member
OP
Member
Joined
Feb 24, 2015
Messages
831
Trophies
0
Age
26
XP
2,766
Country
United States
[Main Header]
4 bytes UNKNOWN
4 bytes NUMBER OF FILES

[File Headers]
Filename in ASCII, with max of 64 chars of length, ends with \0 but its padded with 0xFE
4 bytes FILE SIZE
4 bytes OFFSET (in relation with the start of the file)

So the final product is:

[Main Header]
Followed by NUMBER OF FILES * [File Header] and then the file data

Should be easy to extract.

EDIT:

Here's the C++ code to extract it:

C++:
/*
Giga Entergram .arc extractor
Copyright (C) 2024  rahcchi (https://github.com/rahcchi)

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/


#include <iostream>
#include <vector>
#include <fstream>
#include <cstdint>
#include <filesystem>
#include <bit>
#include <format>

typedef struct header {
    uint32_t unk;
    uint32_t file_number;
} header;

typedef struct entry_header {
    char file_name[64];
    uint32_t size;
    uint32_t offset;
} entry_header;

template<typename T>
T get(const uint8_t* data, int offset) {
    return *(T*)&data[offset];
}

int main(int argc, char* argv[]) {
    if (!std::filesystem::exists(argv[1])) {
        return -1;
    }

    std::vector<entry_header> entries;

    std::vector<uint8_t> filedata;
    std::ifstream is(argv[1], std::ios::binary);
    is.seekg(0, std::ios_base::end);
    std::size_t filesize = is.tellg();
    is.seekg(0, std::ios_base::beg);
    filedata.resize(filesize);
    is.read((char*)&filedata[0], (std::streamsize)filesize);
    is.close();


    int global_offset = 0;
    header file;
    file.unk = get<uint32_t>(filedata.data(), global_offset);
    global_offset += sizeof(uint32_t);
    file.file_number = get<uint32_t>(filedata.data(), global_offset);
    global_offset += sizeof(uint32_t);

    std::vector<entry_header> files;

    for (uint32_t i = 0; i < file.file_number; i++) {
        entry_header f;
        memcpy(&f.file_name, filedata.data() + global_offset, 64);
        global_offset += 64;
        f.size = get<uint32_t>(filedata.data(), global_offset);
        global_offset += sizeof(uint32_t);
        f.offset = get<uint32_t>(filedata.data(), global_offset);
        global_offset += sizeof(uint32_t);

        files.push_back(f);
    }

    std::cout << std::format("FOUND {} FILES\n", file.file_number);

    std::filesystem::create_directory("out");
    for (auto& e : files) {
        std::ofstream fs(std::format(".\\out\\{}", e.file_name), std::ios::out | std::ios::binary);
        if (fs.is_open()) {
            fs.write((char*)filedata.data() + e.offset, e.size);
            fs.close();
            std::cout << std::format("EXTRACTED {}\n", e.file_name);
        }
    }
    return 0;
}
How were you able to compile the code? I've tried with GCC and CLANG on macOS, and both fail to compile.
 

Zhongtiao1

Well-Known Member
OP
Member
Joined
Feb 24, 2015
Messages
831
Trophies
0
Age
26
XP
2,766
Country
United States
Must be because of the C++23 bits? You can try adding -std=c++2c to the build command. If you remove the lines with std::cout it should still be functional and should compile with older versions, probably.
The error it screams about is actually with <format>.

Specifically:
foo.cpp:26:10: fatal error: format: No such file or directory
26 | #include <format>
| ^~~~~~~~
I have fmt installed, so I'm confused as to why it's broken
 

Zhongtiao1

Well-Known Member
OP
Member
Joined
Feb 24, 2015
Messages
831
Trophies
0
Age
26
XP
2,766
Country
United States
Yeah, remove that <format> line and the std::cout lines and it should compile.

<format> is the standardized version of fmt, but some systems still don't have it and have to rely on experimental libraries.
Unfortunately, it just results in a bunch of undefined references. Same issue when using an x86 ubuntu machine though which is odd. I don't have a windows machine to test on at the moment. I used gcc-13 on the ubuntu machine.
 

Zhongtiao1

Well-Known Member
OP
Member
Joined
Feb 24, 2015
Messages
831
Trophies
0
Age
26
XP
2,766
Country
United States
C++:
/*
Giga Entergram .arc extractor
Copyright (C) 2024  rahcchi (https://github.com/rahcchi)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <iostream>
#include <vector>
#include <fstream>
#include <sys/stat.h>
#include <sys/types.h>
bool file_exists(char* filename) {
    struct stat   buffer;
    return (stat(filename, &buffer) == 0);
}
#ifdef _WIN32
#include <direct.h>
#define mkdir(F,A) _mkdir(F)
#endif
typedef struct header {
    uint32_t unk;
    uint32_t file_number;
} header;
typedef struct entry_header {
    char file_name[64];
    uint32_t size;
    uint32_t offset;
} entry_header;
template<typename T>
T get(const uint8_t* data, int offset) {
    return *(T*)&data[offset];
}
int main(int argc, char* argv[]) {
    if (!file_exists(argv[1])) {
        return -1;
    }
    std::vector<entry_header> entries;
    std::vector<uint8_t> filedata;
    std::ifstream is(argv[1], std::ios::binary);
    is.seekg(0, std::ios_base::end);
    std::size_t filesize = is.tellg();
    is.seekg(0, std::ios_base::beg);
    filedata.resize(filesize);
    is.read((char*)&filedata[0], (std::streamsize)filesize);
    is.close();

    int global_offset = 0;
    header file;
    file.unk = get<uint32_t>(filedata.data(), global_offset);
    global_offset += sizeof(uint32_t);
    file.file_number = get<uint32_t>(filedata.data(), global_offset);
    global_offset += sizeof(uint32_t);
    std::vector<entry_header> files;
    for (uint32_t i = 0; i < file.file_number; i++) {
        entry_header f;
        memcpy(&f.file_name, filedata.data() + global_offset, 64);
        global_offset += 64;
        f.size = get<uint32_t>(filedata.data(), global_offset);
        global_offset += sizeof(uint32_t);
        f.offset = get<uint32_t>(filedata.data(), global_offset);
        global_offset += sizeof(uint32_t);
        files.push_back(f);
    }
    printf("FOUND %d FILES\n", file.file_number);
    mkdir("out",0777);
    for (int i=0;i<files.size();i++){
        char buffer[256];
        memset(buffer, 0, 256);
        sprintf(buffer, ".\\out\\%s", files[i].file_name);
        FILE* f = fopen(buffer, "wb");
        if (f != NULL) {
            fwrite((char*)filedata.data() + files[i].offset, files[i].size, 1, f);
            fclose(f);
            printf("EXTRACTED %s\n", files[i].file_name);
        }
    }
    return 0;
}

This should compile with a 15 year old gcc and should be compatible with everything that supports C++98.

It compiles! However, even though it reads the arc file (seemingly correctly), it only extracts the first five files. It outputs the following in the terminal:

FOUND 37 FILES
EXTRACTED 00_info.bin
EXTRACTED 00_init.txt
EXTRACTED 99_ria.txt
EXTRACTED 99_special.txt
EXTRACTED _stuffroll.txt

And then cleanly exits with no error code. Those files do seem valid though
 

Zhongtiao1

Well-Known Member
OP
Member
Joined
Feb 24, 2015
Messages
831
Trophies
0
Age
26
XP
2,766
Country
United States
C++:
/*
Giga Entergram .arc extractor
Copyright (C) 2024  rahcchi (https://github.com/rahcchi)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <iostream>
#include <vector>
#include <fstream>
#include <sys/stat.h>
#include <sys/types.h>
#include <stdlib.h>

bool file_exists(char* filename) {
    struct stat   buffer;
    return (stat(filename, &buffer) == 0);
}
#ifdef _WIN32
#include <direct.h>
#define mkdir(F,A) _mkdir(F)
#endif
typedef struct header {
    uint32_t unk;
    uint32_t file_number;
} header;
typedef struct entry_header {
    char file_name[64];
    uint32_t size;
    uint32_t offset;
} entry_header;
template<typename T>
T get(const uint8_t* data, int offset) {
    return *(T*)&data[offset];
}
int main(int argc, char* argv[]) {
    setlocale(LC_ALL, "");
    if (!file_exists(argv[1])) {
        return -1;
    }
    std::vector<entry_header> entries;
    std::vector<uint8_t> filedata;
    std::ifstream is(argv[1], std::ios::binary);
    is.seekg(0, std::ios_base::end);
    std::size_t filesize = is.tellg();
    is.seekg(0, std::ios_base::beg);
    filedata.resize(filesize);
    is.read((char*)&filedata[0], (std::streamsize)filesize);
    is.close();

    int global_offset = 0;
    header file;
    file.unk = get<uint32_t>(filedata.data(), global_offset);
    global_offset += sizeof(uint32_t);
    file.file_number = get<uint32_t>(filedata.data(), global_offset);
    global_offset += sizeof(uint32_t);
    std::vector<entry_header> files;
    for (uint32_t i = 0; i < file.file_number; i++) {
        entry_header f;
        memcpy(&f.file_name, filedata.data() + global_offset, 64);
        global_offset += 64;
        f.size = get<uint32_t>(filedata.data(), global_offset);
        global_offset += sizeof(uint32_t);
        f.offset = get<uint32_t>(filedata.data(), global_offset);
        global_offset += sizeof(uint32_t);
        files.push_back(f);
    }
    printf("FOUND %d FILES\n", file.file_number);
    mkdir("out", 0777);
    for (int i = 0; i < files.size(); i++) {
        char buffer[256];
        memset(buffer, 0, 256);
        sprintf(buffer, ".\\out\\%s", files[i].file_name);
        FILE* f = fopen(buffer, "wb");
        if (f != NULL) {
            fwrite((char*)filedata.data() + files[i].offset, files[i].size, 1, f);
            fclose(f);
            printf("EXTRACTED %s\n", files[i].file_name);
        }
    }
    return 0;
}

Here in my computer it was working, so I can only guess its the locale problem, so you computer wasn't being able to create the files that contained Japanese characters and the program crashed silently. So I added a line to help with that, but just to be sure, before running it try to set the LANG environment variable to ja_JA.utf8. Like export LANG=ja_JA.utf8 && ./program.
That seemed to do the trick! Thanks! What would be the best way to repack it?
 

Site & Scene News

Popular threads in this forum

General chit-chat
Help Users
    Psionic Roshambo @ Psionic Roshambo: https://www.youtube.com/watch?v=bgCjp3-rF_Y