Au, acabat
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
|
||||
# Rango del checksum
|
||||
desp = 0x2000
|
||||
numBytes = 0x2000
|
||||
firstByte = desp + 0x200
|
||||
lastByte = desp + numBytes - 1
|
||||
checksum1 = desp + 0x270
|
||||
checksum2 = desp + 0x271
|
||||
|
||||
# Bloque de armarios
|
||||
ARM_START = 0x02930
|
||||
ARM_END = 0x0293B
|
||||
|
||||
# Armario que quieres dejar cerrado
|
||||
TARGET_BYTE = 0x02934
|
||||
TARGET_BIT = 0x80 # bit 7
|
||||
|
||||
def calc_checksum(buffer):
|
||||
total = 0
|
||||
for i in range(firstByte, lastByte + 1):
|
||||
if i not in (checksum1, checksum2):
|
||||
total += buffer[i]
|
||||
total += 3328
|
||||
buffer[checksum1] = total % 256
|
||||
buffer[checksum2] = (total >> 8) % 256
|
||||
|
||||
if len(sys.argv) != 3:
|
||||
print("Uso: python3 abrir_armarios.py save_in.srm save_out.srm")
|
||||
sys.exit(1)
|
||||
|
||||
infile = sys.argv[1]
|
||||
outfile = sys.argv[2]
|
||||
|
||||
with open(infile, "rb") as f:
|
||||
buf = bytearray(f.read())
|
||||
|
||||
# Abrir todos los armarios
|
||||
for off in range(ARM_START, ARM_END + 1):
|
||||
buf[off] = 0xFF
|
||||
|
||||
# Cerrar solo el armario objetivo
|
||||
buf[TARGET_BYTE] &= ~TARGET_BIT # FF sin el bit 7 → 7E
|
||||
|
||||
# Recalcular checksum
|
||||
calc_checksum(buf)
|
||||
|
||||
with open(outfile, "wb") as f:
|
||||
f.write(buf)
|
||||
|
||||
print("Hecho:", outfile)
|
||||
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
|
||||
# Rango del checksum
|
||||
desp = 0x2000
|
||||
numBytes = 0x2000
|
||||
firstByte = desp + 0x200
|
||||
lastByte = desp + numBytes - 1
|
||||
checksum1 = desp + 0x270
|
||||
checksum2 = desp + 0x271
|
||||
|
||||
# Rango ampliado de armarios
|
||||
ARM_START = 0x02920
|
||||
ARM_END = 0x0293F
|
||||
|
||||
# Armario que quieres dejar cerrado
|
||||
TARGET_BYTE = 0x02934
|
||||
TARGET_BIT = 0x80 # bit 7
|
||||
|
||||
def calc_checksum(buffer):
|
||||
total = 0
|
||||
for i in range(firstByte, lastByte + 1):
|
||||
if i not in (checksum1, checksum2):
|
||||
total += buffer[i]
|
||||
total += 3328
|
||||
buffer[checksum1] = total % 256
|
||||
buffer[checksum2] = (total >> 8) % 256
|
||||
|
||||
if len(sys.argv) != 3:
|
||||
print("Uso: python3 abrir_armarios_extendido.py save_in.srm save_out.srm")
|
||||
sys.exit(1)
|
||||
|
||||
infile = sys.argv[1]
|
||||
outfile = sys.argv[2]
|
||||
|
||||
with open(infile, "rb") as f:
|
||||
buf = bytearray(f.read())
|
||||
|
||||
# Abrir todos los armarios del rango ampliado
|
||||
for off in range(ARM_START, ARM_END + 1):
|
||||
buf[off] = 0xFF
|
||||
|
||||
# Cerrar solo el armario objetivo
|
||||
buf[TARGET_BYTE] &= ~TARGET_BIT # FF sin el bit 7 → 7E
|
||||
|
||||
# Recalcular checksum
|
||||
calc_checksum(buf)
|
||||
|
||||
with open(outfile, "wb") as f:
|
||||
f.write(buf)
|
||||
|
||||
print("Hecho:", outfile)
|
||||
@@ -0,0 +1,85 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
def load_saves(path):
|
||||
files = sorted(os.listdir(path))
|
||||
saves = []
|
||||
size = None
|
||||
|
||||
for f in files:
|
||||
full = os.path.join(path, f)
|
||||
if not os.path.isfile(full):
|
||||
continue
|
||||
with open(full, "rb") as fh:
|
||||
data = fh.read()
|
||||
if size is None:
|
||||
size = len(data)
|
||||
elif len(data) != size:
|
||||
print("Tamaños inconsistentes, abortando.")
|
||||
sys.exit(1)
|
||||
saves.append(data)
|
||||
|
||||
print(f"Cargados {len(saves)} saves de {size} bytes.")
|
||||
return saves, size
|
||||
|
||||
|
||||
def is_one_way_flag(seq):
|
||||
"""Devuelve True si el valor nunca vuelve a 0 después de ser 1."""
|
||||
seen_one = False
|
||||
for v in seq:
|
||||
if v == 1:
|
||||
seen_one = True
|
||||
if seen_one and v == 0:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("Uso: python3 detect_armarios.py <directorio>")
|
||||
sys.exit(1)
|
||||
|
||||
saves, size = load_saves(sys.argv[1])
|
||||
num_saves = len(saves)
|
||||
|
||||
# Transponer: valores por offset
|
||||
flags = [] # lista de (offset, seq)
|
||||
|
||||
for off in range(size):
|
||||
seq = [s[off] for s in saves]
|
||||
values = set(seq)
|
||||
|
||||
# Solo nos interesan offsets con valores 0/1
|
||||
if values.issubset({0, 1}) and len(values) > 1:
|
||||
if is_one_way_flag(seq):
|
||||
flags.append(off)
|
||||
|
||||
print(f"\nEncontrados {len(flags)} offsets que parecen flags de armarios.\n")
|
||||
|
||||
# Agrupar offsets contiguos
|
||||
if not flags:
|
||||
return
|
||||
|
||||
blocks = []
|
||||
start = flags[0]
|
||||
prev = flags[0]
|
||||
|
||||
for off in flags[1:]:
|
||||
if off == prev + 1:
|
||||
prev = off
|
||||
else:
|
||||
blocks.append((start, prev))
|
||||
start = off
|
||||
prev = off
|
||||
blocks.append((start, prev))
|
||||
|
||||
print("Bloques sospechosos:")
|
||||
for a, b in blocks:
|
||||
count = b - a + 1
|
||||
print(f" 0x{a:05X} – 0x{b:05X} ({count} bytes)")
|
||||
|
||||
print("\nSi ves un bloque de ~80 bytes, probablemente son los armarios.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,23 @@
|
||||
import sys
|
||||
|
||||
def load(path):
|
||||
with open(path, "rb") as f:
|
||||
return f.read()
|
||||
|
||||
if len(sys.argv) != 4:
|
||||
print("Uso: python3 diff3.py A.srm B.srm C.srm")
|
||||
sys.exit(1)
|
||||
|
||||
A = load(sys.argv[1])
|
||||
B = load(sys.argv[2])
|
||||
C = load(sys.argv[3])
|
||||
|
||||
if not (len(A) == len(B) == len(C)):
|
||||
print("Los saves no tienen el mismo tamaño.")
|
||||
sys.exit(1)
|
||||
|
||||
print("Offsets que cambian SOLO al abrir el armario:\n")
|
||||
|
||||
for i, (a, b, c) in enumerate(zip(A, B, C)):
|
||||
if a != c and a == b:
|
||||
print(f"Offset 0x{i:05X}: {a:02X} → {c:02X}")
|
||||
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
|
||||
if len(sys.argv) != 3:
|
||||
print("Uso: python3 diff_saves.py save_antes.srm save_despues.srm")
|
||||
sys.exit(1)
|
||||
|
||||
path_a = sys.argv[1]
|
||||
path_b = sys.argv[2]
|
||||
|
||||
with open(path_a, "rb") as f:
|
||||
a = f.read()
|
||||
|
||||
with open(path_b, "rb") as f:
|
||||
b = f.read()
|
||||
|
||||
if len(a) != len(b):
|
||||
print(f"Los saves tienen tamaños distintos: {len(a)} vs {len(b)} bytes")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"Comparando {len(a)} bytes...\n")
|
||||
|
||||
for i, (x, y) in enumerate(zip(a, b)):
|
||||
if x != y:
|
||||
print(f"Offset 0x{i:05X}: {x:02X} → {y:02X}")
|
||||
@@ -0,0 +1,149 @@
|
||||
===========================================
|
||||
DOCUMENTACIÓN DEL SISTEMA DE ARMARIOS
|
||||
===========================================
|
||||
|
||||
Juego: [Nombre del juego]
|
||||
Autor del análisis: Sergio
|
||||
Colaborador técnico: Copilot
|
||||
Fecha: [poner fecha]
|
||||
|
||||
===========================================
|
||||
1. INTRODUCCIÓN
|
||||
===========================================
|
||||
|
||||
Este documento resume todo lo descubierto durante el análisis del sistema
|
||||
de guardado relacionado con los armarios (contenedores) del juego.
|
||||
|
||||
Incluye:
|
||||
|
||||
- Localización del contador global de armarios abiertos
|
||||
- Identificación de flags de evento relacionados
|
||||
- Confirmación de qué offsets NO son armarios
|
||||
- Estado actual de la investigación
|
||||
- Próximos pasos recomendados
|
||||
|
||||
El objetivo final es permitir manipular el save para:
|
||||
- abrir o cerrar armarios individualmente
|
||||
- forzar el logro de “abrir todos los armarios”
|
||||
- depurar el comportamiento del juego
|
||||
|
||||
===========================================
|
||||
2. CONTADOR GLOBAL DE ARMARIOS ABIERTOS
|
||||
===========================================
|
||||
|
||||
Offset: 0x0295C
|
||||
Tipo: contador simple (1 byte)
|
||||
Función: almacena cuántos armarios ha abierto el jugador
|
||||
|
||||
Ejemplo:
|
||||
- 0x3B = 59 armarios abiertos
|
||||
- 0x3C = 60 armarios abiertos
|
||||
- 0x50 = 80 armarios abiertos (valor máximo para el logro)
|
||||
|
||||
Este contador:
|
||||
- aumenta en +1 cada vez que se abre un armario
|
||||
- NO depende del guardado
|
||||
- NO es un bitfield
|
||||
- NO controla si un armario está abierto o cerrado
|
||||
- solo refleja el total acumulado
|
||||
|
||||
IMPORTANTE:
|
||||
Modificar este contador NO abre ni cierra armarios reales.
|
||||
Solo afecta al número que muestra el juego y a la lógica del logro
|
||||
(si el logro depende únicamente del contador).
|
||||
|
||||
===========================================
|
||||
3. FLAG DE EVENTO RELACIONADO (NO ES ARMARIO)
|
||||
===========================================
|
||||
|
||||
Offset: 0x02934
|
||||
Cambio observado: EE → FE al abrir un armario concreto
|
||||
Bit afectado: bit 7 (0x80)
|
||||
|
||||
Este byte:
|
||||
- cambia al abrir el armario de prueba
|
||||
- revertirlo hace que el armario vuelva a aparecer cerrado
|
||||
- PERO no forma parte del bloque real de armarios
|
||||
- es un flag de evento o script asociado al acto de abrir ese armario
|
||||
|
||||
Conclusión:
|
||||
0x02934 NO es el bitfield de armarios.
|
||||
Es un flag de evento que se activa al abrir ese armario concreto.
|
||||
|
||||
===========================================
|
||||
4. BLOQUE REAL DE ARMARIOS (AÚN NO LOCALIZADO)
|
||||
===========================================
|
||||
|
||||
El juego tiene 80 armarios → se necesitan 80 bits → 10 bytes.
|
||||
|
||||
El bloque real:
|
||||
- NO está en 0x02930–0x0293F
|
||||
- NO incluye 0x02934
|
||||
- NO se ve afectado al poner FF en ese rango
|
||||
- debe contener exactamente 59 bits activos en el save analizado
|
||||
- debe cambiar un solo bit al abrir un armario
|
||||
- debe permanecer estable al guardar sin abrir nada
|
||||
|
||||
Estado actual:
|
||||
El bloque real aún no ha sido identificado, pero sabemos que:
|
||||
- está cerca del rango 0x02800–0x02B00
|
||||
- debe ser un bloque compacto de 10 bytes
|
||||
- debe tener valores tipo: 00, 01, 03, 07, 0F, 1F, 3F, 7F, FF
|
||||
- debe contener 59 bits activos en el save actual
|
||||
|
||||
===========================================
|
||||
5. RESUMEN DE HALLAZGOS
|
||||
===========================================
|
||||
|
||||
✔ 0x0295C = contador global de armarios abiertos
|
||||
✔ 0x02934 = flag de evento asociado al armario de prueba
|
||||
✔ revertir 0x02934 cierra el armario visualmente
|
||||
✔ el contador global NO depende de 0x02934
|
||||
✔ poner FF en 0x02920–0x0293F NO abre armarios reales
|
||||
✔ el bloque real de armarios NO está en esa zona
|
||||
✔ el contador global coincide exactamente con el número mostrado por el juego
|
||||
|
||||
===========================================
|
||||
6. PRÓXIMOS PASOS RECOMENDADOS
|
||||
===========================================
|
||||
|
||||
1. Volcar el rango 0x02800–0x02A00 para localizar el bloque real.
|
||||
2. Buscar un bloque de 10 bytes con ~59 bits activos.
|
||||
3. Confirmar qué byte cambia al abrir un armario nuevo.
|
||||
4. Mapear los 80 bits → 80 armarios.
|
||||
5. Crear un editor que:
|
||||
- abra/cierre armarios individualmente
|
||||
- actualice el contador global
|
||||
- recalcule el checksum automáticamente
|
||||
|
||||
===========================================
|
||||
7. CHECKSUM
|
||||
===========================================
|
||||
|
||||
El checksum del save se encuentra en:
|
||||
- 0x2270 (LSB)
|
||||
- 0x2271 (MSB)
|
||||
|
||||
Se calcula sumando:
|
||||
- todos los bytes desde 0x2200 hasta 0x3FFF
|
||||
- excepto los dos bytes del propio checksum
|
||||
- más un offset fijo de 3328
|
||||
|
||||
===========================================
|
||||
8. ESTADO FINAL
|
||||
===========================================
|
||||
|
||||
El sistema de armarios está parcialmente resuelto:
|
||||
|
||||
- Contador global: ✔ localizado y confirmado
|
||||
- Flag de evento del armario de prueba: ✔ localizado
|
||||
- Bloque real de armarios: ❌ pendiente de localizar
|
||||
|
||||
Una vez identificado el bloque real, se podrá:
|
||||
- abrir/cerrar armarios a voluntad
|
||||
- fabricar saves para logros
|
||||
- documentar completamente el formato
|
||||
|
||||
===========================================
|
||||
FIN DEL DOCUMENTO
|
||||
===========================================
|
||||
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
|
||||
desp = 0x2000
|
||||
numBytes = 0x2000
|
||||
firstByte = desp + 0x200
|
||||
lastByte = desp + numBytes - 1
|
||||
checksum1 = desp + 0x270
|
||||
checksum2 = desp + 0x271
|
||||
|
||||
def calc_checksum(buffer):
|
||||
total = 0
|
||||
for i in range(firstByte, lastByte + 1):
|
||||
if i not in (checksum1, checksum2):
|
||||
total += buffer[i]
|
||||
total += 3328
|
||||
buffer[checksum1] = total % 256
|
||||
buffer[checksum2] = (total >> 8) % 256
|
||||
|
||||
if len(sys.argv) != 3:
|
||||
print("Uso: python3 revert_2934.py save_in.srm save_out.srm")
|
||||
sys.exit(1)
|
||||
|
||||
infile = sys.argv[1]
|
||||
outfile = sys.argv[2]
|
||||
|
||||
with open(infile, "rb") as f:
|
||||
buf = bytearray(f.read())
|
||||
|
||||
# revertir el posible flag del armario
|
||||
buf[0x02934] = 0xEE # valor original
|
||||
|
||||
calc_checksum(buf)
|
||||
|
||||
with open(outfile, "wb") as f:
|
||||
f.write(buf)
|
||||
|
||||
print("Hecho:", outfile)
|
||||
@@ -0,0 +1,38 @@
|
||||
import sys
|
||||
|
||||
desp = 0x2000
|
||||
numBytes = 0x2000
|
||||
firstByte = desp + 0x200
|
||||
lastByte = desp + numBytes - 1
|
||||
checksum1 = desp + 0x270
|
||||
checksum2 = desp + 0x271
|
||||
|
||||
def calc_checksum(buffer):
|
||||
total = 0
|
||||
for i in range(firstByte, lastByte + 1):
|
||||
if i not in (checksum1, checksum2):
|
||||
total += buffer[i]
|
||||
total += 3328
|
||||
buffer[checksum1] = total % 256
|
||||
buffer[checksum2] = (total >> 8) % 256
|
||||
|
||||
if len(sys.argv) != 3:
|
||||
print("Uso: python3 revert_armario.py save_in.srm save_out.srm")
|
||||
sys.exit(1)
|
||||
|
||||
infile = sys.argv[1]
|
||||
outfile = sys.argv[2]
|
||||
|
||||
with open(infile, "rb") as f:
|
||||
buf = bytearray(f.read())
|
||||
|
||||
# revertir el byte del armario
|
||||
buf[0x0295C] = 0x3B
|
||||
|
||||
# recalcular checksum
|
||||
calc_checksum(buf)
|
||||
|
||||
with open(outfile, "wb") as f:
|
||||
f.write(buf)
|
||||
|
||||
print("Hecho. Guarda generado:", outfile)
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
-58422
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
|
||||
# Rango del checksum
|
||||
desp = 0x2000
|
||||
numBytes = 0x2000
|
||||
firstByte = desp + 0x200
|
||||
lastByte = desp + numBytes - 1
|
||||
checksum1 = desp + 0x270
|
||||
checksum2 = desp + 0x271
|
||||
|
||||
# Offset del contador de armarios
|
||||
CONTADOR = 0x0295C
|
||||
|
||||
def calc_checksum(buffer):
|
||||
total = 0
|
||||
for i in range(firstByte, lastByte + 1):
|
||||
if i not in (checksum1, checksum2):
|
||||
total += buffer[i]
|
||||
total += 3328
|
||||
buffer[checksum1] = total % 256
|
||||
buffer[checksum2] = (total >> 8) % 256
|
||||
|
||||
if len(sys.argv) != 3:
|
||||
print("Uso: python3 set_contador_armarios.py save_in.srm save_out.srm")
|
||||
sys.exit(1)
|
||||
|
||||
infile = sys.argv[1]
|
||||
outfile = sys.argv[2]
|
||||
|
||||
with open(infile, "rb") as f:
|
||||
buf = bytearray(f.read())
|
||||
|
||||
# Poner el contador a 0x80
|
||||
buf[CONTADOR] = 0x4F
|
||||
|
||||
# Recalcular checksum
|
||||
calc_checksum(buf)
|
||||
|
||||
with open(outfile, "wb") as f:
|
||||
f.write(buf)
|
||||
|
||||
print("Hecho:", outfile)
|
||||
Reference in New Issue
Block a user