- Aug 15, 2016
- 47
- 35
I wonder about step 2, is it to move the files containing .dll into a separate folder or simply delete the .dll file??Added.
It is.
No.
Locale Emulator works on Windows 11, it is what I use. Try following the steps you quoted + deleting _FONTSET.MED.
What about 4?This translation mostly happened because I wanted to test my tools/scripts and those games use another engine.
Saiminjutsu 1's remake (that uses this engine) has someone actually interested in properly translating it so I won't touch it. There is a partial for the prologue of saimin 2 and ura saimin 2 here https://f95zone.to/threads/translation-request-saiminjutsu-2-black-rainbow.90051/post-13627477.
The games are all standalone so no reason to play in any order tbh.
It worked, thank you very much for the help! If you can do more translations of Mind Control/Saimin games, I'm a big fan of the genre. Once again, thank you for your patience and help.I was able to reproduce this. Delete_FONTSET.MED
before running with Locale Emulator, it should work then.
You don't have permission to view the spoiler content. Log in or register now.
Second this, the Soushinjutsu games for Studio Jaren occupy a similar engine, it would be good if these classic novels were translated.It worked, thank you very much for the help! If you can do more translations of Mind Control/Saimin games, I'm a big fan of the genre. Once again, thank you for your patience and help.
Uh... Why does it straight up say "[VIRUS]"?For posterity, here's the stand-alone translation. The reason to include the translation separately is so an improved translation can be used in the future if someone manually translates it or machine translations improve. This assumes you have the original version with Japanese of course.
Because of this:Uh... Why does it straight up say "[VIRUS]"?
False positive according to OP. It's just the same files they included in the release.The exe is the original game exe with a few bytes patched for the translation.
The dlls are from/for Locale Emulator and yes they do inject into the game but it is to make it work on non Japanese Windows.
Basically yes, it is a false positive.
I'm not talking about a virus scan, though, the name of the file literally says "[VIRUS] Saimin3_ENG.7z". Like, that's straight up how it's written as an attachment under your post.Because of this:
False positive according to OP. It's just the same files they included in the release.
I know. It's automatically added by F95, probably after a virus scan.I'm not talking about a virus scan, though, the name of the file literally says "[VIRUS] Saimin3_ENG.7z". Like, that's straight up how it's written as an attachement under your post.
Usexorxorrax what tools did you use to unpack, repack the .med files? The artwork is pretty low resolution, and the game upscaler isn't very good with noticeable pixelation. I want to see if I can improve/replace the pictures.
from pathlib import Path
from dataclasses import dataclass
from struct import unpack, pack
from glob import glob
from PIL import Image
unpack_u16 = lambda x: unpack("<H", x)[0]
unpack_u32 = lambda x: unpack("<I", x)[0]
@dataclass
class YB:
width: int
height: int
bpp: int
flag: int
packed_sz: int
unpacked_sz: int
data: bytearray
length_table: tuple = tuple(i + 3 for i in range(0xfe)) + (0x400, 0x1000)
def __post_init__(self):
if self.bpp == 3:
self.image_format = "BGR"
if self.bpp == 4:
self.image_format = "BGRA"
@staticmethod
def _copy_overlap(data, src, dst, count):
if dst > src:
while count > 0:
preceding = min(dst - src, count)
data[dst:dst + preceding] = data[src:src + preceding]
count -= preceding
dst += preceding
else:
data[dst:dst + count] = data[src:src + count]
@staticmethod
def _is_dummy_alpha(data):
alpha = data[3]
if(alpha == 0xff):
return False
for i in range(7, len(data), 4):
if data[i] != alpha:
return False
return True
def unpack(self):
if (self.flag & 0x40) != 0:
unpacked_sz = self.unpacked_sz
else:
unpacked_sz = self.width * self.height * self.bpp
output = bytearray(unpacked_sz)
remaining = self.packed_sz
bit = 0
ctl = 0
dst = 0
cursor = 0
while remaining > 0 and dst < len(output):
bit >>= 1
if bit == 0:
ctl = self.data[cursor]
cursor += 1
remaining -= 1
bit = 0x80
if remaining <= 0:
break
if (ctl & bit) == 0:
output[dst] = self.data[cursor]
dst += 1
cursor += 1
remaining -= 1
continue
b = self.data[cursor]
cursor += 1
remaining -= 1
shift = 0
length = 0
if (b & 0x80) != 0:
if remaining <= 0:
break
shift = self.data[cursor]
cursor += 1
remaining -= 1
shift |= (b & 0x3f) << 8
if (b & 0x40) != 0:
if remaining <= 0:
break
offset = self.data[cursor]
cursor += 1
remaining -= 1
length = self.length_table[offset]
else:
length = (shift & 0xf) + 3
shift >>= 4
else:
length = b >> 2
b &= 3
if b == 3:
length += 9
output[dst:dst + length] = self.data[cursor:cursor + length]
if cursor + length > len(self.data):
break
cursor += length
remaining -= length
dst += length
continue
shift = length
length = b + 2
shift += 1
if dst < shift:
raise ValueError("Invalid offset value")
length = min(length, len(output) - dst)
YB._copy_overlap(output, dst - shift, dst, length)
dst += length
if self.flag & 0x80 != 0:
for i in range(self.bpp, len(output)):
output[i] = (output[i] + output[i - self.bpp]) & 0xff
if self.bpp == 4 and YB._is_dummy_alpha(output):
self.image_format = "BGR32"
return output
@staticmethod
def _dumb_pack(input_data):
"""
Dumb pack algorithm. This does no compression at all, just
packs the input data in a way that the game can unpack it.
Tried to reverse the pack algorithm but i burnt too many
brain cells and gave up.
"""
output = bytearray()
pos = 0
input_length = len(input_data)
while pos < input_length:
ctl_pos = len(output)
output.append(0)
control_byte = 0x00
bit = 0x80
for i in range(8):
if pos >= input_length:
break
output.append(input_data[pos])
pos += 1
bit >>= 1
output[ctl_pos] = control_byte
return bytes(output)
@staticmethod
def _lz77_pack(input_data):
"""
Tried to guess the LZ77 type algorithm used in the game.
TODO: FIXME Doesn't work.
"""
output = bytearray()
pos = 0
input_length = len(input_data)
WINDOW_SIZE = 0x1000
MIN_MATCH_LENGTH = 3
while pos < input_length:
ctl_pos = len(output)
output.append(0)
control_byte = 0
bit = 0x80
for i in range(8):
if pos >= input_length:
break
match_length = 0
match_offset = 0
max_match_length = min(0x1000, input_length - pos)
for offset in range(1, min(pos, WINDOW_SIZE) + 1):
candidate_length = 0
while candidate_length < max_match_length and input_data[pos - offset + candidate_length] == input_data[pos + candidate_length]:
candidate_length += 1
if candidate_length > match_length:
match_length = candidate_length
match_offset = offset
if match_length >= MIN_MATCH_LENGTH:
break
if match_length >= MIN_MATCH_LENGTH:
control_byte |= bit
if match_length in YB.length_table:
length_index = YB.length_table.index(match_length)
else:
length_index = len(YB.length_table) - 1
output.append(0x80 | (match_offset >> 8) | 0x40)
output.append(match_offset & 0xFF)
output.append(length_index)
pos += match_length
else:
output.append(input_data[pos])
pos += 1
bit >>= 1
output[ctl_pos] = control_byte
return bytes(output)
@staticmethod
def from_image(image: Image):
if image.mode == "RGB":
image = image.convert("RGBA")
if image.mode != "RGBA":
raise ValueError("Image must be in RGBA mode.")
width, height = image.size
bpp = 4
flag = 0x00
R, G, B, A = image.split()
image = Image.merge("RGBA", (B, G, R, A))
packed = bytearray(YB._dumb_pack(image.tobytes()))
packed_sz = len(packed)
unpacked_sz = 0xE7BEADDE
return YB(width, height, bpp, flag, packed_sz, unpacked_sz, packed)
def to_bytes(self):
return b"YB" + bytes([self.flag, self.bpp]) + \
pack("<II", self.packed_sz, self.unpacked_sz) + \
pack("<HH", self.width, self.height) + self.data
"""
img_path = Path("gra") / "SYS_CFG2.prs"
with open(img_path, "rb") as f:
magic = f.read(2)
assert magic == b"YB"
flag = f.read(1)[0]
bpp = f.read(1)[0]
assert bpp in (0x3, 0x4)
packed_sz = unpack_u32(f.read(4))
unpacked_sz = unpack_u32(f.read(4))
width = unpack_u16(f.read(2))
height = unpack_u16(f.read(2))
data = f.read(packed_sz)
yb_img = YB(width, height, bpp, flag, packed_sz, unpacked_sz, data)
img = Image.frombytes("RGBA", (width, height), yb_img.unpack())
R, G, B, A = img.split()
img = Image.merge("RGBA", (B, G, R, A))
reverse = YB.from_image(img)
with open("reverse.yb", "wb") as f:
f.write(reverse.to_bytes())
img = Image.frombytes("RGBA", (reverse.width, reverse.height), reverse.unpack())
R, G, B, A = img.split()
img = Image.merge("RGBA", (B, G, R, A))
"""
for img_path in glob("gra/*.png"):
path = Path(img_path)
img = Image.open(path)
output = YB.from_image(img)
path = Path("gra_output") / (path.stem + "_0")
with open(path, "wb") as f:
f.write(output.to_bytes())
img = Image.frombytes("RGBA", (output.width, output.height), output.unpack())
R, G, B, A = img.split()
img = Image.merge("RGBA", (B, G, R, A))
# display(img)
diff MED.py MED_gra.py
485c485
< header = b'MDE0'+to_bytes(entry_length, 1)+b'\x00'
---
> header = b'MDE1'+to_bytes(entry_length, 1)+b'\x00'
501c501
< _file_data = MED.encrypt(_file_data, file_index['key'].encode())
---
> #_file_data = MED.encrypt(_file_data, file_index['key'].encode())
Fixed the kanji. Send me a save or the text that precedes the choice.So I've noticed two significant errors so far. During the first scene in Satsuki's "evil" path, there's a line where two kanji are left untranslated for some reason. ("For now, I need to the suggestion.") And when choosing between Tsubasa's "good" and "evil" path, the latter is just an empty choice box.
or the text that precedes the choice.
As for my feelings, I want to unleash my sexual desire right away.
But I'm scared of failing.
[Fukamichi Satoru]
Hmm, what should I do...?
[Let's graudually change my heart]
[_________]