match TSA order on efxskill
5
.gitignore
vendored
@ -74,6 +74,11 @@ tools/agbcc
|
||||
*.fk
|
||||
data/banim/*.bin
|
||||
|
||||
*.feimg.bin
|
||||
*.fetsa1.bin
|
||||
*.fetsa2.bin
|
||||
*.fetsa3.bin
|
||||
|
||||
# =========================
|
||||
# Dump Scripts Output
|
||||
# =========================
|
||||
|
6
Makefile
@ -33,6 +33,7 @@ AIF2PCM := tools/aif2pcm/aif2pcm$(EXE)
|
||||
MID2AGB := tools/mid2agb/mid2agb$(EXE)
|
||||
TEXTENCODE := tools/textencode/textencode$(EXE)
|
||||
JSONPROC := tools/jsonproc/jsonproc$(EXE)
|
||||
FETSATOOL := tools/gfxtools/tsa_generator.py
|
||||
|
||||
ifeq ($(UNAME),Darwin)
|
||||
SED := sed -i ''
|
||||
@ -110,7 +111,7 @@ clean:
|
||||
# Remove converted songs
|
||||
$(RM) -f $(MID_SUBDIR)/*.s
|
||||
$(RM) -f $(AUTO_GEN_TARGETS)
|
||||
find . \( -iname '*.o' -o -iname '*.obj' -o -iname '*.1bpp' -o -iname '*.4bpp' -o -iname '*.8bpp' -o -iname '*.gbapal' -o -iname '*.lz' -o -iname '*.fk' -o -iname '*.latfont' -o -iname '*.hwjpnfont' -o -iname '*.fwjpnfont' \) -exec rm {} +
|
||||
@find . \( -iname '*.o' -o -iname '*.obj' -o -iname '*.feimg.bin' -o -iname '*.fetsa1.bin' -o -iname '*.1bpp' -o -iname '*.4bpp' -o -iname '*.8bpp' -o -iname '*.gbapal' -o -iname '*.lz' -o -iname '*.fk' -o -iname '*.latfont' -o -iname '*.hwjpnfont' -o -iname '*.fwjpnfont' \) -exec rm {} +
|
||||
|
||||
.PHONY: clean
|
||||
|
||||
@ -152,6 +153,9 @@ sound/%.bin: sound/%.aif ; $(AIF2PCM) $< $@
|
||||
%.4bpp.h: %.4bpp
|
||||
$(BIN2C) $< $(subst .,_,$(notdir $<)) | sed 's/^const //' > $@
|
||||
|
||||
%.feimg.bin %.fetsa1.bin: %.png
|
||||
$(FETSATOOL) $< $*.feimg.bin $*.fetsa1.bin
|
||||
|
||||
# Battle Animation Recipes
|
||||
|
||||
$(BANIM_OBJECT): $(shell ./scripts/arm_compressing_linker.py -t linker_script_banim.txt -m)
|
||||
|
@ -1,195 +1,193 @@
|
||||
.section .data
|
||||
.include "animscr.inc"
|
||||
.include "gba_sprites.inc"
|
||||
|
||||
.global Img_EfxSkill1
|
||||
Img_EfxSkill1: @ 0x085C935C
|
||||
.incbin "baserom.gba", 0x5C935C, 0x5C9740 - 0x5C935C
|
||||
.incbin "graphics/efxskill/efxskill_1.feimg.bin.lz"
|
||||
|
||||
.global Img_EfxSkill2
|
||||
Img_EfxSkill2:
|
||||
.incbin "baserom.gba", 0x5C9740, 0x5C9B38 - 0x5C9740
|
||||
.incbin "graphics/efxskill/efxskill_2.feimg.bin.lz"
|
||||
|
||||
.global Img_EfxSkill3
|
||||
Img_EfxSkill3:
|
||||
.incbin "baserom.gba", 0x5C9B38, 0x5C9F48 - 0x5C9B38
|
||||
.incbin "graphics/efxskill/efxskill_3.feimg.bin.lz"
|
||||
|
||||
.global Img_EfxSkill4
|
||||
Img_EfxSkill4:
|
||||
.incbin "baserom.gba", 0x5C9F48, 0x5CA380 - 0x5C9F48
|
||||
.incbin "graphics/efxskill/efxskill_4.feimg.bin.lz"
|
||||
|
||||
.global Img_EfxSkill5
|
||||
Img_EfxSkill5:
|
||||
.incbin "baserom.gba", 0x5CA380, 0x5CA7FC - 0x5CA380
|
||||
.incbin "graphics/efxskill/efxskill_5.feimg.bin.lz"
|
||||
|
||||
.global Img_EfxSkill6
|
||||
Img_EfxSkill6:
|
||||
.incbin "baserom.gba", 0x5CA7FC, 0x5CACF4 - 0x5CA7FC
|
||||
.incbin "graphics/efxskill/efxskill_6.feimg.bin.lz"
|
||||
|
||||
.global Img_EfxSkill7
|
||||
Img_EfxSkill7:
|
||||
.incbin "baserom.gba", 0x5CACF4, 0x5CB2CC - 0x5CACF4
|
||||
.incbin "graphics/efxskill/efxskill_7.feimg.bin.lz"
|
||||
|
||||
.global Img_EfxSkill8
|
||||
Img_EfxSkill8:
|
||||
.incbin "baserom.gba", 0x5CB2CC, 0x5CB9AC - 0x5CB2CC
|
||||
.incbin "graphics/efxskill/efxskill_8.feimg.bin.lz"
|
||||
|
||||
.global Img_EfxSkill9
|
||||
Img_EfxSkill9:
|
||||
.incbin "baserom.gba", 0x5CB9AC, 0x5CC0E8 - 0x5CB9AC
|
||||
.incbin "graphics/efxskill/efxskill_9.feimg.bin.lz"
|
||||
|
||||
.global Img_EfxSkillA
|
||||
Img_EfxSkillA:
|
||||
.incbin "baserom.gba", 0x5CC0E8, 0x5CC820 - 0x5CC0E8
|
||||
.incbin "graphics/efxskill/efxskill_10.feimg.bin.lz"
|
||||
|
||||
.global Img_EfxSkillB
|
||||
Img_EfxSkillB:
|
||||
.incbin "baserom.gba", 0x5CC820, 0x5CCF14 - 0x5CC820
|
||||
.incbin "graphics/efxskill/efxskill_11.feimg.bin.lz"
|
||||
|
||||
.global Img_EfxSkillC
|
||||
Img_EfxSkillC:
|
||||
.incbin "baserom.gba", 0x5CCF14, 0x5CD5A0 - 0x5CCF14
|
||||
.incbin "graphics/efxskill/efxskill_12.feimg.bin.lz"
|
||||
|
||||
.global Img_EfxSkillD
|
||||
Img_EfxSkillD:
|
||||
.incbin "baserom.gba", 0x5CD5A0, 0x5CDC00 - 0x5CD5A0
|
||||
.incbin "graphics/efxskill/efxskill_13.feimg.bin.lz"
|
||||
|
||||
.global Img_EfxSkillE
|
||||
Img_EfxSkillE:
|
||||
.incbin "baserom.gba", 0x5CDC00, 0x5CE200 - 0x5CDC00
|
||||
.incbin "graphics/efxskill/efxskill_14.feimg.bin.lz"
|
||||
|
||||
.global Img_EfxSkillF
|
||||
Img_EfxSkillF:
|
||||
.incbin "baserom.gba", 0x5CE200, 0x5CE7C4 - 0x5CE200
|
||||
.incbin "graphics/efxskill/efxskill_15.feimg.bin.lz"
|
||||
|
||||
.global Img_EfxSkill10
|
||||
Img_EfxSkill10:
|
||||
.incbin "baserom.gba", 0x5CE7C4, 0x5CEC6C - 0x5CE7C4
|
||||
.incbin "graphics/efxskill/efxskill_16.feimg.bin.lz"
|
||||
|
||||
.global Pal_EfxSkill1
|
||||
Pal_EfxSkill1:
|
||||
.incbin "baserom.gba", 0x5CEC6C, 0x5CEC8C - 0x5CEC6C
|
||||
.incbin "graphics/efxskill/efxskill_1.gbapal"
|
||||
|
||||
.global Pal_EfxSkill2
|
||||
Pal_EfxSkill2:
|
||||
.incbin "baserom.gba", 0x5CEC8C, 0x5CECAC - 0x5CEC8C
|
||||
.incbin "graphics/efxskill/efxskill_2.gbapal"
|
||||
|
||||
.global Pal_EfxSkill3
|
||||
Pal_EfxSkill3:
|
||||
.incbin "baserom.gba", 0x5CECAC, 0x5CECCC - 0x5CECAC
|
||||
.incbin "graphics/efxskill/efxskill_3.gbapal"
|
||||
|
||||
.global Pal_EfxSkill4
|
||||
Pal_EfxSkill4:
|
||||
.incbin "baserom.gba", 0x5CECCC, 0x5CECEC - 0x5CECCC
|
||||
.incbin "graphics/efxskill/efxskill_4.gbapal"
|
||||
|
||||
.global Pal_EfxSkill5
|
||||
Pal_EfxSkill5:
|
||||
.incbin "baserom.gba", 0x5CECEC, 0x5CED0C - 0x5CECEC
|
||||
.incbin "graphics/efxskill/efxskill_5.gbapal"
|
||||
|
||||
.global Pal_EfxSkill6
|
||||
Pal_EfxSkill6:
|
||||
.incbin "baserom.gba", 0x5CED0C, 0x5CED2C - 0x5CED0C
|
||||
.incbin "graphics/efxskill/efxskill_6.gbapal"
|
||||
|
||||
.global Pal_EfxSkill7
|
||||
Pal_EfxSkill7:
|
||||
.incbin "baserom.gba", 0x5CED2C, 0x5CED4C - 0x5CED2C
|
||||
.incbin "graphics/efxskill/efxskill_7.gbapal"
|
||||
|
||||
.global Pal_EfxSkill8
|
||||
Pal_EfxSkill8:
|
||||
.incbin "baserom.gba", 0x5CED4C, 0x5CED6C - 0x5CED4C
|
||||
.incbin "graphics/efxskill/efxskill_8.gbapal"
|
||||
|
||||
.global Pal_EfxSkill9
|
||||
Pal_EfxSkill9:
|
||||
.incbin "baserom.gba", 0x5CED6C, 0x5CED8C - 0x5CED6C
|
||||
.incbin "graphics/efxskill/efxskill_9.gbapal"
|
||||
|
||||
.global Pal_EfxSkillA
|
||||
Pal_EfxSkillA:
|
||||
.incbin "baserom.gba", 0x5CED8C, 0x5CEDAC - 0x5CED8C
|
||||
.incbin "graphics/efxskill/efxskill_10.gbapal"
|
||||
|
||||
.global Pal_EfxSkillB
|
||||
Pal_EfxSkillB:
|
||||
.incbin "baserom.gba", 0x5CEDAC, 0x5CEDCC - 0x5CEDAC
|
||||
.incbin "graphics/efxskill/efxskill_11.gbapal"
|
||||
|
||||
.global Pal_EfxSkillC
|
||||
Pal_EfxSkillC:
|
||||
.incbin "baserom.gba", 0x5CEDCC, 0x5CEDEC - 0x5CEDCC
|
||||
.incbin "graphics/efxskill/efxskill_12.gbapal"
|
||||
|
||||
.global Pal_EfxSkillD
|
||||
Pal_EfxSkillD:
|
||||
.incbin "baserom.gba", 0x5CEDEC, 0x5CEE0C - 0x5CEDEC
|
||||
.incbin "graphics/efxskill/efxskill_13.gbapal"
|
||||
|
||||
.global Pal_EfxSkillE
|
||||
Pal_EfxSkillE:
|
||||
.incbin "baserom.gba", 0x5CEE0C, 0x5CEE2C - 0x5CEE0C
|
||||
.incbin "graphics/efxskill/efxskill_14.gbapal"
|
||||
|
||||
.global Pal_EfxSkillF
|
||||
Pal_EfxSkillF:
|
||||
.incbin "baserom.gba", 0x5CEE2C, 0x5CEE4C - 0x5CEE2C
|
||||
.incbin "graphics/efxskill/efxskill_15.gbapal"
|
||||
|
||||
.global Pal_EfxSkill10
|
||||
Pal_EfxSkill10:
|
||||
.incbin "baserom.gba", 0x5CEE4C, 0x5CEE6C - 0x5CEE4C
|
||||
.incbin "graphics/efxskill/efxskill_16.gbapal"
|
||||
|
||||
.global Tsa_EfxSkill1
|
||||
Tsa_EfxSkill1:
|
||||
.incbin "baserom.gba", 0x5CEE6C, 0x5CEF04 - 0x5CEE6C
|
||||
.incbin "graphics/efxskill/efxskill_1.fetsa1.bin.lz"
|
||||
|
||||
.global Tsa_EfxSkill2
|
||||
Tsa_EfxSkill2:
|
||||
.incbin "baserom.gba", 0x5CEF04, 0x5CEFA4 - 0x5CEF04
|
||||
.incbin "graphics/efxskill/efxskill_2.fetsa1.bin.lz"
|
||||
|
||||
.global Tsa_EfxSkill3
|
||||
Tsa_EfxSkill3:
|
||||
.incbin "baserom.gba", 0x5CEFA4, 0x5CF044 - 0x5CEFA4
|
||||
.incbin "graphics/efxskill/efxskill_3.fetsa1.bin.lz"
|
||||
|
||||
.global Tsa_EfxSkill4
|
||||
Tsa_EfxSkill4:
|
||||
.incbin "baserom.gba", 0x5CF044, 0x5CF0E8 - 0x5CF044
|
||||
.incbin "graphics/efxskill/efxskill_4.fetsa1.bin.lz"
|
||||
|
||||
.global Tsa_EfxSkill5
|
||||
Tsa_EfxSkill5:
|
||||
.incbin "baserom.gba", 0x5CF0E8, 0x5CF1A0 - 0x5CF0E8
|
||||
.incbin "graphics/efxskill/efxskill_5.fetsa1.bin.lz"
|
||||
|
||||
.global Tsa_EfxSkill6
|
||||
Tsa_EfxSkill6:
|
||||
.incbin "baserom.gba", 0x5CF1A0, 0x5CF264 - 0x5CF1A0
|
||||
.incbin "graphics/efxskill/efxskill_6.fetsa1.bin.lz"
|
||||
|
||||
.global Tsa_EfxSkill7
|
||||
Tsa_EfxSkill7:
|
||||
.incbin "baserom.gba", 0x5CF264, 0x5CF33C - 0x5CF264
|
||||
.incbin "graphics/efxskill/efxskill_7.fetsa1.bin.lz"
|
||||
|
||||
.global Tsa_EfxSkill8
|
||||
Tsa_EfxSkill8:
|
||||
.incbin "baserom.gba", 0x5CF33C, 0x5CF440 - 0x5CF33C
|
||||
.incbin "graphics/efxskill/efxskill_8.fetsa1.bin.lz"
|
||||
|
||||
.global Tsa_EfxSkill9
|
||||
Tsa_EfxSkill9:
|
||||
.incbin "baserom.gba", 0x5CF440, 0x5CF544 - 0x5CF440
|
||||
.incbin "graphics/efxskill/efxskill_9.fetsa1.bin.lz"
|
||||
|
||||
.global Tsa_EfxSkillA
|
||||
Tsa_EfxSkillA:
|
||||
.incbin "baserom.gba", 0x5CF544, 0x5CF648 - 0x5CF544
|
||||
.incbin "graphics/efxskill/efxskill_10.fetsa1.bin.lz"
|
||||
|
||||
.global Tsa_EfxSkillB
|
||||
Tsa_EfxSkillB:
|
||||
.incbin "baserom.gba", 0x5CF648, 0x5CF750 - 0x5CF648
|
||||
.incbin "graphics/efxskill/efxskill_11.fetsa1.bin.lz"
|
||||
|
||||
.global Tsa_EfxSkillC
|
||||
Tsa_EfxSkillC:
|
||||
.incbin "baserom.gba", 0x5CF750, 0x5CF83C - 0x5CF750
|
||||
.incbin "graphics/efxskill/efxskill_12.fetsa1.bin.lz"
|
||||
|
||||
.global Tsa_EfxSkillD
|
||||
Tsa_EfxSkillD:
|
||||
.incbin "baserom.gba", 0x5CF83C, 0x5CF91C - 0x5CF83C
|
||||
.incbin "graphics/efxskill/efxskill_13.fetsa1.bin.lz"
|
||||
|
||||
.global Tsa_EfxSkillE
|
||||
Tsa_EfxSkillE:
|
||||
.incbin "baserom.gba", 0x5CF91C, 0x5CF9F4 - 0x5CF91C
|
||||
.incbin "graphics/efxskill/efxskill_14.fetsa1.bin.lz"
|
||||
|
||||
.global Tsa_EfxSkillF
|
||||
Tsa_EfxSkillF:
|
||||
.incbin "baserom.gba", 0x5CF9F4, 0x5CFAC0 - 0x5CF9F4
|
||||
.incbin "graphics/efxskill/efxskill_15.fetsa1.bin.lz"
|
||||
|
||||
.global Tsa_EfxSkill10
|
||||
Tsa_EfxSkill10:
|
||||
.incbin "baserom.gba", 0x5CFAC0, 0x5CFB70 - 0x5CFAC0
|
||||
.incbin "graphics/efxskill/efxskill_16.fetsa1.bin.lz"
|
||||
|
BIN
graphics/efxskill/efxskill_1.png
Normal file
After Width: | Height: | Size: 227 B |
BIN
graphics/efxskill/efxskill_10.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
graphics/efxskill/efxskill_11.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
graphics/efxskill/efxskill_12.png
Normal file
After Width: | Height: | Size: 968 B |
BIN
graphics/efxskill/efxskill_13.png
Normal file
After Width: | Height: | Size: 930 B |
BIN
graphics/efxskill/efxskill_14.png
Normal file
After Width: | Height: | Size: 843 B |
BIN
graphics/efxskill/efxskill_15.png
Normal file
After Width: | Height: | Size: 752 B |
BIN
graphics/efxskill/efxskill_16.png
Normal file
After Width: | Height: | Size: 458 B |
BIN
graphics/efxskill/efxskill_2.png
Normal file
After Width: | Height: | Size: 258 B |
BIN
graphics/efxskill/efxskill_3.png
Normal file
After Width: | Height: | Size: 298 B |
BIN
graphics/efxskill/efxskill_4.png
Normal file
After Width: | Height: | Size: 357 B |
BIN
graphics/efxskill/efxskill_5.png
Normal file
After Width: | Height: | Size: 449 B |
BIN
graphics/efxskill/efxskill_6.png
Normal file
After Width: | Height: | Size: 582 B |
BIN
graphics/efxskill/efxskill_7.png
Normal file
After Width: | Height: | Size: 809 B |
BIN
graphics/efxskill/efxskill_8.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
graphics/efxskill/efxskill_9.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
109
scripts/dump_img.py
Executable file
@ -0,0 +1,109 @@
|
||||
#!/bin/python3
|
||||
|
||||
import sys, struct, os
|
||||
import array
|
||||
from PIL import Image
|
||||
import lzss_lib
|
||||
|
||||
def read_palette_from_binary(pal_bytes):
|
||||
palette = []
|
||||
palette_len = len(pal_bytes)
|
||||
for i in range(0, palette_len, 2):
|
||||
color = struct.unpack('<H', pal_bytes[i:i+2])[0]
|
||||
r = (color & 0x1F) << 3
|
||||
g = ((color >> 5) & 0x1F) << 3
|
||||
b = ((color >> 10) & 0x1F) << 3
|
||||
|
||||
palette.append(r)
|
||||
palette.append(g)
|
||||
palette.append(b)
|
||||
|
||||
return palette
|
||||
|
||||
def create_image_from_4bpp(img_data, tsa_data, pal_bytes, ntiles_x, ntiles_y):
|
||||
|
||||
width = ntiles_x * 8
|
||||
height = ntiles_y * 8
|
||||
img = Image.new('P', (width, height))
|
||||
|
||||
pal_data = read_palette_from_binary(pal_bytes)
|
||||
|
||||
tiles_8x8 = []
|
||||
|
||||
pixels = [0] * (width * height)
|
||||
|
||||
# step1: generate tiles
|
||||
for tile_idx in range(len(img_data) // 0x20):
|
||||
_tile = []
|
||||
|
||||
for y in range(8):
|
||||
for x in range(0, 8, 2):
|
||||
offset = tile_idx * (8 * 8 // 2) + y * (8 // 2) + (x // 2)
|
||||
|
||||
byte = img_data[offset]
|
||||
|
||||
pixel1 = byte & 0x0F
|
||||
pixel2 = (byte >> 4) & 0x0F
|
||||
|
||||
_tile.append(pixel1)
|
||||
_tile.append(pixel2)
|
||||
|
||||
tiles_8x8.append(_tile)
|
||||
|
||||
# apply TSA
|
||||
for tile_idx in range(ntiles_x * ntiles_y):
|
||||
base_y = tile_idx // ntiles_x
|
||||
base_x = tile_idx % ntiles_x
|
||||
|
||||
tsa_idx = tsa_data[tile_idx]
|
||||
tile = tiles_8x8[tsa_idx]
|
||||
|
||||
for y in range(8):
|
||||
for x in range(0, 8):
|
||||
offset = y * (8 // 2) + (x // 2)
|
||||
|
||||
real_x = x + base_x * 8
|
||||
real_y = y + base_y * 8
|
||||
|
||||
pixels[real_x + 0 + real_y * width] = tile[y * 8 + x]
|
||||
|
||||
img.putpalette(pal_data)
|
||||
img.putdata(pixels)
|
||||
return img
|
||||
|
||||
def dump_img(prefix, img_addr, tsa_addr, pal_addr, ntiles_x, ntiles_y):
|
||||
img_addr &= 0x00FFFFFF
|
||||
tsa_addr &= 0x00FFFFFF
|
||||
pal_addr &= 0x00FFFFFF
|
||||
|
||||
pal_bytes = lzss_lib.copy_direct(pal_addr, 0x20)
|
||||
img_bytes = lzss_lib.lz77_decomp_data(img_addr)
|
||||
tsa_bytes = lzss_lib.lz77_decomp_data(tsa_addr)
|
||||
|
||||
img_data = array.array('B', img_bytes)
|
||||
|
||||
tsa_data = []
|
||||
for i in range(0, len(tsa_bytes), 2):
|
||||
tsa = struct.unpack('<H', tsa_bytes[i:i+2])[0]
|
||||
tsa_data.append(tsa)
|
||||
|
||||
img = create_image_from_4bpp(img_data, tsa_data, pal_bytes, ntiles_x, ntiles_y)
|
||||
|
||||
fpath = f"{prefix}.png"
|
||||
img.save(fpath)
|
||||
|
||||
def main(args):
|
||||
try:
|
||||
prefix = args[1]
|
||||
img_addr = eval(args[2]) & 0x00FFFFFF
|
||||
tsa_addr = eval(args[3]) & 0x00FFFFFF
|
||||
pal_addr = eval(args[4]) & 0x00FFFFFF
|
||||
ntiles_x = eval(args[5])
|
||||
ntiles_y = eval(args[6])
|
||||
except IndexError:
|
||||
sys.exit(f"Usage: {args[0]} [prefix] [img_addr] [tsa_addr] [pal_addr] [ntiles_x] [ntiles_y]")
|
||||
|
||||
dump_img(prefix, img_addr, tsa_addr, pal_addr, ntiles_x, ntiles_y)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv)
|
20
scripts/dump_img_efxskill.py
Executable file
@ -0,0 +1,20 @@
|
||||
#!/bin/python3
|
||||
|
||||
import sys
|
||||
from dump_img import dump_img
|
||||
|
||||
rom = "baserom.gba"
|
||||
ImgLut_EfxSkill = 0x5d9370
|
||||
TsaLut_EfxSkill = 0x5d9330
|
||||
PalLut_EfxSkill = 0x5d93b0
|
||||
|
||||
with open(rom, 'rb') as f:
|
||||
rom_data = f.read()
|
||||
|
||||
for i in range(11, 16):
|
||||
img_addr = int.from_bytes(rom_data[ImgLut_EfxSkill + 4 * i:ImgLut_EfxSkill + 4 * i + 4], 'little')
|
||||
tsa_addr = int.from_bytes(rom_data[TsaLut_EfxSkill + 4 * i:TsaLut_EfxSkill + 4 * i + 4], 'little')
|
||||
pal_addr = int.from_bytes(rom_data[PalLut_EfxSkill + 4 * i:PalLut_EfxSkill + 4 * i + 4], 'little')
|
||||
|
||||
print(f"IMG 0x{img_addr:08X}, TSA 0x{tsa_addr:08X}, PAL 0x{pal_addr:08X}")
|
||||
dump_img(f"graphics/efxskill/efxskill_{i + 1}", img_addr, tsa_addr, pal_addr, 30, 20)
|
255
scripts/lzss_lib.py
Normal file
@ -0,0 +1,255 @@
|
||||
#!/bin/python3
|
||||
|
||||
from sys import stderr
|
||||
|
||||
from collections import defaultdict
|
||||
from operator import itemgetter
|
||||
from struct import pack, unpack
|
||||
|
||||
ROM="baserom.gba"
|
||||
|
||||
def lz77_decompress(src):
|
||||
if src[0] != 0x10:
|
||||
raise ValueError("Not a valid GBA LZ77 compressed stream.")
|
||||
|
||||
dest_size = (src[1] | (src[2] << 8) | (src[3] << 16))
|
||||
dest = [0] * dest_size
|
||||
|
||||
src_pos = 4
|
||||
dest_pos = 0
|
||||
|
||||
while True:
|
||||
if src_pos >= len(src):
|
||||
raise ValueError("overflow")
|
||||
|
||||
flags = src[src_pos]
|
||||
src_pos += 1
|
||||
|
||||
for i in range(8):
|
||||
if flags & 0x80: # compressed blocks
|
||||
if src_pos + 1 >= len(src):
|
||||
raise ValueError("overflow in flags")
|
||||
|
||||
block_size = (src[src_pos] >> 4) + 3
|
||||
block_distance = (((src[src_pos] & 0xF) << 8) | src[src_pos + 1]) + 1
|
||||
|
||||
src_pos += 2
|
||||
|
||||
block_pos = dest_pos - block_distance
|
||||
|
||||
if block_pos < 0:
|
||||
raise ValueError("invalid distance")
|
||||
|
||||
if dest_pos + block_size > dest_size:
|
||||
block_size = dest_size - dest_pos
|
||||
print("Destination buffer overflow. Truncating block size.")
|
||||
|
||||
for j in range(block_size):
|
||||
dest[dest_pos] = dest[block_pos + j]
|
||||
dest_pos += 1
|
||||
|
||||
else: # uncompressed blocks
|
||||
if src_pos >= len(src) or dest_pos >= dest_size:
|
||||
raise ValueError("overflow in uncompressed blocks")
|
||||
|
||||
dest[dest_pos] = src[src_pos]
|
||||
src_pos += 1
|
||||
dest_pos += 1
|
||||
|
||||
if dest_pos == dest_size:
|
||||
return bytes(dest)
|
||||
|
||||
flags <<= 1
|
||||
|
||||
def lz77_decomp_data(offset):
|
||||
offset = offset & 0x00FFFFFF
|
||||
with open(ROM, "rb") as f:
|
||||
f.seek(offset)
|
||||
return lz77_decompress(f.read())
|
||||
|
||||
def copy_direct(offset, len):
|
||||
offset = offset & 0x00FFFFFF
|
||||
with open(ROM, "rb") as f:
|
||||
f.seek(offset)
|
||||
return f.read(len)
|
||||
|
||||
class SlidingWindow:
|
||||
# The size of the sliding window
|
||||
size = 4096
|
||||
|
||||
# The minimum displacement.
|
||||
disp_min = 2
|
||||
|
||||
# The hard minimum — a disp less than this can't be represented in the
|
||||
# compressed stream.
|
||||
disp_start = 1
|
||||
|
||||
# The minimum length for a successful match in the window
|
||||
match_min = 1
|
||||
|
||||
# The maximum length of a successful match, inclusive.
|
||||
match_max = None
|
||||
|
||||
def __init__(self, buf):
|
||||
self.data = buf
|
||||
self.hash = defaultdict(list)
|
||||
self.full = False
|
||||
|
||||
self.start = 0
|
||||
self.stop = 0
|
||||
#self.index = self.disp_min - 1
|
||||
self.index = 0
|
||||
|
||||
assert self.match_max is not None
|
||||
|
||||
def next(self):
|
||||
if self.index < self.disp_start - 1:
|
||||
self.index += 1
|
||||
return
|
||||
|
||||
if self.full:
|
||||
olditem = self.data[self.start]
|
||||
assert self.hash[olditem][0] == self.start
|
||||
self.hash[olditem].pop(0)
|
||||
|
||||
item = self.data[self.stop]
|
||||
self.hash[item].append(self.stop)
|
||||
self.stop += 1
|
||||
self.index += 1
|
||||
|
||||
if self.full:
|
||||
self.start += 1
|
||||
else:
|
||||
if self.size <= self.stop:
|
||||
self.full = True
|
||||
|
||||
def advance(self, n=1):
|
||||
for _ in range(n):
|
||||
self.next()
|
||||
|
||||
def search(self):
|
||||
match_max = self.match_max
|
||||
match_min = self.match_min
|
||||
|
||||
counts = []
|
||||
indices = self.hash[self.data[self.index]]
|
||||
for i in indices:
|
||||
matchlen = self.match(i, self.index)
|
||||
if matchlen >= match_min:
|
||||
disp = self.index - i
|
||||
#assert self.index - disp >= 0
|
||||
#assert self.disp_min <= disp < self.size + self.disp_min
|
||||
if self.disp_min <= disp:
|
||||
counts.append((matchlen, -disp))
|
||||
if matchlen >= match_max:
|
||||
#assert matchlen == match_max
|
||||
return counts[-1]
|
||||
|
||||
if counts:
|
||||
match = max(counts, key=itemgetter(0))
|
||||
return match
|
||||
|
||||
return None
|
||||
|
||||
def match(self, start, bufstart):
|
||||
size = self.index - start
|
||||
|
||||
if size == 0:
|
||||
return 0
|
||||
|
||||
matchlen = 0
|
||||
it = range(min(len(self.data) - bufstart, self.match_max))
|
||||
for i in it:
|
||||
if self.data[start + (i % size)] == self.data[bufstart + i]:
|
||||
matchlen += 1
|
||||
else:
|
||||
break
|
||||
return matchlen
|
||||
|
||||
class NLZ10Window(SlidingWindow):
|
||||
size = 4096
|
||||
|
||||
match_min = 3
|
||||
match_max = 3 + 0xf
|
||||
|
||||
class NLZ11Window(SlidingWindow):
|
||||
size = 4096
|
||||
|
||||
match_min = 3
|
||||
match_max = 0x111 + 0xFFFF
|
||||
|
||||
class NOverlayWindow(NLZ10Window):
|
||||
disp_min = 3
|
||||
|
||||
def _compress(input, windowclass=NLZ10Window):
|
||||
|
||||
window = windowclass(input)
|
||||
|
||||
i = 0
|
||||
while True:
|
||||
if len(input) <= i:
|
||||
break
|
||||
match = window.search()
|
||||
if match:
|
||||
yield match
|
||||
#if match[1] == -283:
|
||||
# raise Exception(match, i)
|
||||
window.advance(match[0])
|
||||
i += match[0]
|
||||
else:
|
||||
yield input[i]
|
||||
window.next()
|
||||
i += 1
|
||||
|
||||
def packflags(flags):
|
||||
n = 0
|
||||
for i in range(8):
|
||||
n <<= 1
|
||||
try:
|
||||
if flags[i]:
|
||||
n |= 1
|
||||
except IndexError:
|
||||
pass
|
||||
return n
|
||||
|
||||
def chunkit(it, n):
|
||||
buf = []
|
||||
for x in it:
|
||||
buf.append(x)
|
||||
if n <= len(buf):
|
||||
yield buf
|
||||
buf = []
|
||||
if buf:
|
||||
yield buf
|
||||
|
||||
|
||||
def lz77_compress(input):
|
||||
# header
|
||||
out = b''
|
||||
out += (pack("<L", (len(input) << 8) + 0x10))
|
||||
|
||||
# body
|
||||
length = 0
|
||||
for tokens in chunkit(_compress(input), 8):
|
||||
flags = [type(t) == tuple for t in tokens]
|
||||
out += (pack(">B", packflags(flags)))
|
||||
|
||||
for t in tokens:
|
||||
if type(t) == tuple:
|
||||
count, disp = t
|
||||
count -= 3
|
||||
disp = (-disp) - 1
|
||||
assert 0 <= disp < 4096
|
||||
sh = (count << 12) | disp
|
||||
out += (pack(">H", sh))
|
||||
else:
|
||||
out += (pack(">B", t))
|
||||
|
||||
length += 1
|
||||
length += sum(2 if f else 1 for f in flags)
|
||||
|
||||
# padding
|
||||
# padding = 4 - (length % 4 or 4)
|
||||
# if padding:
|
||||
# out += (b'\x00' * padding)
|
||||
return out
|
31
test.py
Normal file
@ -0,0 +1,31 @@
|
||||
from PIL import Image
|
||||
import sys, re
|
||||
|
||||
def reduce_palette(image_path, output_path):
|
||||
# 打开原始图像
|
||||
image = Image.open(image_path)
|
||||
|
||||
# 确保图像是调色板模式
|
||||
if image.mode != 'P':
|
||||
raise ValueError("Image must be in 'P' mode (palette mode)")
|
||||
|
||||
# 获取原始调色板
|
||||
palette = image.getpalette()
|
||||
|
||||
# 只保留前 16 个颜色
|
||||
new_palette = palette[:16 * 3] # 每个颜色有三个值(R, G, B)
|
||||
|
||||
# 创建新的图像
|
||||
new_image = Image.new('P', image.size)
|
||||
|
||||
# 将新调色板应用到新图像
|
||||
new_image.putpalette(new_palette)
|
||||
|
||||
# 重新调色,确保使用新调色板
|
||||
new_image.putdata(image.getdata())
|
||||
|
||||
# 保存新的图像
|
||||
new_image.save(output_path)
|
||||
|
||||
# 示例使用
|
||||
reduce_palette(f'{sys.argv[1]}.png', f'{sys.argv[1]}.png')
|
22
tools/gfxtools/lzss_compress.py
Executable file
@ -0,0 +1,22 @@
|
||||
#!/bin/python3
|
||||
|
||||
import sys
|
||||
import lzss_lib
|
||||
|
||||
def main(args):
|
||||
try:
|
||||
in_fpath = args[1]
|
||||
out_fpath = args[2]
|
||||
|
||||
except IndexError:
|
||||
sys.exit(f"Usage: {args[0]} <input> <output>")
|
||||
|
||||
with open(in_fpath, 'rb') as f:
|
||||
bin_data = f.read()
|
||||
|
||||
with open(out_fpath, "wb") as f:
|
||||
compressed_data = lzss_lib.lz77_compress(bin_data)
|
||||
f.write(compressed_data)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv)
|
18
tools/gfxtools/lzss_decompress.py
Executable file
@ -0,0 +1,18 @@
|
||||
#!/bin/python3
|
||||
|
||||
import sys
|
||||
import lzss_lib
|
||||
|
||||
def main(args):
|
||||
try:
|
||||
offset = eval(args[1])
|
||||
out_fpath = args[2]
|
||||
|
||||
except IndexError:
|
||||
sys.exit(f"Usage: {args[0]} <offset> <output>")
|
||||
|
||||
with open(out_fpath, "wb") as f:
|
||||
f.write(lzss_lib.lz77_decomp_data(offset))
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv)
|
255
tools/gfxtools/lzss_lib.py
Normal file
@ -0,0 +1,255 @@
|
||||
#!/bin/python3
|
||||
|
||||
from sys import stderr
|
||||
|
||||
from collections import defaultdict
|
||||
from operator import itemgetter
|
||||
from struct import pack, unpack
|
||||
|
||||
import rom_def
|
||||
|
||||
def lz77_decompress(src):
|
||||
if src[0] != 0x10:
|
||||
raise ValueError("Not a valid GBA LZ77 compressed stream.")
|
||||
|
||||
dest_size = (src[1] | (src[2] << 8) | (src[3] << 16))
|
||||
dest = [0] * dest_size
|
||||
|
||||
src_pos = 4
|
||||
dest_pos = 0
|
||||
|
||||
while True:
|
||||
if src_pos >= len(src):
|
||||
raise ValueError("overflow")
|
||||
|
||||
flags = src[src_pos]
|
||||
src_pos += 1
|
||||
|
||||
for i in range(8):
|
||||
if flags & 0x80: # compressed blocks
|
||||
if src_pos + 1 >= len(src):
|
||||
raise ValueError("overflow in flags")
|
||||
|
||||
block_size = (src[src_pos] >> 4) + 3
|
||||
block_distance = (((src[src_pos] & 0xF) << 8) | src[src_pos + 1]) + 1
|
||||
|
||||
src_pos += 2
|
||||
|
||||
block_pos = dest_pos - block_distance
|
||||
|
||||
if block_pos < 0:
|
||||
raise ValueError("invalid distance")
|
||||
|
||||
if dest_pos + block_size > dest_size:
|
||||
block_size = dest_size - dest_pos
|
||||
print("Destination buffer overflow. Truncating block size.")
|
||||
|
||||
for j in range(block_size):
|
||||
dest[dest_pos] = dest[block_pos + j]
|
||||
dest_pos += 1
|
||||
|
||||
else: # uncompressed blocks
|
||||
if src_pos >= len(src) or dest_pos >= dest_size:
|
||||
raise ValueError("overflow in uncompressed blocks")
|
||||
|
||||
dest[dest_pos] = src[src_pos]
|
||||
src_pos += 1
|
||||
dest_pos += 1
|
||||
|
||||
if dest_pos == dest_size:
|
||||
return bytes(dest)
|
||||
|
||||
flags <<= 1
|
||||
|
||||
def copy_direct(offset, len):
|
||||
offset = offset & 0x00FFFFFF
|
||||
with open(rom_def.ROM, "rb") as f:
|
||||
f.seek(offset)
|
||||
return f.read(len)
|
||||
|
||||
def lz77_decomp_data(offset):
|
||||
offset = offset & 0x00FFFFFF
|
||||
with open(rom_def.ROM, "rb") as f:
|
||||
f.seek(offset)
|
||||
return lz77_decompress(f.read())
|
||||
|
||||
class SlidingWindow:
|
||||
# The size of the sliding window
|
||||
size = 4096
|
||||
|
||||
# The minimum displacement.
|
||||
disp_min = 2
|
||||
|
||||
# The hard minimum — a disp less than this can't be represented in the
|
||||
# compressed stream.
|
||||
disp_start = 1
|
||||
|
||||
# The minimum length for a successful match in the window
|
||||
match_min = 1
|
||||
|
||||
# The maximum length of a successful match, inclusive.
|
||||
match_max = None
|
||||
|
||||
def __init__(self, buf):
|
||||
self.data = buf
|
||||
self.hash = defaultdict(list)
|
||||
self.full = False
|
||||
|
||||
self.start = 0
|
||||
self.stop = 0
|
||||
#self.index = self.disp_min - 1
|
||||
self.index = 0
|
||||
|
||||
assert self.match_max is not None
|
||||
|
||||
def next(self):
|
||||
if self.index < self.disp_start - 1:
|
||||
self.index += 1
|
||||
return
|
||||
|
||||
if self.full:
|
||||
olditem = self.data[self.start]
|
||||
assert self.hash[olditem][0] == self.start
|
||||
self.hash[olditem].pop(0)
|
||||
|
||||
item = self.data[self.stop]
|
||||
self.hash[item].append(self.stop)
|
||||
self.stop += 1
|
||||
self.index += 1
|
||||
|
||||
if self.full:
|
||||
self.start += 1
|
||||
else:
|
||||
if self.size <= self.stop:
|
||||
self.full = True
|
||||
|
||||
def advance(self, n=1):
|
||||
for _ in range(n):
|
||||
self.next()
|
||||
|
||||
def search(self):
|
||||
match_max = self.match_max
|
||||
match_min = self.match_min
|
||||
|
||||
counts = []
|
||||
indices = self.hash[self.data[self.index]]
|
||||
for i in indices:
|
||||
matchlen = self.match(i, self.index)
|
||||
if matchlen >= match_min:
|
||||
disp = self.index - i
|
||||
#assert self.index - disp >= 0
|
||||
#assert self.disp_min <= disp < self.size + self.disp_min
|
||||
if self.disp_min <= disp:
|
||||
counts.append((matchlen, -disp))
|
||||
if matchlen >= match_max:
|
||||
#assert matchlen == match_max
|
||||
return counts[-1]
|
||||
|
||||
if counts:
|
||||
match = max(counts, key=itemgetter(0))
|
||||
return match
|
||||
|
||||
return None
|
||||
|
||||
def match(self, start, bufstart):
|
||||
size = self.index - start
|
||||
|
||||
if size == 0:
|
||||
return 0
|
||||
|
||||
matchlen = 0
|
||||
it = range(min(len(self.data) - bufstart, self.match_max))
|
||||
for i in it:
|
||||
if self.data[start + (i % size)] == self.data[bufstart + i]:
|
||||
matchlen += 1
|
||||
else:
|
||||
break
|
||||
return matchlen
|
||||
|
||||
class NLZ10Window(SlidingWindow):
|
||||
size = 4096
|
||||
|
||||
match_min = 3
|
||||
match_max = 3 + 0xf
|
||||
|
||||
class NLZ11Window(SlidingWindow):
|
||||
size = 4096
|
||||
|
||||
match_min = 3
|
||||
match_max = 0x111 + 0xFFFF
|
||||
|
||||
class NOverlayWindow(NLZ10Window):
|
||||
disp_min = 3
|
||||
|
||||
def _compress(input, windowclass=NLZ10Window):
|
||||
|
||||
window = windowclass(input)
|
||||
|
||||
i = 0
|
||||
while True:
|
||||
if len(input) <= i:
|
||||
break
|
||||
match = window.search()
|
||||
if match:
|
||||
yield match
|
||||
#if match[1] == -283:
|
||||
# raise Exception(match, i)
|
||||
window.advance(match[0])
|
||||
i += match[0]
|
||||
else:
|
||||
yield input[i]
|
||||
window.next()
|
||||
i += 1
|
||||
|
||||
def packflags(flags):
|
||||
n = 0
|
||||
for i in range(8):
|
||||
n <<= 1
|
||||
try:
|
||||
if flags[i]:
|
||||
n |= 1
|
||||
except IndexError:
|
||||
pass
|
||||
return n
|
||||
|
||||
def chunkit(it, n):
|
||||
buf = []
|
||||
for x in it:
|
||||
buf.append(x)
|
||||
if n <= len(buf):
|
||||
yield buf
|
||||
buf = []
|
||||
if buf:
|
||||
yield buf
|
||||
|
||||
|
||||
def lz77_compress(input):
|
||||
# header
|
||||
out = b''
|
||||
out += (pack("<L", (len(input) << 8) + 0x10))
|
||||
|
||||
# body
|
||||
length = 0
|
||||
for tokens in chunkit(_compress(input), 8):
|
||||
flags = [type(t) == tuple for t in tokens]
|
||||
out += (pack(">B", packflags(flags)))
|
||||
|
||||
for t in tokens:
|
||||
if type(t) == tuple:
|
||||
count, disp = t
|
||||
count -= 3
|
||||
disp = (-disp) - 1
|
||||
assert 0 <= disp < 4096
|
||||
sh = (count << 12) | disp
|
||||
out += (pack(">H", sh))
|
||||
else:
|
||||
out += (pack(">B", t))
|
||||
|
||||
length += 1
|
||||
length += sum(2 if f else 1 for f in flags)
|
||||
|
||||
# padding
|
||||
# padding = 4 - (length % 4 or 4)
|
||||
# if padding:
|
||||
# out += (b'\x00' * padding)
|
||||
return out
|
16
tools/gfxtools/rom_def.py
Normal file
@ -0,0 +1,16 @@
|
||||
ROM = "baserom.gba"
|
||||
|
||||
BANIM_MODES = {
|
||||
0: "NORMAL_ATK",
|
||||
1: "NORMAL_ATK_PRIORITY_L",
|
||||
2: "CRIT_ATK",
|
||||
3: "CRIT_ATK_PRIORITY_L",
|
||||
4: "RANGED_ATK",
|
||||
5: "RANGED_CRIT_ATK",
|
||||
6: "CLOSE_DODGE",
|
||||
7: "RANGED_DODGE",
|
||||
8: "STANDING",
|
||||
9: "STANDING2",
|
||||
10: "RANGED_STANDING",
|
||||
11: "MISSED_ATK"
|
||||
}
|
47
tools/gfxtools/tsa_analysis.py
Executable file
@ -0,0 +1,47 @@
|
||||
#!/bin/python3
|
||||
|
||||
import sys, struct
|
||||
from collections import Counter
|
||||
import lzss_lib
|
||||
|
||||
def count_and_sort_numbers(numbers):
|
||||
counts = Counter(numbers)
|
||||
sorted_counts = sorted(counts.items())
|
||||
return sorted_counts
|
||||
|
||||
def main(args):
|
||||
try:
|
||||
offset = eval(args[1])
|
||||
width = eval(args[2])
|
||||
height = eval(args[3])
|
||||
|
||||
except IndexError:
|
||||
sys.exit(f"Usage: {args[0]} <offset> <width> <height>")
|
||||
|
||||
decomped_data = lzss_lib.lz77_decomp_data(offset)
|
||||
numbers = [(struct.unpack('<H', decomped_data[i:i+2])[0]) for i in range(0, len(decomped_data), 2)]
|
||||
|
||||
|
||||
n_cols = width // 8
|
||||
|
||||
for col in range(n_cols):
|
||||
x_start = 8 * col
|
||||
x_end = 8 * col + 8
|
||||
print(f"[col: {col}]")
|
||||
|
||||
for y in range(height):
|
||||
for x in range(x_start, x_end):
|
||||
idx = y * width + x
|
||||
# print(f"[{x}, {y}] = ", end="")
|
||||
tile = numbers[idx]
|
||||
tile_0 = numbers[0]
|
||||
if tile == tile_0:
|
||||
# print(" ", end="")
|
||||
print(f" {numbers[idx]:04X}", end=" ")
|
||||
else:
|
||||
print(f"0x{numbers[idx]:04X}", end=" ")
|
||||
|
||||
print("")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv)
|
45
tools/gfxtools/tsa_bin_analysis.py
Executable file
@ -0,0 +1,45 @@
|
||||
#!/bin/python3
|
||||
|
||||
import sys, struct
|
||||
from collections import Counter
|
||||
|
||||
def read_bin_file(filename):
|
||||
with open(filename, 'rb') as f:
|
||||
data = f.read()
|
||||
numbers = [(struct.unpack('<H', data[i:i+2])[0] & 0x3FF) for i in range(0, len(data), 2)]
|
||||
return numbers
|
||||
|
||||
def main(args):
|
||||
try:
|
||||
filename = args[1]
|
||||
width = eval(args[2])
|
||||
height = eval(args[3])
|
||||
|
||||
except IndexError:
|
||||
sys.exit(f"Usage: {args[0]} <offset> <width> <height>")
|
||||
|
||||
numbers = read_bin_file(filename)
|
||||
|
||||
n_cols = width // 8
|
||||
|
||||
for col in range(n_cols):
|
||||
x_start = 8 * col
|
||||
x_end = 8 * col + 8
|
||||
print(f"[col: {col}]")
|
||||
|
||||
for y in range(height):
|
||||
for x in range(x_start, x_end):
|
||||
idx = y * width + x
|
||||
# print(f"[{x}, {y}] = ", end="")
|
||||
tile = numbers[idx]
|
||||
tile_0 = numbers[0]
|
||||
if tile == tile_0:
|
||||
# print(" ", end="")
|
||||
print(f" {numbers[idx]:04X}", end=" ")
|
||||
else:
|
||||
print(f"0x{numbers[idx]:04X}", end=" ")
|
||||
|
||||
print("")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv)
|
147
tools/gfxtools/tsa_generator.py
Executable file
@ -0,0 +1,147 @@
|
||||
#!/bin/python3
|
||||
|
||||
import sys, re
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
'''
|
||||
方案1:
|
||||
常见于 banim 相关的图片
|
||||
1. 整张图按行从左向右扫描, 将获得的独特的 tile 入栈, 从而获得 tile array
|
||||
2. 上述获得的第一个 tile 丢到末尾作为最后一个 tile
|
||||
3. tile array 第 0 个 tile 置空
|
||||
'''
|
||||
def process_tiles_method1(tiles, ntile_x, ntile_y):
|
||||
unique_tiles = []
|
||||
tsa_data = []
|
||||
tile_dict = {}
|
||||
|
||||
for tile_y in range(0, ntile_y):
|
||||
for tile_x in range(0, ntile_x):
|
||||
tile = tiles[tile_y][tile_x]
|
||||
|
||||
tile_4bpp = convert_to_4bpp(tile)
|
||||
tile_tuple = tuple(tile_4bpp)
|
||||
|
||||
tile_2d = tile.reshape((8, 8))
|
||||
tile_hf = np.flip(tile_2d, axis=1).flatten()
|
||||
tile_vf = np.flip(tile_2d, axis=0).flatten()
|
||||
tile_se = np.flip(tile_2d).flatten()
|
||||
|
||||
tile_hf_4bpp = convert_to_4bpp(tile_hf)
|
||||
tile_hf_tuple = tuple(tile_hf_4bpp)
|
||||
|
||||
tile_vf_4bpp = convert_to_4bpp(tile_vf)
|
||||
tile_vf_tuple = tuple(tile_vf_4bpp)
|
||||
|
||||
tile_se_4bpp = convert_to_4bpp(tile_se)
|
||||
tile_se_tuple = tuple(tile_se_4bpp)
|
||||
|
||||
if tile_tuple in tile_dict:
|
||||
tsa_data.append(tile_dict[tile_tuple])
|
||||
elif tile_hf_tuple in tile_dict:
|
||||
tsa_data.append(tile_dict[tile_hf_tuple] | (1 << 10))
|
||||
elif tile_vf_tuple in tile_dict:
|
||||
tsa_data.append(tile_dict[tile_vf_tuple] | (2 << 10))
|
||||
elif tile_se_tuple in tile_dict:
|
||||
tsa_data.append(tile_dict[tile_se_tuple] | (3 << 10))
|
||||
else:
|
||||
# fine, we did not find it
|
||||
unique_tiles.append(tile_4bpp)
|
||||
tile_index = len(unique_tiles) - 1
|
||||
tile_dict[tile_tuple] = tile_index
|
||||
tsa_data.append(tile_index)
|
||||
|
||||
# put the first tile to the tale
|
||||
last_idx = len(unique_tiles)
|
||||
print(last_idx)
|
||||
for i, tsa in enumerate(tsa_data):
|
||||
if tsa == 0:
|
||||
tsa_data[i] = last_idx
|
||||
|
||||
unique_tiles.append(unique_tiles[0])
|
||||
unique_tiles[0] = [0] * 32
|
||||
|
||||
return unique_tiles, tsa_data
|
||||
|
||||
'''
|
||||
方案2: (todo)
|
||||
1. 以 8 个 tile 为一行,将图片分割为多个列
|
||||
2. 按照方案1的方式扫描所有列
|
||||
'''
|
||||
|
||||
def extract_tiles(image, ntile_x, ntile_y):
|
||||
tiles = [[None for _ in range(ntile_x)] for _ in range(ntile_y)]
|
||||
|
||||
for tile_y in range(0, ntile_y):
|
||||
for tile_x in range(0, ntile_x):
|
||||
x = tile_x * 8
|
||||
y = tile_y * 8
|
||||
|
||||
tile = image.crop((x, y, x + 8, y + 8))
|
||||
tile_data = np.array(tile).flatten()
|
||||
|
||||
tiles[tile_y][tile_x] = tile_data
|
||||
|
||||
return tiles
|
||||
|
||||
def convert_to_4bpp(tile):
|
||||
result = []
|
||||
for i in range(0, len(tile), 2):
|
||||
byte = (tile[i] & 0xF) | ((tile[i + 1] & 0xF) << 4)
|
||||
result.append(byte)
|
||||
return result
|
||||
|
||||
def extract_suffix_from_filename(file_name):
|
||||
match = re.search(r'\.(fetsa(\d+)\.bin)$', file_name)
|
||||
if match:
|
||||
return match.group(2)
|
||||
return None
|
||||
|
||||
def main(args):
|
||||
try:
|
||||
png_file = args[1]
|
||||
out_img = args[2]
|
||||
out_tsa = args[3]
|
||||
except IndexError:
|
||||
sys.exit(f"Usage: {args[0]} [*.png] [*.feimg.bin] [*.fetsa<x>.bin]")
|
||||
|
||||
method = extract_suffix_from_filename(out_tsa)
|
||||
if method is None:
|
||||
method = 0 # default
|
||||
|
||||
image = Image.open(png_file)
|
||||
if image.mode != 'P':
|
||||
raise ValueError("IMAGE ERRIR (P mode)")
|
||||
|
||||
width, height = image.size
|
||||
|
||||
ntile_x = width // 8
|
||||
ntile_y = height // 8
|
||||
|
||||
tiles = extract_tiles(image, ntile_x, ntile_y)
|
||||
|
||||
if method == 1:
|
||||
unique_tiles, tsa_data = process_tiles_method1(tiles, ntile_x, ntile_y)
|
||||
else:
|
||||
# todo
|
||||
unique_tiles, tsa_data = process_tiles_method1(tiles, ntile_x, ntile_y)
|
||||
|
||||
with open(out_img, 'wb') as f:
|
||||
cnt = 0
|
||||
for tile in unique_tiles:
|
||||
cnt += 1
|
||||
f.write(bytearray(tile))
|
||||
|
||||
if cnt > 0x100:
|
||||
raise ValueError("Compressed image overflowed!")
|
||||
|
||||
for i in range(cnt, 0x100):
|
||||
f.write(b'\x00' * 32)
|
||||
|
||||
with open(out_tsa, 'wb') as f:
|
||||
for entry in tsa_data:
|
||||
f.write(entry.to_bytes(2, byteorder='little'))
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv)
|