Oyunlardan ayıklanan bazı PNG dosyaları neden yanlış görüntüleniyor?


14

Bazı oyun dosyalarından PNG'leri ayıklayarak görüntünün bozulduğunu fark ettim. Örneğin, Skyrim'deki Dokular dosyasından çıkarılan birkaç PNG:

Skyrim'den Işıklı J PNG Skyrim'den Işıklı K PNG

Bu bir PNG biçiminde olağandışı bir varyasyon mu? Bu PNG'leri düzgün bir şekilde görüntülemek için hangi değişiklikleri yapmam gerekir?


1
Belki de insanların böyle şeyler yapmasını önlemek için dosyalarına bazı özel kodlamalar koymuşlardır. Ya da çıkarmak için kullandığınız her şey düzgün çalışmıyor olabilir.
Richard Marskell - Drackir

Belki görüntüleri dosya boyutunda küçültmek bir tür sıkıştırmadır. Bu, iPhone uygulamalarında da yapılır.
sağda

1
Biraz konu dışı, ama bu bir midilli mi?
jcora

Yanıtlar:


22

İşte tillberg'in daha fazla araştırması sayesinde “restore edilmiş” görüntüler:

final1 final2

Beklendiği gibi, her 0x4020 baytta bir 5 baytlık blok işaretleyici vardır . Biçim şu şekildedir:

struct marker {
    uint8_t tag;  /* 1 if this is the last marker in the file, 0 otherwise */
    uint16_t len; /* size of the following block (little-endian) */
    uint16_t notlen; /* 0xffff - len */
};

İşaretçi okunduktan sonra, sonraki marker.lenbaytlar dosyanın parçası olan bir blok oluşturur. marker.notlenkontrol değişkenidir marker.len + marker.notlen == 0xffff. Son blok öyle marker.tag == 1.

Yapı muhtemelen aşağıdaki gibidir. Hala bilinmeyen değerler var.

struct file {
    uint8_t name_len;    /* number of bytes in the filename */
                         /* (not sure whether it's uint8_t or uint16_t) */
    char name[name_len]; /* filename */
    uint32_t file_len;   /* size of the file (little endian) */
                         /* eg. "40 25 01 00" is 0x12540 bytes */
    uint16_t unknown;    /* maybe a checksum? */

    marker marker1;             /* first block marker (tag == 0) */
    uint8_t data1[marker1.len]; /* data of the first block */
    marker marker2;             /* second block marker (tag == 0) */
    uint8_t data2[marker2.len]; /* data of the second block */
    /* ... */
    marker lastmarker;                /* last block marker (tag == 1) */
    uint8_t lastdata[lastmarker.len]; /* data of the last block */

    uint32_t unknown2; /* end data? another checksum? */
};

Sonunda ne olduğunu anlayamadım, ancak PNG'ler dolguyu kabul ettiğinden, çok dramatik değil. Ancak, kodlanmış dosya boyutu açıkça son 4 baytın göz ardı edilmesi gerektiğini gösterir ...

Dosyanın başlangıcından hemen önce tüm blok işaretleyicilerine erişimim olmadığından, sonunda başlayan ve blok işaretleyicilerini bulmaya çalışan bu kod çözücüyü yazdım. Hiç sağlam değil ama iyi, test görüntüleriniz için çalıştı:

#include <stdio.h>
#include <string.h>

#define MAX_SIZE (1024 * 1024)
unsigned char buf[MAX_SIZE];

/* Usage: program infile.png outfile.png */
int main(int argc, char *argv[])
{
    size_t i, len, lastcheck;
    FILE *f = fopen(argv[1], "rb");
    len = fread(buf, 1, MAX_SIZE, f);
    fclose(f);

    /* Start from the end and check validity */
    lastcheck = len;
    for (i = len - 5; i-- > 0; )
    {
        size_t off = buf[i + 2] * 256 + buf[i + 1];
        size_t notoff = buf[i + 4] * 256 + buf[i + 3];
        if (buf[i] >= 2 || off + notoff != 0xffff)
            continue;
        else if (buf[i] == 1 && lastcheck != len)
            continue;
        else if (buf[i] == 0 && i + off + 5 != lastcheck)
            continue;
        lastcheck = i;
        memmove(buf + i, buf + i + 5, len - i - 5);
        len -= 5;
        i -= 5;
    }

    f = fopen(argv[2], "wb+");
    fwrite(buf, 1, len, f);
    fclose(f);

    return 0;
}

Daha eski araştırmalar

Bayt 0x4022ikinci görüntüden kaldırılırken, ardından bayt kaldırılırken elde ettiğiniz budur 0x8092:

orijinal ilk adım ikinci adım

Görüntüleri gerçekten “onarmaz”; Bunu deneme yanılma yoluyla yaptım. Bununla birlikte, her 16384 baytta beklenmedik veriler olduğu söylenir. Tahminim, görüntülerin bir tür dosya sistemi yapısında paketlenmesidir ve beklenmedik veriler sadece blok işaretleyicilerdir verileri okurken kaldırmanız gereken .

Blok işaretleyicilerinin tam olarak nerede olduğunu ve boyutlarını bilmiyorum, ancak blok boyutunun kendisi kesinlikle 2 ^ 14 bayt.

Ayrıca, görüntünün hemen öncesinde ve hemen sonrasında görünenlerin onaltılık dökümünü (birkaç düzine bayt) da sağlamanızda yardımcı olur. Bu, blokların başında veya sonunda ne tür bilgilerin saklandığı hakkında ipuçları verecektir.

Tabii ki çıkarma kodunuzda bir hata da olabilir. Dosya işlemleri için 16384 baytlık bir arabellek kullanıyorsanız, önce orayı kontrol ederim.


+1 çok yardımcı; Bana verdiğiniz ipucuyla bu konuya girmeye devam edeceğim ve bazı ek bilgiler göndereceğim
James Tauber

Katıştırılmış "dosya", dosya adını içeren uzunluk ön ekli bir dize ile başlar; ardından PNG dosyaları için 89 50 4e 47 sihrinden önce 12 bayt gelir. 12 bayt: 40 25 01 00 78 9c 00 2a 40 d5 bf
James Tauber

Workyi iţler, Sam. BSA dosyalarını doğrudan okuyan python kodunu güncelledim. Sonuçlar orbza.s3.amazonaws.com/tillberg/pics.html adresinde görülebilir (Orada sadece görüntülerin 1 / 3'ünü gösteriyorum, sonuçları göstermek için yeterli). Bu, birçok görüntü için işe yarar. Diğer görüntülerle ilgili başka şeyler de var. Ben bunun Fallout 3 veya Skyrim yeniden başka bir yerde çözülmüş olup olmadığını merak ediyorum.
tillberg

Mükemmel iş, çocuklar! Kodumu da güncelleyeceğim
James Tauber

18

Sam'in önerisine dayanarak, James'in kodunu https://github.com/tillberg/skyrim adresinden çatalladım ve Skyrim Textures BSA dosyasından n_letter.png dosyasını başarıyla çıkarabildim.

N harfi

BSA üstbilgileri tarafından verilen "file_size", gerçek son dosya boyutu değildir. Bazı üstbilgi bilgilerinin yanı sıra etrafa dağılmış yararsız görünen verilerin rastgele parçalarını içerir.

Üstbilgiler şöyle görünür:

  • 1 bayt (dosya yolu uzunluğu?)
  • dosyanın tam yolu, karakter başına bir bayt
  • Bilinmeyen 12 bayt, James'in kaydettiği gibi (40 25 01 00 78 9c 00 2a 40 d5 bf).

Üstbilgi baytlarını ayırmak için şunu yaptım:

f.seek(file_offset)
data = f.read(file_size)
header_size = 1 + len(folder_path) + len(filename) + 12
d = data[header_size:]

Oradan, gerçek PNG dosyası başlar. PNG 8 bayt başlangıç ​​dizisinden bunu doğrulamak kolaydır.

PNG başlıklarını okuyarak ve IDAT yığınından geçirilen uzunluğu, IEND yığınına kadar bayt sayısını ölçmekten ima edilen veri uzunluğu ile karşılaştırarak ekstra baytların nerede bulunduğunu anlamaya çalıştım. (bununla ilgili ayrıntılar için github'daki bsa.py dosyasına bakın)

Parçalar tarafından n_letter.png içinde verilen boyutlar:

IHDR: 13 bytes
pHYs: 9 bytes
iCCP: 2639 bytes
cHRM: 32 bytes
IDAT: 60625 bytes
IEND: 0 bytes

Sonra IDAT yığın ve IEND yığın arasındaki gerçek mesafeyi ölçtüğümde (Python'da string.find () kullanarak baytları sayarak), ima edilen gerçek IDAT uzunluğunun 60640 bayt olduğunu buldum - orada fazladan 15 bayt vardı .

Genel olarak, "harf" dosyalarının çoğunda toplam dosya boyutunun her 16 KB'si için fazladan 5 bayt bulunur. Örneğin, 73KB civarında o_letter.png, fazladan 20 bayta sahipti. Arcane scribblings gibi daha büyük dosyalar çoğunlukla aynı kalıbı izledi, ancak bazılarına garip miktarlar eklendi (52 bayt, 12 bayt veya 32 bayt). Orada neler olduğu hakkında hiçbir fikrim yok.

N_letter.png dosyası için, 5 baytlık segmentleri kaldırmak için doğru dengeleri (çoğunlukla deneme yanılma yoluyla) bulabildim.

index = 0x403b
index2 = 0x8070
index3 = 0xc0a0
pngdata = (
  d[0      : (index - 5)] + 
  d[index  : (index2 - 5)] + 
  d[index2 : (index3 - 5)] + 
  d[index3 : ] )
pngfile.write(pngdata)

Kaldırılan beş bayt segmenti:

at 000000: 00 2A 40 D5 BF (<-- included at end of 12 bytes above)
at 00403B: 00 30 40 CF BF
at 008070: 00 2B 40 D4 BF
at 00C0A0: 01 15 37 EA C8

Değeri için, diğer dizilerle bazı benzerlikler nedeniyle bilinmeyen 12 baytlık segmentin son beş baytını dahil ettim.

Her 16KB değil, ~ 0x4030 bayt aralıklarında oldukları ortaya çıkıyor.

Yukarıdaki endekslerde yakın ama mükemmel olmayan eşleşmelere karşı korunmak için, elde edilen PNG'den IDAT yığınının zlib dekompresyonunu da test ettim ve geçer.


"Rastgele bir @ işareti için 1 bayt" dosya adı dizesinin uzunluğu, sanırım
James Tauber

her bir durumda 5 baytlık segmentlerin değeri nedir?
James Tauber

Cevabımı kaldırılan 5 baytlık segmentlerin onaltılık değerleriyle güncelledim. Ayrıca, 5 baytlık segmentlerin sayısı konusunda kendimi karıştırdım (daha önce gizemli 12 bayt üstbilgiyi 7 bayt üstbilgi ve 5 bayt yinelenen bölücü olarak sayıyordum). Ben de düzelttim.
tillberg

(küçük endian) 0x402A, 0x4030, 0x402B'nin bu 5 baytlık segmentlerde göründüğünü unutmayın; bunlar gerçek aralıklar mı?
James Tauber

Zaten bunun mükemmel bir iş olduğunu söylemiştim, ama görünüşe göre yapmadım. Harika iş! :-)
sam hocevar

3

Aslında aralıklı 5 bayt, zlib sıkıştırmasının bir parçasıdır.

Http://drj11.wordpress.com/2007/11/20/a-use-for-uncompressed-pngs/ adresinde ayrıntılı olarak açıklandığı gibi ,

01 son bloğu gösteren küçük endian bit dizisi 1 00 00000. 1, sıkıştırılmamış bir bloğu belirten son blok, 00 ve 00000, bir bloğun başlangıcını sekizlide (sıkıştırılmamış bloklar için gerekli) hizalamak için 5 bitlik dolgudur ve benim için çok uygun). 05 00 fa ff Sıkıştırılmamış bloktaki (5) veri sekizli sayısı. Küçük endian 16 bitlik bir tamsayı ve ardından 1'in tamamlayıcısı (!) Olarak saklanır.

.. böylece 00, bir 'sonraki' bloğu (bir bitiş bloğunu değil) belirtir ve sonraki 4 bayt, blok uzunluğu ve tersidir.

[Düzenle] Daha güvenilir bir kaynak elbette RFC 1951 (Sıkıştırılmış Veri Biçimi Spesifikasyonunu Söndür), bölüm 3.2.4'tür.


1

Dosyadaki verileri ikili mod yerine metin modunda (PNG verilerinde görünen satır sonlarının büyük olasılıkla karıştırıldığı yerlerde) okuyor olabilir misiniz?


1
Evet. Konuya çok benziyor. Bunu okuyan kod göz önüne alındığında: github.com/jtauber/skyrim/blob/master/bsa.py --- teyit :-)
Armin Ronacher

Hayır, fark etmez.
James Tauber

@JamesTauber, eğer kendi PNG yükleyicinizi Armin'in yorumu ima ettiği gibi kodluyorsanız, (a) denediğiniz diğer PNG'lerde çalışıyor ve (b) libpngSkyrim PNG'lerini okumak gibi kanıtlanmış bir PNG yükleyicisi var mı? Başka bir deyişle, bu sadece PNG yükleyicinizdeki bir hata mıdır?
Nathan Reed

@NathanReed yaptığım tek şey bayt akışını ayıklamak ve buraya yüklemek. dahil "yükleyici" yoktur
James Tauber

3
-1, bu sebep olamaz. PNG dosyaları bu şekilde bozulduysa, şişirme aşamasında, görüntü kod çözme aşamasındaki hatalardan çok önce CRC hataları olacaktır. Ayrıca, başlıkta beklenen dosya dışında dosyalarda CRLF oluşumu yoktur.
sam hocevar
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.