Unpacking/Extracting .arc files

Zhongtiao1

Well-Known Member
OP
Member
Joined
Feb 24, 2015
Messages
831
Trophies
0
Age
26
XP
2,807
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
24
XP
1,423
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,807
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,807
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,807
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,807
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,807
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,807
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,807
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
  • BakerMan @ BakerMan:
    first of all, i don't watch those kinds of videos, and if i did i'd pull a verbalase 50k but with wizards and wario
    +1
  • BakerMan @ BakerMan:
    second of all, i don't even have a 3ds
  • BigOnYa @ BigOnYa:
    OnlyWizard&WarioFans.com
    +2
  • BakerMan @ BakerMan:
    i just want a wizard to stick his wand (whether literal or figurative is up to interpretation, either way it's either freaky or sus, or both i guess) up my ass
  • BigOnYa @ BigOnYa:
    I'm making Texas sheet cake for first time today, my Nieghbor brought us some few weeks ago and damn that's good, so I got her recipe and gonna try it today.
  • BakerMan @ BakerMan:
    mmm, sounds good
  • BigOnYa @ BigOnYa:
    Its not a brownie, and its not a cake, so what is it- Texas sheet cake.
  • BigOnYa @ BigOnYa:
    I tried making chocolate lava cakes the other day in cupcake pan, what a mess, my lava exploded out of the cakes everywhere while baking, was still ok tho, just no lava inside.
  • BigOnYa @ BigOnYa:
    We had our grandkids over yesterday and I got a small above ground swimming pool I filled for them to play in. Well today I woke to find 3 ducks swimming around in it. Don't mind really but they are annoyingly loud, quack quack. Gotta drain it today. Guess what were having for dinner, lol.
    +1
  • BakerMan @ BakerMan:
    lol
  • AncientBoi @ AncientBoi:
    BBQ'd 🦆
    +1
  • BakerMan @ BakerMan:
    also i'm sorry your molten lava cakes failed
    +2
  • BakerMan @ BakerMan:
    just looked up a pic of texas sheet cake, and it looks delicious
    +1
  • AncientBoi @ AncientBoi:
    🌋 Science Project?
  • BakerMan @ BakerMan:
    i think i might need to try making lava cakes for the 4th of july fr
    +2
  • BigOnYa @ BigOnYa:
    I used butter instead of vegetable oil, and think that's why they squirted out during baking, who knows
  • BakerMan @ BakerMan:
    yeah i think oil is the right call
    +1
  • BakerMan @ BakerMan:
    plus if you're making brownies or lava cakes for people with dairy allergies, you should use oil instead of butter anyway
    +2
  • ZeroT21 @ ZeroT21:
    @BakerMan Make me a space cake plz
  • BigOnYa @ BigOnYa:
    I make rum cake for 4th July every year, I make it a week prior and then soak it in rum in the fridge all week. I flip the cake each day, and add little more rum, it soaks it up everyday, so good.
    +2
  • BakerMan @ BakerMan:
    sorry, idk what you mean by a space cake, and even if i did, i'm not really taking requests right now, because otherwise people will get mad at me for taking a request but not making a birthday cake for @Xdqwerty (i'm sorry for that btw bro)
  • ZeroT21 @ ZeroT21:
    @BakerMan lies, you just want to smoke it

    :rofl2:
  • ZeroT21 @ ZeroT21:
    Guess all the food in my fridge can knock out a cow or two
    ZeroT21 @ ZeroT21: Guess all the food in my fridge can knock out a cow or two