Разработка и ромхакинг > Переводы игр
[PS1] Lunar: Silver Star Story Complete - сжатие текста
ViToTiV:
Всем доброго дня!
На днях наконец-то вышел наш перевод "Lunar 2", ура! :cool:
Но переводчик жаждет перевести первую часть, а перевода первой части не существует ни в каком виде!
Поковырял игру - шрифт и графика есть, но текст с сожалению сжат по словарю. В нете есть прога для сжатия и распаковки, но запаковка только для Saturn версии - https://github.com/MrConan1/lsb (возможно в исходниках есть все ответы, но я не спец по Си)
Прошу помощи знатоков в распаковке и запаковке текста для PS1.
Что есть:
1. Исходный файл с запакованный текстом - https://disk.yandex.ru/d/QCsebJIuEQKN1A
Формат вроде понятен: первые 0х800 байт - блок поинтеров, по 2 байта на поинтер, пустые поинтеры (0000) игнорятся. Значение поинтера * 2 = реальная позиция текстовой строки в файле.
2. Файл словаря - https://disk.yandex.ru/d/6Z_OU9refnuIcA
3. Файлы с распакованным текстом, полученные через прогу, которую я указал выше - https://disk.yandex.ru/d/uJeejzt6pYWbyw
В этих файлах есть куча информации, которая возможно содержит всё, что нужно, но без опыта в сжатии сложно понять о чём там речь.
Подарим перевод этой игры русскому сообществу!)
Guyver(X.B.M.):
А какие hex коды соответствуют словам словаря? ta каким символом(лами) обозначается? И какими последнее history of our world.?
ViToTiV:
Guyver(X.B.M.), так откуда ж я знаю то?) я поэтому и прошу помочь разобраться
paul_met:
Я бы назвал это шифрованием, а не сжатием. При составлении соответствующей таблицы, текст в привычном виде можно вполне легко отобразить, например, в круптаре.
Например, в самом начале игры ,если подойти к камню возле дерева, вылезет фраза "It say: "In Honor of the Great Dragonmaster, Dyne."
Поинтер этой строки находится в файле TEXT001.DAT по адресу 104h, текст начинается с адреса 389Bh (в памяти это адрес 801E041Bh). Играясь кодами символов, можно составить необходимую таблицу.
ViToTiV:
paul_met, не совсем понимаю о какой таблице идёт речь. Файла словаря не достаточно? надо ещё что-то?
в исходниках программы, которая извлекает текст (ссылку на которую я выложил в первом посте) есть такой код:
--- Код: ---/*****************************************************************************/
/* psx_string_decode.c : Code to decode WD's Text in Lunar's Script files. */
/* PSX English Edition - Created using Supper's notes, */
/* comparing with Saturn version, and taking some */
/* guesses. (I didn't feel like staring at PSX ASM). */
/*****************************************************************************/
#ifdef _MSC_VER
#pragma warning(disable:4996)
#endif
/* Includes */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* Defines */
#define psxBufferSize (10*1024) //Should be Overkill
/* Function Prototypes */
int loadPSXStringTable(char* inFname);
int releasePSXStringTable();
int getPSXComprStr(int compressionIndex, char* target, int* tlen);
int convertPSXText(char* strIn, char** strOut, int len, int* lenOut);
static unsigned char tmpPsxBuf[psxBufferSize];
static int G_NumPSXTableEntries = 0;
static char** pPSXTableEntries = NULL;
/******************************************************************************/
/* loadPSXStringTable - Loads in a table of NULL-terminated lookup strings. */
/* Input - Name of file to load. */
/* Returns 0 on success, -1 on failure. */
/******************************************************************************/
int loadPSXStringTable(char* inFname){
FILE* infile;
int entryIndex;
int x = 0;
infile = fopen(inFname, "rb");
if (infile == NULL){
printf("Error opening %s\n", inFname);
return -1;
}
/* Part 1, count # entries */
while (1){
fread(tmpPsxBuf, 1, 1, infile);
if (feof(infile))
break;
if (*tmpPsxBuf == 0x00)
G_NumPSXTableEntries++;
}
/* Part 2, Allocate Space and add entries */
fseek(infile, 0, SEEK_SET);
entryIndex = 0;
pPSXTableEntries = (char**)malloc(sizeof(char*)*G_NumPSXTableEntries);
if (*pPSXTableEntries == NULL){
printf("Error allocating memory for table entries.\n");
return -1;
}
while (1){
x = 0;
fread(&tmpPsxBuf[x], 1, 1, infile);
if (feof(infile))
break;
while (tmpPsxBuf[x] != 0x00){
x++;
fread(&tmpPsxBuf[x], 1, 1, infile);
}
pPSXTableEntries[entryIndex] = (char*)malloc(x+1);
if (pPSXTableEntries[entryIndex] == NULL){
printf("Error allocating memory for table entry.\n");
return -1;
}
memset(pPSXTableEntries[entryIndex], 0,x+1);
memcpy(pPSXTableEntries[entryIndex], tmpPsxBuf, x);
entryIndex++;
}
fclose(infile);
return 0;
}
/******************************************************************************/
/* releasePSXStringTable - Removes resources used by a loaded string table. */
/* Returns 0 on success, -1 on failure. */
/******************************************************************************/
int releasePSXStringTable(){
int x;
for (x = 0; x < G_NumPSXTableEntries; x++){
if (pPSXTableEntries[x] != NULL){
free(pPSXTableEntries[x]);
pPSXTableEntries[x] = NULL;
}
}
if (pPSXTableEntries != NULL){
free(pPSXTableEntries);
}
pPSXTableEntries = NULL;
G_NumPSXTableEntries = 0;
return 0;
}
/******************************************************************************/
/* getPSXComprStr - Looks up a compression string by an index value and */
/* copies the string and length of the string. */
/* Input - Lookup index. */
/* Returns 0 on success, -1 on failure. */
/******************************************************************************/
int getPSXComprStr(int compressionIndex, char* target, int* tlen){
if (compressionIndex >= G_NumPSXTableEntries){
printf("Invalid index in getPSXComprStr\n");
return -1;
}
else{
strcpy(target, pPSXTableEntries[compressionIndex]);
*tlen = strlen(pPSXTableEntries[compressionIndex]);
}
return 0;
}
/******************************************************************************/
/* convertPSXText - Decompresses a compressed string. */
/* I took a lot of guessing here. Seems to work fine for all*/
/* files except maybe File51? iOS has 3 text statements */
/* there that print as "{10}" whereas this outputs "ta{10}I "*/
/* Maybe thats normal, not a big deal if it isn't. */
/* Input - Compressed String. */
/* Returns 0 on success, -1 on failure. */
/******************************************************************************/
int convertPSXText(char* strIn, char** strOut, int len, int* lenOut){
unsigned char* ptarget, *optr;
unsigned char input, inputNext;
int compressionIndex, x, stroutIndex;
int offset = 0;
int out_offset = 0;
int enableCtrlCodes = 1;
*lenOut = 0;
ptarget = tmpPsxBuf;
memset(ptarget, 0, psxBufferSize);
/* Decode the message */
while (offset < len){
int tlen = 0;
input = (unsigned char)strIn[offset];
inputNext = (unsigned char)strIn[offset+1];
if (input == 0x0){
offset++; //maybe this re-enables control codes?
enableCtrlCodes = 1;
continue;
}
else if (input == 0xFF){
printf("End of input text stream detected.\n");
ptarget[out_offset++] = 0xFF;
ptarget[out_offset++] = 0xFF;
offset++;
break;
}
if (enableCtrlCodes){
if (input == 0xE){
enableCtrlCodes = 0;
offset++;
continue;
}
if ((input == 0x1) || (input == 0x2) || (input == 0x3)){
ptarget[out_offset++] = 0xFF;
ptarget[out_offset++] = input;
offset++;
continue;
}
if (input == 0x4){
ptarget[out_offset++] = 0xFF;
ptarget[out_offset++] = 0x00;
offset++;
continue;
}
if (((input == 0xF9) || (input == 0xF8) || (input == 0xFA) || (input == 0xFB) ||
(input == 0xFC) || (input == 0xF6) || (input == 0xF1))){
//Copy Ctrl Code
ptarget[out_offset++] = input;
ptarget[out_offset++] = inputNext;
offset += 2;
continue;
}
if (input == 0xFF){
printf("End of input text stream detected.\n");
ptarget[out_offset++] = 0xFF;
ptarget[out_offset++] = 0xFF;
offset++;
break;
}
//If you get here, assume not a control code, enter Text Mode
//printf("Unknown value 0x%X encountered. '%c'\n",input, input+0x1F);
}
enableCtrlCodes = 0;
if (input < 0x5C){
if (input == 0xB){
ptarget[out_offset++] = 0xFF;
ptarget[out_offset++] = 0x00;
ptarget[out_offset++] = 0xFF;
ptarget[out_offset++] = 0x01;
offset++;
continue;
}
else if (input == 0x21){
ptarget[out_offset++] = 0xFF;
ptarget[out_offset++] = 0x02;
offset++;
continue;
}
else if (input == 0x6){
ptarget[out_offset++] = 0xFF;
ptarget[out_offset++] = 0x00;
offset++;
continue;
}
else if (input == 0x5){
ptarget[out_offset++] = 0xFF;
ptarget[out_offset++] = 0x00;
ptarget[out_offset++] = 0xFF;
ptarget[out_offset++] = 0x03;
ptarget[out_offset++] = 0xFF;
ptarget[out_offset++] = 0xFF;
offset++;
break;
}
else{
//Uncompressed ASCII Literal
ptarget[out_offset] = input + (unsigned char)0x1F;
out_offset++;
}
}
else if (input == 0xFD){
//Compression Extended Lkup Version 1
offset++;
input = (unsigned char)strIn[offset];
compressionIndex = (unsigned int)input + 0x00A1;
getPSXComprStr(compressionIndex, (char*)&ptarget[out_offset], &tlen);
out_offset += tlen;
}
else if (input == 0xFE){
//Compression Extended Lkup Version 2
offset++;
input = (unsigned char)strIn[offset];
compressionIndex = (unsigned int)input + 0x019E;
getPSXComprStr(compressionIndex, (char*)&ptarget[out_offset], &tlen);
out_offset += tlen;
}
else{
//Std Lookup
compressionIndex = input - 0x005C;
getPSXComprStr(compressionIndex, (char*)&ptarget[out_offset], &tlen);
out_offset += tlen;
}
offset++;
/* End of text */
if (ptarget[out_offset - 1] == '$')
break;
}
/* Copy out actual decompressed string */
*strOut = (char*)malloc(2*(out_offset + 1));
if (*strOut == NULL){
printf("Error allocating memory for string buffer.\n");
return -1;
}
optr = (unsigned char*)(*strOut);
stroutIndex = 0;
memset(optr, 0, 2 * (out_offset + 1));
for (x = 0; x < out_offset; x++){
if (ptarget[x] >= 0xF0){ //ctrl code
optr[stroutIndex++] = ptarget[x++];
optr[stroutIndex++] = ptarget[x];
}
else{
optr[stroutIndex++] = ptarget[x];
}
}
*lenOut = stroutIndex;
return offset;
}
--- Конец кода ---
но я в Си не силён, может кто-нить сможет разобраться?
paul_met:
--- Цитата: ViToTiV от 17 Май 2024, 16:18:17 ---paul_met, не совсем понимаю о какой таблице идёт речь. Файла словаря не достаточно? надо ещё что-то?
--- Конец цитаты ---
Толку то от файла словаря, когда ты не знаешь каким кодам соответствуют слова в таблице.
Обычная MTE таблица:
--- Код: ---0100=Hello
0101=World
...
--- Конец кода ---
В качестве примера, я декодировал первую реплику:
ViToTiV:
paul_met, не понял, так у тебя уже получилось декодировать???
paul_met:
--- Цитата: ViToTiV от 17 Май 2024, 17:10:08 ---paul_met, не понял, так у тебя уже получилось декодировать???
--- Конец цитаты ---
Это же обычное DTE/MTE кодирование. Просто составляешь соответствующую таблицу и всё.
Таблица этой реплики:
--- Код: ---01=
03="
0f=.
1B=:
21=_
28=G
29=H
2A=I
54=s
5a=y
64=t
6D=sa
6E=,
87=on
8D=n
AF=re
B1=at
FD1B=or
FDBB=Dyne
FDD8=Dragonmaster
FE5A=of the
--- Конец кода ---
Guyver(X.B.M.):
ViToTiV, хотя бы шрифт игровой приложи картинкой, чтобы обычную таблицу можно было вообще составить... А ведь надо и русскую таблицу сразу составить. Сразу, чтобы потом не оказалось, что букв не хватает и т.п. И ещё, символы словаря и текста должны быть тождественны, а в словаре используется другая "кодировка", к примеру, 22=А против 41=А. И нужно сразу составить русскую таблицу и для словаря, чтобы все буквы выводились и были тождественны таблице с текстом.
Таблица словаря (не уверен, что она полная, но вроде бы всё ОК.):
--- Цитата ---20=
21=!
27='
2C=,
2E=.
3F=?
41=A
42=B
43=C
44=D
45=E
46=F
47=G
48=H
49=I
4A=J
4B=K
4C=L
4D=M
4E=N
4F=O
50=P
51=Q
52=R
53=S
54=T
55=U
56=V
57=W
58=X
59=Y
5A=Z
61=a
62=b
63=c
64=d
65=e
66=f
67=g
68=h
69=i
6A=j
6B=k
6C=l
6D=m
6E=n
6F=o
70=p
71=q
72=r
73=s
74=t
75=u
76=v
77=w
78=x
79=y
7A=z
--- Конец цитаты ---
Влезет весь русский алфавит в неё? Какие будут hex коды у недостающих букв? Будут ли они "тождественно" выводиться в тексте?
Вот таблица для твоего текста (её надо дополнять знаками препинания и, может, ещё какими-то спецсимволами, пока там 572 "символа"):
https://dropmefiles.com/cRvRD
Я внёс туда весь твой словарь и обычные буквы, но какие есть в ней знаки препинания - я не знаю. Поэтому ввёл только те, что определил на первый взгляд:
--- Код: ---02=!
03="
08='
0D=,
0F=.
1B=:
20=?
--- Конец кода ---
Поэтому и нужна картинка шрифта, что используется в игре, чтобы внести дополнительные знаки...
Добавлено позже:
И ещё, я не хочу считать, умножать на два и т.д. Дай мне для примера пойнтеры в таком виде:
адрес поинтера, сам поинтер, адрес текста
--- Цитата ---Значение поинтера * 2 = реальная позиция текстовой строки в файле.
Поинтеры в начале файла:
0000 0004 4B04 4F04 7404 8804
4B04 это h044Вх2 и получаем адрес текста h8E8? Не вижу текста по такому адресу...
--- Конец цитаты ---
И я составлю проект для круптара (если там не сложно). Я не знаю, как в игре устроено всё, в моей игре текст шёл вперемешку с информацией вывода самого текста, рамок и т.п. и перемещать его на другое место было ОЧЕНЬ сложно, поэтому я перемещал только те фразы, что никак не влезали. А меню всякие у меня шли блоками, и их изменять/перемещать было проще намного.
Как я понимаю, словарь один на все файлы dat с текстом, а ты приложил только один из них? В самом словаре 512 "слов" и ты потом просто заменишь все из них на наиболее повторяемые русские такой же длины, это не сложно, тем более что почти все слова словаря сгруппированы по длине. Только перед этим придётся перевести ВЕСЬ текст, потом составить словарь, и уже потом можно будет вставлять текст в каждый из файлов. Причём сразу нужно учитывать, что переводить не стоит слишком размашисто, а делать это нужно более "сжато и сухо". Нужно сразу использовать много повторяющихся конструкций, чтобы потом получить как можно меньше текста в итоге.
В круптаре можно подключать внешний словарь и перед вставкой текста "генерировать его на лету", но я не помню как это делается и поэтому могу работать только с "тупым" многозатратным методом с обычными таблицами, в которые и вносится сам словарь... В теории я могу "повспоминать", так как у меня должны быть готовые проекты перевода игр со словарными системами, составленные грамотными людьми. Но не факт, что я их найду. Вроде Кристалис на гейм бой должен валяться где-то, там как раз словарь подключали нормальные люди...
paul_met:
--- Цитата: Guyver(X.B.M.) от 17 Май 2024, 19:48:38 ---И ещё, я не хочу считать, умножать на два и т.д. Дай мне для примера пойнтеры в таком виде:
--- Конец цитаты ---
Нет нужны умножать на 2 - круптар сделает это за тебя (ptAlignment=2). Так как на ps1 обратный порядок байт, то ptBIG_ENDIAN=false. Размер поинтеров 2 байта, значит ptPointerSize=2. Ресурсы игры находятся в одном файле (LUNADATA.FIL), который нет нужны разбивать на отдельные. Внутри файла есть наглядный список с именами файлов и их номерами секторов (адрес файла = номер сектора * 800h). Так что, очень легко определить ptReference (например, файл TEXT001.DAT будет находится по адресу 0E59h*800h=72C800h).
Guyver(X.B.M.):
В любом случае у меня нет этих файлов, но раз так всё просто, то составить проект тогда можно без труда. Таблицу я скинул, недостающее можно в неё дописать... По крайней мере, можно вынуть и перевести весь текст для начала...
Проект для словаря: https://dropmefiles.com/im26D
В нём надо добавить в русскую таблицу русские буквы (именно добавить, а не переписать на месте английских) и в самом словаре в словах использовать ту же длину слов, что и у оригинала (раз пойнтеров на слова словаря не предоставлено, но, обычно, они и не нужны).
ViToTiV:
paul_met, Guyver(X.B.M.), спасибо большое!
вот шрифт игры - https://disk.yandex.ru/d/wpLqDYZDuuSl_w
словарь да, один на весь текст
--- Цитата: Guyver(X.B.M.) от 17 Май 2024, 19:48:38 ---Вот таблица для твоего текста (её надо дополнять знаками препинания и, может, ещё какими-то спецсимволами, пока там 572 "символа"):
--- Конец цитаты ---
так а как ты её получил? откуда ты взял все эти коды? :?
и сразу поправочка в твою таблицу: 01=пробел, 21=перенос строки
круптар я только знаю, что есть такая программа, но никогда не пользовался
и мысли в слух - теоретически же текст назад не обязательно кодировать, ведь можно не использовать словарь, а писать прямым текстом?
Guyver(X.B.M.):
Мне было достаточно знать код одной буквы и одного слова из словаря. Остальное я чисто на предположениях подключил (игры у меня нет, есть только пара файлов из неё).
1. Составил шаблон таблицы (это умеет Djinn Tile Mapper в один клик) вида:
--- Код: ---00=
01=
...
FE=
FF=
--- Конец кода ---
2. Посмотрел что в сообщении paul_met написал, что есть коды вида FDD8=Dragonmaster и FE5A=of the. Дополнил таблицу (онлайн сервисом, который добавил в уже существующую таблицу в каждую строку нужные мне префиксы FD и FE), которая приобрела вид:
--- Код: ---00=
01=
...
FE=
FF=
FD00=
FD01=
...
FDFE=
FDFF=
FE00=
FE01=
...
FEFE=
FEFF=
--- Конец кода ---
3. Вытащил словарь с помощью Kruptar'а в текстовой файл. Коды таблицы видно в хексредакторе Translhextion (стандартная таблица). Сделал простейший проект и вынул словарь в виде, где просто каждое новое слово находится на новой строке. Всего 512 строк.
4. 28=G, 54=s - забил весь остальной алфавит.
5. 6D=sa - забил все остальные слова на соответствующие места в таблице от 6D до FF по порядку.
6. FD1B=or - забил все остальные слова на соответствующие места в таблице от FD1B до FDFF по порядку.
7. FE5A=of the - забил все остальные слова на соответствующие места в таблице от FE5A до скольки хватило оставшихся слов словаря по порядку. У последнего слова оказался код FE61=history of our world.
8. Забил все оставшиеся слова в таблицу, двигаясь уже вверх от известных символов. Выяснилось, что символы FD00, FD01, FD02 и FE00, FE01, FE02 оказались "пустыми". Т.е. весь словарь идёт не по порядку, а с небольшими сдвигами.
Слова из словаря вставлял в пустую таблицу, используя другой онлайн сервис, который соединяет строки двух текстов в один (таких сервисов множество). Т.е. на всё это у меня ушло минут 25, где большую часть времени я составлял проект к круптару для словаря и вынимал его в удобоваримый вид.
8. Составил в круптаре проект для текста, где просто подключил вновьсозданную таблицу и вынул весь текст из файла без поинтеров (с нулевыми поинтерами), чтобы увидеть текст. Увидел его, внёс поправки в таблицу, добавив некоторые символы.
В теории текст не обязательно опять "кодировать", если:
а. В образе есть свободное место, куда его можно перенести.
б. Можно расширить образ, но тогда там нужно много ещё чего изменить.
С этим тебе подскажут специалисты.
Дополненная таблица для текста: https://dropmefiles.com/zg3Af
Таблицу для словаря можно дополнить, используя начало этой, сделав необходимые сдвиги.
ViToTiV:
Guyver(X.B.M.), в рот мне ноги)) столько телодвижений пришлось сделать?))
Спасибо ещё раз, с декодированием текста всё понятно! :drinks:
Копаюсь, самое стрёмное - это отделить спецкоды от текста, и это капец. Но тут уже придётся разбираться, что сделаешь
Guyver(X.B.M.):
А в чём ты текст смотришь? В хексредакторе? Если тебе его нужно просто вынуть, можно использовать круптар с нулевыми поинтерами. Это несколько кликов...
Мусора хватает, но можно его удалить...
paul_met:
ViToTiV, Мой тебе совет - освой отладчик PCSX (благо там всё просто) и твои возможности сразу возрастут в 1000 раз.
Порядок определения кодов прост как 5 копеек.
* Сохраняешься перед тем, как активировать определённую реплику.
* Переходишь в адрес начала строки реплики и меняешь код символа на произвольный (например, самый первый код в строке).
* Активируешь реплику и смотришь как поменялся символ (или набор символов).
* Записываешь код и соответствующий ему символ (или набор символов) в свою таблицу.
* Повторяешь процедуру заново (благо словарь не большой и ты быстро его разберёшь по кодам).PS: Многие люди пишут софт под конкретный случай, тратя кучу времени на код и отладку вместо того, чтобы адаптировать свой случай под уже существующий софт.
ViToTiV:
paul_met, согласен, но у меня, к сожалению, уже давно нет времени на изучение чего-то нового (занимаюсь этим всем исключительно на работе, дома свои дела)
Guyver(X.B.M.), ну мне не очистить текст надо, а отделить от кодов, чтобы потом правильно собрать это всё при вставке текста назад.
в общем, буду копать, всем спасибо за помощь :thumbup:
Guyver(X.B.M.):
Как отделить от кодов. Так?
https://dropmefiles.com/g1BqL
Если да, то это пара кликов...
ViToTiV:
Guyver(X.B.M.), ты разделяешь строки по 0х00, а они не разделяются по нулю, они вообще вроде как никаким кодом не разделяются, как до текста могут быть любые коды, так и после текста могут быть любые коды
Guyver(X.B.M.):
Я это понимаю (хотя /00 является стопбайтом в словаре), но я ведь не собираюсь вставлять текст. Я его вынимаю только, поэтому сделал так, чтобы текст не был одним сплошным предложением :neznayu: Зачем для перевода всякие спецсимволы? В будущем при составлении словаря тебе нужен только переведённый текст, вообще без спецсимволов.
Навигация
Перейти к полной версии