PSX Compressão de Texto e Gráfico Final Fantasy VII e VIII

Iniciado por MachineMX, Março 05, 2017, 03:23:19 AM

tópico anterior - próximo tópico

0 Membros e 2 Visitantes estão vendo este tópico.

MachineMX

E ai pessoal blz


Eu me empenho e passo a maior parte do tempo estudando compressão de dados e estruturas de dado em linguagem C e C#, resolvo problemas que vai de simples até o intermediário com ajuda do youtube.
Consegui enfim entende a compressão em pares do LZ77, que começa com um tamanho vazio e um buffer com um tamanho de 4 bits, logo que encontra um caracter novo ele adiciona 0,0 ou quando é feito a busca um caractere ou dois caractere repetido ele volta, e avança quando o mesmo é encontrado.
Mas a minha pergunta é preciso saber que tipo de compressão de texto e gráfico o Final Fantasy VII e VIII usa.r.
Abs

tvtoon

#1
FF7 usa uma variante da compressão encontrada na ZLib, gzip e afins: método "deflate". É uma mistura desse LZ com Huffman. Existem várias ferramentas que podem lhe auxiliar no entendimento.

FF8 e FF9, alguém que trabalhou pode lhe dizer com mais detalhes, mas até onde eu saiba, não usa compressão nos textos.

Os gráficos não estão comprimidos, exceto os filmes que usam uma variação muito cabulosa do STR, formato próprio do PlayStation, contendo compressão.

***
Correção: de acordo com essa ferramenta, FF8 usa a mesma compressão.

MachineMX

#2
Valeu tvtoon

Eu vou da uma olhada os arquivos são feitos no python é uma ferramenta com recurso completo pra tradução de texto é interessante também porque o FFVII trabalha com mais uma tabela de caracteres, vou estuda a pasta de arquivos e ve o que eu posso fazer, se não me engano ele roda em sistema em msdos pelo que eu li.
Quero cria um dumper pro FFVII, já as outras não existe compressão de texto não é.
Pra começa minha primeira ferramenta dumper pra PSX.
Preciso de informações como New Line, Space, Tabs, informações do dicionário de compressão.
x
Será preciso programar as tabelas de caracteres ASCII pra extrair os textos?

kuroi

#3
Bom dia, cara.

Não sei se isso pode te ajudar em seus estudos, mas quando eu estava estudando pra criar o meu próprio Dumper/Inserter do jogo Crystalis de GBC, eu também me deparei com um problema semelhante ao seu.
A Rom na qual eu estava trabalhando não possuía nenhuma compressão gráfica e nem tão pouco textual, porém havia um pequeno problema na hora de extrair os textos: O jogo não se utilizava da tabela de caracteres ASCII, padronizada para o mercado americano...

Ora, mas se eu utilizasse um editor hexadecimal comum, com certeza eu teria sucesso em recriar a sua tabela fazendo uma busca relativa na rom, certo? Certo. Porém esse não era o meu objetivo. Na verdade eu queria desenvolver um Algoritmo que fosse capaz de transcodificar o conteúdo textual do jogo que estava na tabela "X" para a tabela ANSI (para que pudesse visualizar também caracteres acentuados).

Com a ajuda de um Software de busca relativa (Monkey Moore, o melhor que já usei!), eu consegui descobrir algum conteúdo de dentro da rom e com isso cheguei à seguinte tabela:

A=00
B=01
C=02
D=03
...
e etc.

Obs: Os valores estão em hexadecimal.

Já a famosa tabela ASCII possui os seguintes valores:

A=41
B=42
C=43
D=44
...
e etc.


Certo. Agora, se eu conseguisse carregar a tabela do jogo Crystalis no meu Editor Hexadecimal obviamente eu teria todos os caracteres da rom visíveis para edição. Mas como eu já havia mencionado, este não era o meu objetivo.

Em busca de algum Algoritmo que pudesse me ajudar a Dumpar textos de uma rom, eu encontrei lá no Po.BR.e o Script do Hyllian, que traduziu o Castlevania Order of Eclesia do Nintendo DS

http://romhackers.org/modules/PDdownloads2/singlefile.php?cid=9&lid=236

Como o seu código estava em C (linguagem que eu relativamente conheço), ficou mais fácil de entender a lógica que ele utilizou para que o seu código Dumper pudesse trabalhar com uma tabela não padronizada.

O script do Hillian foi o seguinte (nota: Vou utilizar a tabela que eu criei no Crystalis como exemplo):

   //primeiro há a declaração de um Array de caracteres logo no início do programa

       unsigned char tabela[256] = "ABCDEFGHIJKLMNOP"
                    "QRSTUVWXYZabcdef"
                    "ghijklmnopqrstuv"         
                    "wxyz0123456789ÂÃ"
                    "ÄÇÈÉÊËÎÏÔÖÛÜ-ÓÙÚ"         
                    "àáâãçèé%úêõîïôíñ"         
                    "óöûü@.,!?;: @@@'"         
                    "@@@@@@$@@@@@@@@@"         
                "@@@@@@@@@@@@@@@@"         
                "@@@@@@@@@@@@@@@@"         
                "@@@@@@@@@@@@@@@@"         
                "@@@@@@@@@@@@@@@@"
                "@@@@@@@@@@@@@@@@"
                "@@@@@@@@@@@@@@@@"
                "@@@@@@@@@@@@@@@@"
                "@@@@@@@@@@@@@@@*";
      

O Array acima possui 256 posições. Aí você me pergunta "Por que tudo isso?" Ora, simplesmente porque como queremos converter todas as 256 posições da tabela do jogo para uma tabela proveniente da tabela ASCII , temos que fazer comparações com dois Arrays identicos em tamanho, caso contrário iríamos nos deparar com problemas de "Array Out of Bounds".

Com relação à posição dos caracteres, repare que na minha tabela, os primeiros caracteres já são "A,B,C,D... e etc..."
Isso se dá pelo fato de que o compilador entenderá que a posição 0x00 do vetor "tabela" é igual a A, a posição 0x01 do vetor "tabela" é igual a B e assim sucessivamente até que a tabela atinja 256 (de 0 a 255) caracteres ou 0xFF.

Assim sendo, o código do Hyllian continua executando as rotinas que ele mesmo arquitetou até que chegamos às seguintes linhas:

if(tabela[memoria] != '@'){

          fprintf(arquivo_saida, "%c", tabela[memoria]);
}

O código acima verifica primeiro se o caractere que foi lido binariamente pelo programa, tem um correspondente em minha tabela, além de verificar se ele é diferente de @. Se essa afirmação for falsa, o programa não irá gravar nada no arquivo de saída, pois o caractere @ só serve para preencher espaços não utilizados na tabela.
Porém, se a afirmação for verdadeira, quer dizer que o caractere lido binariamente possui sim um correspondente em minha tabela.
Agora só falta gravar o valor correspondente deste caractere em meu arquivo de saída (o Dump). Ou seja, é isso que a próxima linha de instrução representa: A gravação do valor encontrado, correspondente à minha tabela. Ou melhor dizendo, o caractere já tratado e convertido para ANSI.

Espero ter ajudado e qualquer dúvida estamos aí!

Até mais!!
君の夢が叶うのは誰かの影じゃないぜ。
風の強い日を選んで走ってきた。

MachineMX

#4
Valeu Kuroi

:parabens:

É exatamente o que eu quero desenvolve um dumper e inserter, uma vez eu baixei no repositorio uns arquivos que era do breath of fire 2 ele tinha muitos arquivos sobre esse assunto feito em linguagem C, mas acontece que sumiu no meio da bagunça, dentro dele o romhacker fazia o uso da programação da tabela em arquivo C pra dumpear blocos de dialogos, por isso eu perguntei se é necessário programa tbls.

Eu vou adapta o Castlevania Order of Eclesia com o Final Fantasy VII, preciso saber se preciso especifica o dado binario o (BIN) na sáida file ou não é necessário?

No exemplo vc citou o exemplo do vetor de 256 caracteres.

    unsigned char tabela[256] = " !\"#$%&`()@+,-./"
                                "0123456789:;<=>?"
                                "@ABCDEFGHIJKLMNO"
                                "PQRSTUVWXYZ[\\]^_"
                                "@abcdefghijklmno"
                                "pqrstuvwxyz{|}~@"
                                "@@@@@@@@@@@ÀÁÂÃ@"
                                "@@Ç@ÉÊ@@Í@@@Ñ@ÓÔ"
                                "Õ@@@@Ú@Ü@@àáâã@@"
                                "@ç@éê@@í@@@ñ@óôõ"
                                "@@@@ú@ü@@@@@@@@'"
                                "@@@@@@@@@@@@@@@@"
                                "@@@@@@@@@@@@@@@@"
                                "@@@@@@@@@@@@@@@@"
                                "@@@@@@*@@@@@@@@@"
                                "@@@@@@@@@@@@@@@@";


tvtoon

Se você tá querendo fazer o "dumper" em C, não usar a abertura em modo binário é procurar problemas. Modo texto é muito insensato porque não é padronizado, não é como no C++ recente que você tem um aparato unicode e por aí vai...

Não tem muito problema, até porque é só considerar as quebras de linha como você quiser, só não é bom misturar a especificação ('\n' com 0xA).

Já sobre as tabelas, é só ver o exemplo aí e modificar os caracteres de acordo, a condicional será os controladores, que usarão mais de 1 byte.

Ondinha

A minha franca opinião, é melhor gastar um tempo para criar um módulo para carregar as tabelas de arquivos *.tbl (tabelas), o tempo que você perde na primeira vez, você economiza em futuros projetos. Creio que em C, o ideal seria fazer com liked lists, seria uma idéia.

kuroi

Bom dia, cara!

Sim. No caso do Dumper, você deve abrir o arquivo em modo Binário utilizando-se dos parâmetros "rb" dentro da função fopen() para que o compilador entenda que o arquivo que está sendo lido ("r" de read) está em modo binário ("b" de binary).
Mas como obviamente estamos trabalhando com dois arquivos (ainda no caso do Dumper), temos também que declarar um segundo arquivo que deverá ser aberto em modo texto para que nele seja armazenado o resultado do Dumper. Porém, em C, quando realizamos a abertura de um arquivo em modo texto, não é necessário especificar o parâmetro "wt" (write text), pois por padrão a linguagem já determina que qualquer abertura de arquivo está em modo texto. Assim sendo, a abertura do arquivo de texto só carrega o parâmetro "w" (write).

Exemplos:

FILE *arquivo_dump;
FILE *arquivo_saida;


     arquivo_dump = fopen("Binário_orig.bin", "rb");

     arquivo_saida = fopen("Dump.txt", "w");

Agora, no caso do Inserter, temos que nos atentar para algumas coisas antes
declarar a abertura dos arquivos...

Na prática, o Dumper é o programa que lerá o arquivo binário e gravará só seu conteúdo textual dentro de um txt. E o Inserter é o programa que lerá arquivo txt e gravará todo seu conteúdo no arquivo binário.
Tendo isso em mente, sabemos que ao declarar a abertura dos arquivos no Inserter, temos que abrí-los da seguinte forma:

FILE *entrada_txt, *saida_bin;

     entrada_txt = fopen("Script.txt", "r");

     saida_bin = fopen("rom.bin", "r+b");

Repare que na abertura do arquivo de entrada é especificado só o "r" no segundo parâmetro da função fopen, mostrando que trata-se de uma leitura de um arquivo no modo texto.
Já o arquivo binário recebe o prefixo "r+b" dentro do segundo parâmetro,
mostrando que este arquivo será aberto para "leitura E escrita" em modo
binário.
Mas como são só dois tipos principais de dados que podem ser lidos ou
gravados em tratamento de arquivos em C (Texto ou Binário), o resto é só
entender como funcionam os parâmetros da função fopen().

Vou deixar aqui o código da minha função Dumper com cada linha comentada para ficar mais didático o seu entendimento.

void Dumper(char* nome, unsigned int OffsetInicioTexto, unsigned int OffsetFinalTexto){
    FILE *arquivo;
    FILE *arquivo_saida;
    unsigned char *memoria;
    int cont = 0;
    int l;
    unsigned int k=0;
    unsigned int n=0;
    unsigned int OffsetInicioPonteiros;
    unsigned int OffsetFinalPonteiros;
    unsigned int h;
    unsigned int i, j;

    unsigned char tabela[256] = "ABCDEFGHIJKLMNOP"
    "QRSTUVWXYZabcdef"
    "ghijklmnopqrstuv"
    "wxyz0123456789ÂÃ"
    "ÄÇÈÉÊËÎÏÔÖÛÜ-ÓÙÚ"
    "àáâãçèé%úêõîïôíñ"
    "óöûü@.,!?;: @@@'"
    "@@@@@@$@@@@@@@@@"
"@@@@@@@@@@@@@@@@"
"@@@@@@@@@@@@@@@@"
"@@@@@@@@@@@@@@@@"
"@@@@@@@@@@@@@@@@"
"@@@@@@@@@@@@@@@@"
"@@@@@@@@@@@@@@@@"
"@@@@@@@@@@@@@@@@"
"@@@@@@@@@@@@@@@*";
   
   
        //Abre o arquivo de leitura   
        arquivo = fopen("Crystalis (U) [C][!].gbc", "rb");
       
        //Inicia o ponteiro do arquivo no começo
        fseek (arquivo, OFFSET, SEEK_SET);
       
        //Aloca em memória um unsigned char de tamanho 2097152 em decimal
        memoria = (unsigned char*) malloc (sizeof(unsigned char)*TAM_ARQ);
       
        //Lê o arquivo de entrada e grava todos os seus bytes na memória, retornando um contador, ou seja, dando um Malloc na bagaça
        cont = fread (memoria, sizeof(unsigned char), TAM_ARQ, arquivo);
       
       
        //Depois que toda a rom estiver na memoria, o arquivo é fechado
        fclose(arquivo);
       
        //Abre o arquivo de saída
        arquivo_saida = fopen(nome, "wt");

   
            //Inicio do loop que irá percorrer todo o bloco de diálogos a fim de gravar seus dados no arquivo de saída
            for(i=OffsetInicioTexto;i<=OffsetFinalTexto;i++){

   
   
            //Verificação se houve uma quebra de linha
            if(memoria[i] == 0xf1){
            //Gravação do dado hexadecimal F1 dentro de parênteses no arquivo de saída
            fprintf(arquivo_saida, "{%.2x}\n", memoria[i]);
}

//Verificação se houve um fim de diálogo
else if(memoria[i] == 0xf8){
//Gravação do dado hexadecimal F8 dentro de parênteses no arquivo de saída
fprintf(arquivo_saida, "{%.2x}\n------------------\n", memoria[i]);
}


//Se não houver mais nenhum caractere de controle, inserir o caractere coincidindo-o com a tabela de caracteres previamente criada
else{
//Se o caractere lido for igual a @
if(tabela[memoria[i]] != '@'){

//O programa grava o caractere no arquivo de saída coincidindo-o com o valor na tabela
fprintf(arquivo_saida, "%c", tabela[memoria[i]]);
}
               
            }



            }
       
            //Fechamento do arquivo de saída
            fclose(arquivo_saida);
           
           
            //Liberação da memória utilizada
            free(memoria);           
           
           
           
           
           
}



Obs: Ondinha, eu fui procurar saber como poderia desenvolver esse módulo em C
para tratar dos arquivos .tbl que você comentou, e vi que isso na verdade se
trata daquelas famosas "Listas Encadeadas por Ponteiros", né? Se for o mesmo
assunto (que exige um type def struct e tudo mais...) eu ainda não vejo como
poderia implementar isso em um gerenciador de arquivos .tbl... Vou começar a
estudar sobre isso e aí passo aqui no fórum pra comentar...


Até mais!!
君の夢が叶うのは誰かの影じゃないぜ。
風の強い日を選んで走ってきた。

Ondinha

Eu não sei como seria em C, porque nunca programei algo assim em C e, quando precisei, fiz hardcoded mesmo.

Em Python, existe uma estrutura de dados chamada Dicionário, que é basicamente uma linked list, então eu fiz um módulo, bem antigo, que lia o arquivo *.tbl e convertia em um dicionário para Python, além disso, uma galera que participava no fórum antigo criou o PyTable, que era um módulo bem poderoso que fazia isso também.

MachineMX

#9
Boa Tarde ao tvtoon, Macaco Ancião e Kuroi

tvtoon
Eu pretendo criar um dumper sem compressão pra começa com coisas mais faceis, consegui extrair blocos de textos, e fazer edição do texto no bloco de notas, preciso encontrar o dicionario de compressão, pra cada rom ou iso que for usa ex:
quebra de linha \n, ponto final
No editor binario eu especifico 0xA como quebra de linha, se esse valor for referente a quebra de linha, assim como referi o valor que move aquela seta pra baixo do breath of fire II,indicando mais texto abaixo

Macaco Ancião
Eu já vi em alguns arquivos esse modulo, mas não achava que era pra ser usado em tabelas, sobre as listas encadeadas eu vou da uma olhada. Agora que vi chama PyTable, quando usa essa tabela tenho que especifica com o comando import from?

Kuroi
Valeu Kuroi,
É isso que me causava dúvida, porque nas roms  gente especifica a saída ex: Super Mario World.smc adicionando smc ou sfc se for rom japonesa.
A função FILE especifica a entrada e o destino do arquivo, vou esperimenta agora.
Sobre  abertura de um arquivo em modo texto consegui entender finalmente agora, antes não fazia muito sentido pra mim b no final de r.

arquivo_dump = fopen("Binário_orig.bin", "rb");
significa lê través do arquivo binário.

arquivo_saida = fopen("Dump.txt", "w");
significa grava o arquivo dump.txt



    if (argc != 2)
    {
        printf ("Usage: %s <file_to_dump>\n", argv[0]);
        exit (0);
    }


    if ((arq = fopen (argv[1],"rb")) == NULL)
    {
        printf ("Erro na abertura do arquivo!\n");
        exit (0);       
    }

    if ((out = fopen ("script.txt","w")) == NULL)
    {
        printf ("Erro na abertura do arquivo!\n");
        exit (0);       
    }