[SAT] Akumajou Dracula X: Gekka no Yasoukyoku

Iniciado por Mistura_De_Bits, Maio 16, 2021, 18:47:05 PM

tópico anterior - próximo tópico

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

Mistura_De_Bits





Nome Original: Akumajou Dracula X: Gekka no Yasoukyoku
Nome Traduzido: Castlevania Dracula X: Noturno ao Luar
Plataforma: Sega Saturn
Gênero:  Metroidvania, RPG eletrônico de ação, Jogo eletrônico de ação e aventura
Data de lançamento inicial: 25 de Junho de 1998
Série: Castlevania
Estúdio: KCEN
Desenvolvedores: KONAMI
Tradutor: Rafael Silva


Tradução feita com base no patch "Ultimate" do grupo "Medusa Team".

Andamento:
Textos: 100%
Menus e itens: 100%
Gráficos: 100%


Notas de Atualização:
Tradução em testes
Usei o patch "Ultimate" como base para a tradução, corrigi vários erros do mesmo, exemplo: textos fora das caixas transparentes, HP no meio da imagem dos monstros no livro de bestas.


Preciso que testem tudo com e sem o cartucho 4M
Download do patch:
https://drive.google.com/file/d/1JjI3eSdCByBCKe8yXjWvED0HYmpidegs/view?usp=sharing


O que ainda falta fazer...
A única coisa que me incomoda, se alguém conseguir ajudar é mudar a posição da quantidade de itens:



Imagens:





ajkmetiuk

a versão do saturn n sabia que tinha projeto, pessoal anda fazendo projetos secretos ai rsrs, mas muito legal hein? entra lá no chat rhbr pra conversar com o pessoal, tem vários que entendem de compressão lzss (não sei se do saturn estão familiarizados) mas deve ser bem parecido.
supremex!

Mistura_De_Bits

Citação de: ajkmetiuk online Maio 16, 2021, 23:32:20 PM
a versão do saturn n sabia que tinha projeto, pessoal anda fazendo projetos secretos ai rsrs, mas muito legal hein? entra lá no chat rhbr pra conversar com o pessoal, tem vários que entendem de compressão lzss (não sei se do saturn estão familiarizados) mas deve ser bem parecido.
Eu coloquei o código do meu discord lá e apareceu aguardando aprovação do bot, mas não aconteceu nada no discord e eu não uso muito ele, então não entendo quase nada.

Anime_World

#3
Provavelmente é uma variação das LZKN encontradas no Mega Drive.
Tentei ler sua explicação, mas talvez por falta de conhecimento seu na área, não faz sentido algum da forma como você colocou.

Vou tentar facilitar pra você explicar melhor a compressão:
1 - LZ trabalha com um par de BYTE, comumente chamados de WORD, ou 16 bits.
2 - O tamanho da cadeia é definido nesse par em bits, sendo X bits pro tamanho e Y bits pro offset no buffer.

Ou seja se você tem a word $FF00 por exemplo:

1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0
\________/\___________________/
tamanho         offset


O tamanho do MÁXIMO DA JANELA LZ pelo que você indicou é 0x800
Agora resta só você indicar quantos bits são para o tamanho e quantos bits são para o offset.
nonononono

Mistura_De_Bits

Citação de: Anime_World online Maio 17, 2021, 05:02:54 AM
Se não descomprimir com as PRS Tools, provavelmente é uma variação das LZKN encontradas no Mega Drive.
Tentei ler sua explicação, mas talvez por falta de conhecimento seu na área, não faz sentido algum da forma como você colocou.

Vou tentar facilitar pra você explicar melhor a compressão:
1 - LZ trabalha com um par de BYTE, comumente chamados de WORD, ou 16 bits.
2 - O tamanho da cadeia é definido nesse par em bits, sendo X bits pro tamanho e Y bits pro offset no buffer.

Ou seja se você tem a word $FF00 por exemplo:

1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0
\________/\___________________/
tamanho         offset


O tamanho do MÁXIMO DA JANELA LZ pelo que você indicou é 0x800
Agora resta só você indicar quantos bits são para o tamanho e quantos bits são para o offset.
Desculpa eu quis simplificar já que é parecido com lzss.
vou colocar a informação completa hoje a noite, mas o esquema é diferente desse citado, o índice é 1 byte seguido de 8 valores que podem ser 1 ou 2 bytes cada valor, enfim mais tarde coloco o arquivo com a informação completa da compressão, sou leigo em programação mas dá pra explicar tudo sim já que eu consigo manipular manualmente.

Anime_World

#5
Citação de: Mistura_De_Bits online Maio 17, 2021, 17:30:09 PM
Desculpa eu quis simplificar já que é parecido com lzss.
vou colocar a informação completa hoje a noite, mas o esquema é diferente desse citado, o índice é 1 byte seguido de 8 valores que podem ser 1 ou 2 bytes cada valor, enfim mais tarde coloco o arquivo com a informação completa da compressão, sou leigo em programação mas dá pra explicar tudo sim já que eu consigo manipular manualmente.

Conversando com o denim hoje, analisamos por cima, realmente é uma variação da LZKN3 que eu usei no Castlevania Bloodlines.
Portanto são 13 bits para o offset e 5 bits para o tamanho. O que me deixa preocupado agora é se tem as exceções para executar diferentes tarefas dentro da compressão.

Quanto a sua explicação, você explicou em digitos e não em bits. Por isso tem que fazer aquele calculo maluco pro 3 e 4 digito. Se usar em bits não precisa.
O calculo correto fica assim para o par LZ 0x7E36

01111110 00110110
\_______________/
    par LZ

01111110 001 10110
\__________/ \___/
  ponteiro  tamanho


os 3 ultimos bits do ponteiro por estarem
no segundo byte passam para o começo


001 01111110 = 0x17E


e o tamanho soma 3 que é o tamanho minímo da cadeia


10110 = 22
22+3 = 25


portanto,


ponteiro: 0x17E
tamanho: 25


pra calcular mais rapidamente pode usar essa formula:

w16 = 0x7e36
ponteiro = ((w16 &0xE0) << 3) | (w16 >> 8)
tamanho = (w16 &0x1F) + 3


funciona em qualquer linguagem, mas aqui tem uma calculadora em python:
https://www.online-python.com/K4TbnY52Dl
nonononono

Mistura_De_Bits

#6
Citação de: Anime_World online Maio 17, 2021, 18:35:29 PM
Citação de: Mistura_De_Bits online Maio 17, 2021, 17:30:09 PM
Desculpa eu quis simplificar já que é parecido com lzss.
vou colocar a informação completa hoje a noite, mas o esquema é diferente desse citado, o índice é 1 byte seguido de 8 valores que podem ser 1 ou 2 bytes cada valor, enfim mais tarde coloco o arquivo com a informação completa da compressão, sou leigo em programação mas dá pra explicar tudo sim já que eu consigo manipular manualmente.

Conversando com o denim hoje, analisamos por cima, realmente é uma variação da LZKN3 que eu usei no Castlevania Bloodlines.
Portanto são 13 bits para o offset e 5 bits para o tamanho. O que me deixa preocupado agora é se tem as exceções para executar diferentes tarefas dentro da compressão.

Quanto a sua explicação, você explicou em digitos e não em bits. Por isso tem que fazer aquele calculo maluco pro 3 e 4 digito. Se usar em bits não precisa.
O calculo correto fica assim para o par LZ 0x7E36

01111110 00110110
\_______________/
    par LZ

01111110 001 10110
\__________/ \___/
  ponteiro  tamanho


os 3 ultimos bits do ponteiro por estarem
no segundo byte passam para o começo


001 01111110 = 0x17E


e o tamanho soma 3 que é o tamanho minímo da cadeia


10110 = 22
22+3 = 25


portanto,


ponteiro: 0x17E
tamanho: 25


pra calcular mais rapidamente pode usar essa formula:

w16 = 0x7e36
ponteiro = ((w16 &0xE0) << 3) | (w16 >> 8)
tamanho = (w16 &0x1F) + 3


funciona em qualquer linguagem, mas aqui tem uma calculadora em python:
https://www.online-python.com/K4TbnY52Dl

Eu fiz uma explicação detalhada, pra começar eu escrevi errado, são só 0x400 de cache.

A compressão usa um cache de 0x400 mas ela começa a escrever
nesse cache no endereço 3DE.

Para começar temos um índice de 2 nibbles que é lido de trás pra frente em
binário.
No valor binário, 1 indica leitura e gravação direta de um byte
já o 0 indica que há uma compressão formada por 4 Nibbles

Exemplo:

61 00 DE 7F 00 1F 22 1F 3E 18 01 11 5E 08

61=0110 0001

Onde os bytes em parenteses são compressão.
00 (DE 7F) (00 1F) (22 1F) (3E 18) 01 11 (5E 08)

Como funciona a compressão

Neste mesmo exemplo começamos com o valor 00 que será escrito no endereço
inicial do cache "3DE", logo após vem um valor de compressão (DE 7F), onde
os 3 primeiros Nibbles são o endereço e os dois últimos são a quantidade
de bytes a ser lida.

O terceiro nibble "7" será jogado para o começo, ele deve ser
sempre um caractere par e deve ser dividido por 2, o que sobrar
é jogado para a quantidade de bytes a ser lido+3

Sendo assim:
(DE 7f)
(7 DE)(F+3) -> jogamos o 7 para o começo
(6DE) (1F+3) -> tiramos 1 do 7 e juntamos com o F+3
(3DE) (22) -> dividimos o 6 por 2 e somamos o 1F+3.


Sendo assim temos uma leitura de 34 bytes no endereço 0x3DE, porém como só
existe o valor 00 a partir deste endereço por enquanto, esse valor será
repetido até chegar a quantidade pedida.

Depois teremos mais duas escritas de 34 zeros e uma de 27 até chegar nos valores sem compressão "01 11", se tudo der certo esse "01 11" deve começar no endereço do cache 0x60.


O que eu preciso é de uma tool pra fazer isso sozinha.

Anime_World

#7
Citação de: Mistura_De_Bits online Maio 17, 2021, 19:47:37 PM
Eu fiz uma explicação detalhada, pra começar eu escrevi errado, são só 0x400 de cache.

A compressão usa um cache de 0x400 mas ela começa a escrever
nesse cache no endereço 3DE.

Para começar temos um índice de 2 nibbles que é lido de trás pra frente em
binário.
No valor binário, 1 indica leitura e gravação direta de um byte
já o 0 indica que há uma compressão formada por 4 Nibbles

Exemplo:

61 00 DE 7F 00 1F 22 1F 3E 18 01 11 5E 08

61=0110 0001

Onde os bytes em parenteses são compressão.
00 (DE 7F) (00 1F) (22 1F) (3E 18) 01 11 (5E 08)

Como funciona a compressão

Neste mesmo exemplo começamos com o valor 00 que será escrito no endereço
inicial do cache "3DE", logo após vem um valor de compressão (DE 7F), onde
os 3 primeiros Nibbles são o endereço e os dois últimos são a quantidade
de bytes a ser lida.

O terceiro nibble "7" será jogado para o começo, ele deve ser
sempre um caractere par e deve ser dividido por 2, o que sobrar
é jogado para a quantidade de bytes a ser lido+3

Sendo assim:
(DE 7f)
(7 DE)(F+3) -> jogamos o 7 para o começo
(6DE) (1F+3) -> tiramos 1 do 7 e juntamos com o F+3
(3DE) (22) -> dividimos o 6 por 2 e somamos o 1F+3.


Sendo assim temos uma leitura de 34 bytes no endereço 0x3DE, porém como só
existe o valor 00 a partir deste endereço por enquanto, esse valor será
repetido até chegar a quantidade pedida.

Depois teremos mais duas escritas de 34 zeros e uma de 27 até chegar nos valores sem compressão "01 11", se tudo der certo esse "01 11" deve começar no endereço do cache 0x60.


O que eu preciso é de uma tool pra fazer isso sozinha.

Continua errada sua explicação. O indíce ao qual você se refere é lido em bits e não em nibbles, sendo cada bit uma flag que indica se há ou não compressão no byte. E como já mencionei o par LZ não é lido em nibbles e sim em bits, por ler em nibbles você precisou criar aquela tabela de correção.

Quanto ao cache da janela é o mesmo da LZKN3 bem como o offset de início da janela.

Suspeito ainda que haja um controle para diferentes modos de escrita da janela, na LZKN1 e LZKN3 são 4 modos: ponteiro, ponteiro relativo ao final da janela, copia de x bytes direta e byte sem compressão.
Como é de costume a produtora reaproveitar rotinas, dúvido que fuja à regra.

Outro detalhe, não sei se você cortou o arquivo mas é comum ter 2 bytes (1 word) com o tamanho do arquivo decomprimido antes do 0x61


------


Bom, o programa está pronto... supondo que existam os 2 bytes iniciais com o tamanho no inicio do arquivo.


import os
import sys
import io
import struct
import argparse
import textwrap
from os import SEEK_SET, SEEK_CUR, SEEK_END

class RingBuffer:
    MAX_WINDOW_SIZE = 0
    CURSOR = 0

    def __init__(self, max_window_size=0x1024, start_offset=0, fill_byte=0x0):
        self.MAX_WINDOW_SIZE = max_window_size
        self.CURSOR = start_offset
        self._buffer = bytearray([fill_byte]*self.MAX_WINDOW_SIZE)

    def append(self, byte):
        self._buffer[self.CURSOR] = byte
        self.CURSOR=(self.CURSOR+1)%self.MAX_WINDOW_SIZE

    def get(self, offset):
        return self._buffer[offset%self.MAX_WINDOW_SIZE]

class ROM(io.BytesIO):
    """
        Class to manipulate generic ROM files
    """

    CURSOR = 0
    SIZE = 0

    def __init__(self, filename):
        try:
            self.raw = open(filename, 'rb').read()
        except FileNotFoundError:
            print('[ERROR] Unable to found file')
            exit(0)
        super(ROM, self).__init__(self.raw)
        self.SIZE = len(self.raw)

    def read_8(self):
        self.seek(self.CURSOR, SEEK_SET)
        readed = struct.unpack('>B',self.read(1))[0]
        self.CURSOR+=1
        return readed

    def read_16(self):
        self.seek(self.CURSOR, SEEK_SET)
        readed = struct.unpack('>H',self.read(2))[0]
        self.CURSOR+=2
        return readed

    def read_32(self):
        self.seek(self.CURSOR, SEEK_SET)
        readed = struct.unpack('>I',self.read(4))[0]
        self.CURSOR+=4
        return readed

    def set_offset(self, offset=0):
        self.CURSOR=offset
        self.seek(self.CURSOR, SEEK_SET)

    def get_offset(self):
        return self.tell()


class LZKN4:
   
    _buffer = bytearray()

    def __init__(self, input_data):
        self.DATA = input_data

    def append(self, value):
        self._buffer.append(value)
        self._window.append(value)
        return 1

    def append_from_data(self, length=0):
        count=0
        for i in range(length):
            tmp = self.DATA.read_8()
            count += self.append(tmp)
        return count

    def append_from_window(self, length=0, offset=0):
        count = 0
        for i in range(length):
            tmp = self._window.get(offset+i)
            count += self.append(tmp)
        return count

    def decompress(self, offset=0):
        self._window = RingBuffer(0x400, 0x3DE)
        self._buffer = bytearray()
        uncompressed_size = self.DATA.read_16()
        _decoded = 0
        while (_decoded < uncompressed_size):
            control = self.DATA.read_8()
            for readed_bits in range(8):
                bit = bool((control >> readed_bits) &0x1)
                if not bit:
                    _readed = self.DATA.read_16()
                    length = (_readed &0x1F) + 3
                    offset = ((_readed &0xE0) << 3) | (_readed >> 8)
                    _decoded += self.append_from_window(length, offset)
                else:
                    _readed = self.DATA.read_8()
                    _decoded += self.append(_readed)
        return self._buffer

class Application(argparse.ArgumentParser):
    title = '''
        [Saturn] Castlevania SOTN - Decompressor
        ---------------------------------------------------------
        Tool to decompress graphics from:
            [SATURN] Castlevania - Symphony of the Night (US)
            [SATURN] Akumajou Dracula X: Gekka no Yasoukyoku (JP)
    '''

    argument_list = [
        {
            'name'      : 'input',
            'nargs'     : '?',
            'type'      : argparse.FileType('rb'),     
            'default'   : sys.stdin,
            'help'      : 'Compressed File (.bin)'
        }
    ]

    def __init__(self):
        super(Application, self).__init__()
        self.formatter_class=argparse.RawDescriptionHelpFormatter
        self.description=textwrap.dedent(self.title)

        for argument in self.argument_list:
                self.add_argument(
                argument['name'],
                nargs=argument['nargs'],
                type=argument['type'],
                default=argument['default'],
                help=argument['help']
            )       

        args = self.parse_args()
        if args.input.name != '<stdin>':
            binary = ROM(args.input.name)
            lzkn = LZKN4(binary)
            data = lzkn.decompress(binary)
            try:
                os.stat('output/')
            except:
                os.mkdir('output/')
            out = open("output/"+args.input.name, 'wb')
            out.write(data)
            out.close()
        else:
            self.print_help()

if __name__=="__main__":
    app = Application()
nonononono

Mistura_De_Bits

Citação de: Anime_World online Maio 17, 2021, 20:38:37 PM
Citação de: Mistura_De_Bits online Maio 17, 2021, 19:47:37 PM
Eu fiz uma explicação detalhada, pra começar eu escrevi errado, são só 0x400 de cache.

A compressão usa um cache de 0x400 mas ela começa a escrever
nesse cache no endereço 3DE.

Para começar temos um índice de 2 nibbles que é lido de trás pra frente em
binário.
No valor binário, 1 indica leitura e gravação direta de um byte
já o 0 indica que há uma compressão formada por 4 Nibbles

Exemplo:

61 00 DE 7F 00 1F 22 1F 3E 18 01 11 5E 08

61=0110 0001

Onde os bytes em parenteses são compressão.
00 (DE 7F) (00 1F) (22 1F) (3E 18) 01 11 (5E 08)

Como funciona a compressão

Neste mesmo exemplo começamos com o valor 00 que será escrito no endereço
inicial do cache "3DE", logo após vem um valor de compressão (DE 7F), onde
os 3 primeiros Nibbles são o endereço e os dois últimos são a quantidade
de bytes a ser lida.

O terceiro nibble "7" será jogado para o começo, ele deve ser
sempre um caractere par e deve ser dividido por 2, o que sobrar
é jogado para a quantidade de bytes a ser lido+3

Sendo assim:
(DE 7f)
(7 DE)(F+3) -> jogamos o 7 para o começo
(6DE) (1F+3) -> tiramos 1 do 7 e juntamos com o F+3
(3DE) (22) -> dividimos o 6 por 2 e somamos o 1F+3.


Sendo assim temos uma leitura de 34 bytes no endereço 0x3DE, porém como só
existe o valor 00 a partir deste endereço por enquanto, esse valor será
repetido até chegar a quantidade pedida.

Depois teremos mais duas escritas de 34 zeros e uma de 27 até chegar nos valores sem compressão "01 11", se tudo der certo esse "01 11" deve começar no endereço do cache 0x60.


O que eu preciso é de uma tool pra fazer isso sozinha.

Continua errada sua explicação. O indíce ao qual você se refere é lido em bits e não em nibbles, sendo cada bit uma flag que indica se há ou não compressão no byte. E como já mencionei o par LZ não é lido em nibbles e sim em bits, por ler em nibbles você precisou criar aquela tabela de correção.

Quanto ao cache da janela é o mesmo da LZKN3 bem como o offset de início da janela.

Suspeito ainda que haja um controle para diferentes modos de escrita da janela, na LZKN1 e LZKN3 são 4 modos: ponteiro, ponteiro relativo ao final da janela, copia de x bytes direta e byte sem compressão.
Como é de costume a produtora reaproveitar rotinas, dúvido que fuja à regra.

Outro detalhe, não sei se você cortou o arquivo mas é comum ter 2 bytes (1 word) com o tamanho do arquivo decomprimido antes do 0x61


------


Bom, o programa está pronto... supondo que existam os 2 bytes iniciais com o tamanho no inicio do arquivo.


import os
import sys
import io
import struct
import argparse
import textwrap
from os import SEEK_SET, SEEK_CUR, SEEK_END

class RingBuffer:
    MAX_WINDOW_SIZE = 0
    CURSOR = 0

    def __init__(self, max_window_size=0x1024, start_offset=0, fill_byte=0x0):
        self.MAX_WINDOW_SIZE = max_window_size
        self.CURSOR = start_offset
        self._buffer = bytearray([fill_byte]*self.MAX_WINDOW_SIZE)

    def append(self, byte):
        self._buffer[self.CURSOR] = byte
        self.CURSOR=(self.CURSOR+1)%self.MAX_WINDOW_SIZE

    def get(self, offset):
        return self._buffer[offset%self.MAX_WINDOW_SIZE]

class ROM(io.BytesIO):
    """
        Class to manipulate generic ROM files
    """

    CURSOR = 0
    SIZE = 0

    def __init__(self, filename):
        try:
            self.raw = open(filename, 'rb').read()
        except FileNotFoundError:
            print('[ERROR] Unable to found file')
            exit(0)
        super(ROM, self).__init__(self.raw)
        self.SIZE = len(self.raw)

    def read_8(self):
        self.seek(self.CURSOR, SEEK_SET)
        readed = struct.unpack('>B',self.read(1))[0]
        self.CURSOR+=1
        return readed

    def read_16(self):
        self.seek(self.CURSOR, SEEK_SET)
        readed = struct.unpack('>H',self.read(2))[0]
        self.CURSOR+=2
        return readed

    def read_32(self):
        self.seek(self.CURSOR, SEEK_SET)
        readed = struct.unpack('>I',self.read(4))[0]
        self.CURSOR+=4
        return readed

    def set_offset(self, offset=0):
        self.CURSOR=offset
        self.seek(self.CURSOR, SEEK_SET)

    def get_offset(self):
        return self.tell()


class LZKN4:
   
    _buffer = bytearray()

    def __init__(self, input_data):
        self.DATA = input_data

    def append(self, value):
        self._buffer.append(value)
        self._window.append(value)
        return 1

    def append_from_data(self, length=0):
        count=0
        for i in range(length):
            tmp = self.DATA.read_8()
            count += self.append(tmp)
        return count

    def append_from_window(self, length=0, offset=0):
        count = 0
        for i in range(length):
            tmp = self._window.get(offset+i)
            count += self.append(tmp)
        return count

    def decompress(self, offset=0):
        self._window = RingBuffer(0x400, 0x3DE)
        self._buffer = bytearray()
        uncompressed_size = self.DATA.read_16()
        _decoded = 0
        while (_decoded < uncompressed_size):
            control = self.DATA.read_8()
            for readed_bits in range(8):
                bit = bool((control >> readed_bits) &0x1)
                if not bit:
                    _readed = self.DATA.read_16()
                    length = (_readed &0x1F) + 3
                    offset = ((_readed &0xE0) << 3) | (_readed >> 8)
                    _decoded += self.append_from_window(length, offset)
                else:
                    _readed = self.DATA.read_8()
                    _decoded += self.append(_readed)
        return self._buffer

class Application(argparse.ArgumentParser):
    title = '''
        [Saturn] Castlevania SOTN - Decompressor
        ---------------------------------------------------------
        Tool to decompress graphics from:
            [SATURN] Castlevania - Symphony of the Night (US)
            [SATURN] Akumajou Dracula X: Gekka no Yasoukyoku (JP)
    '''

    argument_list = [
        {
            'name'      : 'input',
            'nargs'     : '?',
            'type'      : argparse.FileType('rb'),     
            'default'   : sys.stdin,
            'help'      : 'Compressed File (.bin)'
        }
    ]

    def __init__(self):
        super(Application, self).__init__()
        self.formatter_class=argparse.RawDescriptionHelpFormatter
        self.description=textwrap.dedent(self.title)

        for argument in self.argument_list:
                self.add_argument(
                argument['name'],
                nargs=argument['nargs'],
                type=argument['type'],
                default=argument['default'],
                help=argument['help']
            )       

        args = self.parse_args()
        if args.input.name != '<stdin>':
            binary = ROM(args.input.name)
            lzkn = LZKN4(binary)
            data = lzkn.decompress(binary)
            try:
                os.stat('output/')
            except:
                os.mkdir('output/')
            out = open("output/"+args.input.name, 'wb')
            out.write(data)
            out.close()
        else:
            self.print_help()

if __name__=="__main__":
    app = Application()


O tamanho compactado e extraído fica em outro local, não fica junto com o arquivo em si, obrigado pelo seu tempo e conhecimento, não entendo de programação, apesar de ter traduzido 99% do jogo eu uso programas de edição de imagem e editores hexa, encontro as programações pelo dump de memoria do yabause, eita emulador ruim, mas quebra um galho nessa parte.


Estou acompanhando a tradução pra inglês também, apesar de não ter curtido a fonte que ele usou pois só cabe os textos da versão americana, no meu método cabe os textos japoneses apesar de eu ter abreviado muitos com medo de não ter espaço no arquivo, agora já posso remover essas abreviações.

Anime_World

#9
Citação de: Mistura_De_Bits online Maio 17, 2021, 21:43:45 PM
O tamanho compactado e extraído fica em outro local, não fica junto com o arquivo em si, obrigado pelo seu tempo e conhecimento, não entendo de programação, apesar de ter traduzido 99% do jogo eu uso programas de edição de imagem e editores hexa, encontro as programações pelo dump de memoria do yabause, eita emulador ruim, mas quebra um galho nessa parte.


Estou acompanhando a tradução pra inglês também, apesar de não ter curtido a fonte que ele usou pois só cabe os textos da versão americana, no meu método cabe os textos japoneses apesar de eu ter abreviado muitos com medo de não ter espaço no arquivo, agora já posso remover essas abreviações.


import os
import sys
import io
import struct
import argparse
import textwrap
from os import SEEK_SET, SEEK_CUR, SEEK_END

class RingBuffer:
    MAX_WINDOW_SIZE = 0
    CURSOR = 0

    def __init__(self, max_window_size=0x1024, start_offset=0, fill_byte=0x0):
        self.MAX_WINDOW_SIZE = max_window_size
        self.CURSOR = start_offset
        self._buffer = bytearray([fill_byte]*self.MAX_WINDOW_SIZE)

    def append(self, byte):
        self._buffer[self.CURSOR] = byte
        self.CURSOR=(self.CURSOR+1)%self.MAX_WINDOW_SIZE

    def get(self, offset):
        return self._buffer[offset%self.MAX_WINDOW_SIZE]

class ROM(io.BytesIO):
    """
        Class to manipulate generic ROM files
    """

    CURSOR = 0
    SIZE = 0

    def __init__(self, filename):
        try:
            self.raw = open(filename, 'rb').read()
        except FileNotFoundError:
            print('[ERROR] Unable to found file')
            exit(0)
        super(ROM, self).__init__(self.raw)
        self.SIZE = len(self.raw)

    def read_8(self):
        self.seek(self.CURSOR, SEEK_SET)
        readed = struct.unpack('>B',self.read(1))[0]
        self.CURSOR+=1
        return readed

    def read_16(self):
        self.seek(self.CURSOR, SEEK_SET)
        readed = struct.unpack('>H',self.read(2))[0]
        self.CURSOR+=2
        return readed

    def read_32(self):
        self.seek(self.CURSOR, SEEK_SET)
        readed = struct.unpack('>I',self.read(4))[0]
        self.CURSOR+=4
        return readed

    def set_offset(self, offset=0):
        self.CURSOR=offset
        self.seek(self.CURSOR, SEEK_SET)

    def get_offset(self):
        return self.tell()


class LZKN4:
   
    _buffer = bytearray()

    def __init__(self, input_data):
        self.DATA = input_data

    def append(self, value):
        self._buffer.append(value)
        self._window.append(value)
        return 1

    def append_from_data(self, length=0):
        count=0
        for i in range(length):
            tmp = self.DATA.read_8()
            count += self.append(tmp)
        return count

    def append_from_window(self, length=0, offset=0):
        count = 0
        for i in range(length):
            tmp = self._window.get(offset+i)
            count += self.append(tmp)
        return count

    def decompress(self, offset=0, size=0):
        self._window = RingBuffer(0x400, 0x3DE)
        self._buffer = bytearray()
        uncompressed_size = size
        _decoded = 0
        while (_decoded < uncompressed_size):
            control = self.DATA.read_8()
            for readed_bits in range(8):
                bit = bool((control >> readed_bits) &0x1)
                if not bit:
                    _readed = self.DATA.read_16()
                    length = (_readed &0x1F) + 3
                    offset = ((_readed &0xE0) << 3) | (_readed >> 8)
                    _decoded += self.append_from_window(length, offset)
                else:
                    _readed = self.DATA.read_8()
                    _decoded += self.append(_readed)
        return self._buffer

class Application(argparse.ArgumentParser):
    title = '''
        [Saturn] Castlevania SOTN - Decompressor
        ---------------------------------------------------------
        Tool to decompress graphics from:
            [SATURN] Castlevania - Symphony of the Night (US)
            [SATURN] Akumajou Dracula X: Gekka no Yasoukyoku (JP)
    '''

    argument_list = [
        {
            'name'      : 'input',
            'nargs'     : '?',
            'type'      : argparse.FileType('rb'),     
            'default'   : sys.stdin,
            'help'      : 'Compressed File (.bin)'
        },
        {
            'name'      : 'uncompressed_size',
            'nargs'     : '?',
            'type'      : lambda x: int(x, 0),   
            'default'   : 0,
            'help'      : 'Uncompressed Size'
        }
    ]

    def __init__(self):
        super(Application, self).__init__()
        self.formatter_class=argparse.RawDescriptionHelpFormatter
        self.description=textwrap.dedent(self.title)

        for argument in self.argument_list:
                self.add_argument(
                argument['name'],
                nargs=argument['nargs'],
                type=argument['type'],
                default=argument['default'],
                help=argument['help']
            )       

        args = self.parse_args()
        if args.input.name != '<stdin>':
            binary = ROM(args.input.name)
            lzkn = LZKN4(binary)
            data = lzkn.decompress(binary, args.uncompressed_size)
            try:
                os.stat('output/')
            except:
                os.mkdir('output/')
            out = open("output/"+args.input.name, 'wb')
            out.write(data)
            out.close()
        else:
            self.print_help()

if __name__=="__main__":
    app = Application()


Essa versão modifiquei pra passar o tamanho decomprimido na linha de comando
Salvar como decoder.py e rodar usando o Python 3
Comando:

python decoder.py arquivo.bin tamanho

nonononono

Mistura_De_Bits

Citação de: Anime_World online Maio 17, 2021, 21:48:58 PM
Citação de: Mistura_De_Bits online Maio 17, 2021, 21:43:45 PM
O tamanho compactado e extraído fica em outro local, não fica junto com o arquivo em si, obrigado pelo seu tempo e conhecimento, não entendo de programação, apesar de ter traduzido 99% do jogo eu uso programas de edição de imagem e editores hexa, encontro as programações pelo dump de memoria do yabause, eita emulador ruim, mas quebra um galho nessa parte.


Estou acompanhando a tradução pra inglês também, apesar de não ter curtido a fonte que ele usou pois só cabe os textos da versão americana, no meu método cabe os textos japoneses apesar de eu ter abreviado muitos com medo de não ter espaço no arquivo, agora já posso remover essas abreviações.


import os
import sys
import io
import struct
import argparse
import textwrap
from os import SEEK_SET, SEEK_CUR, SEEK_END

class RingBuffer:
    MAX_WINDOW_SIZE = 0
    CURSOR = 0

    def __init__(self, max_window_size=0x1024, start_offset=0, fill_byte=0x0):
        self.MAX_WINDOW_SIZE = max_window_size
        self.CURSOR = start_offset
        self._buffer = bytearray([fill_byte]*self.MAX_WINDOW_SIZE)

    def append(self, byte):
        self._buffer[self.CURSOR] = byte
        self.CURSOR=(self.CURSOR+1)%self.MAX_WINDOW_SIZE

    def get(self, offset):
        return self._buffer[offset%self.MAX_WINDOW_SIZE]

class ROM(io.BytesIO):
    """
        Class to manipulate generic ROM files
    """

    CURSOR = 0
    SIZE = 0

    def __init__(self, filename):
        try:
            self.raw = open(filename, 'rb').read()
        except FileNotFoundError:
            print('[ERROR] Unable to found file')
            exit(0)
        super(ROM, self).__init__(self.raw)
        self.SIZE = len(self.raw)

    def read_8(self):
        self.seek(self.CURSOR, SEEK_SET)
        readed = struct.unpack('>B',self.read(1))[0]
        self.CURSOR+=1
        return readed

    def read_16(self):
        self.seek(self.CURSOR, SEEK_SET)
        readed = struct.unpack('>H',self.read(2))[0]
        self.CURSOR+=2
        return readed

    def read_32(self):
        self.seek(self.CURSOR, SEEK_SET)
        readed = struct.unpack('>I',self.read(4))[0]
        self.CURSOR+=4
        return readed

    def set_offset(self, offset=0):
        self.CURSOR=offset
        self.seek(self.CURSOR, SEEK_SET)

    def get_offset(self):
        return self.tell()


class LZKN4:
   
    _buffer = bytearray()

    def __init__(self, input_data):
        self.DATA = input_data

    def append(self, value):
        self._buffer.append(value)
        self._window.append(value)
        return 1

    def append_from_data(self, length=0):
        count=0
        for i in range(length):
            tmp = self.DATA.read_8()
            count += self.append(tmp)
        return count

    def append_from_window(self, length=0, offset=0):
        count = 0
        for i in range(length):
            tmp = self._window.get(offset+i)
            count += self.append(tmp)
        return count

    def decompress(self, offset=0, size=0):
        self._window = RingBuffer(0x400, 0x3DE)
        self._buffer = bytearray()
        uncompressed_size = size
        _decoded = 0
        while (_decoded < uncompressed_size):
            control = self.DATA.read_8()
            for readed_bits in range(8):
                bit = bool((control >> readed_bits) &0x1)
                if not bit:
                    _readed = self.DATA.read_16()
                    length = (_readed &0x1F) + 3
                    offset = ((_readed &0xE0) << 3) | (_readed >> 8)
                    _decoded += self.append_from_window(length, offset)
                else:
                    _readed = self.DATA.read_8()
                    _decoded += self.append(_readed)
        return self._buffer

class Application(argparse.ArgumentParser):
    title = '''
        [Saturn] Castlevania SOTN - Decompressor
        ---------------------------------------------------------
        Tool to decompress graphics from:
            [SATURN] Castlevania - Symphony of the Night (US)
            [SATURN] Akumajou Dracula X: Gekka no Yasoukyoku (JP)
    '''

    argument_list = [
        {
            'name'      : 'input',
            'nargs'     : '?',
            'type'      : argparse.FileType('rb'),     
            'default'   : sys.stdin,
            'help'      : 'Compressed File (.bin)'
        },
        {
            'name'      : 'uncompressed_size',
            'nargs'     : '?',
            'type'      : lambda x: int(x, 0),   
            'default'   : 0,
            'help'      : 'Uncompressed Size'
        }
    ]

    def __init__(self):
        super(Application, self).__init__()
        self.formatter_class=argparse.RawDescriptionHelpFormatter
        self.description=textwrap.dedent(self.title)

        for argument in self.argument_list:
                self.add_argument(
                argument['name'],
                nargs=argument['nargs'],
                type=argument['type'],
                default=argument['default'],
                help=argument['help']
            )       

        args = self.parse_args()
        if args.input.name != '<stdin>':
            binary = ROM(args.input.name)
            lzkn = LZKN4(binary)
            data = lzkn.decompress(binary, args.uncompressed_size)
            try:
                os.stat('output/')
            except:
                os.mkdir('output/')
            out = open("output/"+args.input.name, 'wb')
            out.write(data)
            out.close()
        else:
            self.print_help()

if __name__=="__main__":
    app = Application()


Essa versão modifiquei pra passar o tamanho decomprimido na linha de comando
Salvar como decoder.py e rodar usando o Python 3
Comando:

python decoder.py arquivo.bin tamanho


Tipo, eu executei aqui, não testei ainda mas vi que ele serve para extrair correto?
Eu já tenho os arquivos devidamente extraídos o que estou fazendo manualmente é recomprimir, tem algum meio de modificar pra compactar de volta?

Desde já agradeço pela boa vontade de ajudar esse noob aqui. XD

Cara eu tentei registrar no discord mas não dá em nada, copiei minha id e colei lá as não tive resultado, criei até um server novo pra ver no que ia dar.

Anime_World

#11
Citação de: Mistura_De_Bits online Maio 17, 2021, 22:01:51 PM
Tipo, eu executei aqui, não testei ainda mas vi que ele serve para extrair correto?
Eu já tenho os arquivos devidamente extraídos o que estou fazendo manualmente é recomprimir, tem algum meio de modificar pra compactar de volta?

Desde já agradeço pela boa vontade de ajudar esse noob aqui. XD

Cara eu tentei registrar no discord mas não dá em nada, copiei minha id e colei lá as não tive resultado, criei até um server novo pra ver no que ia dar.

Teste o decompressor, se estiver 100% funcional eu faço o compressor. E te passo no privado, não por aqui.
Quanto ao discord, ao entrar no servidor você precisa ir no canal #recem-chegados e se apresentar, após isso vai receber acesso de membro.
nonononono

Mistura_De_Bits

Citação de: Anime_World online Maio 17, 2021, 22:07:43 PM
Citação de: Mistura_De_Bits online Maio 17, 2021, 22:01:51 PM
Tipo, eu executei aqui, não testei ainda mas vi que ele serve para extrair correto?
Eu já tenho os arquivos devidamente extraídos o que estou fazendo manualmente é recomprimir, tem algum meio de modificar pra compactar de volta?

Desde já agradeço pela boa vontade de ajudar esse noob aqui. XD

Cara eu tentei registrar no discord mas não dá em nada, copiei minha id e colei lá as não tive resultado, criei até um server novo pra ver no que ia dar.

Teste o decompressor, se estiver 100% funcional eu faço o compressor. E te passo no privado, não por aqui.
Quanto ao discord, ao entrar no servidor você precisa ir no canal #recem-chegados e se apresentar, após isso vai receber acesso de membro.

Opa, deu certo aqui com o scrypt que usa o tamanho dos dois primeiros bytes do arquivo, eu adicionei aqui, já o de baixo eu coloquei o 61dd na linha de comando mas ficou dizendo que não era um comando reconhecido, ai preferi fazer com o tamanho no começo mesmo.

huskie

#13
Discussão acalorada sobre programação. Continuem...
O importante é chegar lá...

https://media.giphy.com/media/3o7TKra9XDoRiC6dQ4/giphy.gif
Through PS4 PSXItarch v3 Linux!

Mistura_De_Bits

#14
Estou pensando em uma possibilidade e preciso da opinião de vocês...
Estou pensando se vale a pena colocar faixas pretas nos diálogos já que a faixa azul atrás é semitransparente.

Sem a faixa:



Com a faixa:





Outra opção seria mudar a leitura da faixa e remover a transparência, assim não precisaria sompra nos textos, estou procurando a configuração dela para remover a transparência.