首先我们需要知道favorite的立绘通常在一个叫做graph_bs.bin的文件下,用grabro打开可以看到如下内容:

1

2

很明显,主体部分和面部表情是分开的,这种情况我们称之为差分

garbro并不会自动处理差分内容,所以如果要提取人物立绘就得我们自己手动合成

那么如何实现呢,我们先使用010editor打开这个文件看看结构

3

看起来。。。好像什么都看不出来

不用着急,我们先看看最简单的bgm.bin来认识一下*.bin的文件结构

先用使用garbro打开,可以看到,这个bgm.bin文件下共有60个文件

4

使用010editor打开我们可以发现地址0h-4h刚好也等于60

5

接着是一个值大小为240,这个值我们先不用理他

6

现在我们先使用garbro随便提取一个文件,与原bgm.bin文件进行比较

7

你会发现,好家伙原来这文件数据就在.bin文件里面,他甚至不愿意异或一下

起始地址为968,共2723547个字节

接着我们返回原bgm.bin一看,哎这不是968和2723547吗·

8

9

于是我们对前面这一部分就有了一点点了解,但是这里每个文件的第一个数据每次加4,这个4是拿来干什么的的呢?不着急,我们慢慢看。

10

接着,当我们读完这些数据后,会发现之后这个东西

11

wow,这不是文件名吗,还有原来之前的240是指的文件名的长度,那不难猜出前面的4指的就是文件名的大小的,也就是第一块是文件的文件名在文件名块(index)里面的地址

12

接着打开graph_bs.bin验证我们的猜想

13

完全符合,虽然第一个不是0但也对应了第一个文件的文件名所在的地址

14

我们甚至可以知道文件名是以00分割的

这下我们不难写出提取文件的程序了,但问题来了,我们提取出来的是一个.hzc文件啊,相当于garbro提取时的保留原样

15

ffmpeg都无法正确识别这个格式

16

garbro居然可以正确识别,这个时候我们就应该去翻看garbro的源码了

  1//! \file       ArcHZC.cs
  2//! \date       Wed Dec 09 17:04:23 2015
  3//! \brief      Favorite View Point multi-frame image format.
  4//
  5// Copyright (C) 2015 by morkt
  6//
  7// Permission is hereby granted, free of charge, to any person obtaining a copy
  8// of this software and associated documentation files (the "Software"), to
  9// deal in the Software without restriction, including without limitation the
 10// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 11// sell copies of the Software, and to permit persons to whom the Software is
 12// furnished to do so, subject to the following conditions:
 13//
 14// The above copyright notice and this permission notice shall be included in
 15// all copies or substantial portions of the Software.
 16//
 17// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 18// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 19// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 20// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 21// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 22// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 23// IN THE SOFTWARE.
 24//
 25
 26using System;
 27using System.Collections.Generic;
 28using System.ComponentModel.Composition;
 29using System.IO;
 30using GameRes.Utility;
 31using GameRes.Compression;
 32
 33namespace GameRes.Formats.FVP
 34{
 35    internal class HzcArchive : ArcFile
 36    {
 37        public readonly HzcMetaData ImageInfo;
 38
 39        public HzcArchive (ArcView arc, ArchiveFormat impl, ICollection<Entry> dir, HzcMetaData info)
 40            : base (arc, impl, dir)
 41        {
 42            ImageInfo = info;
 43        }
 44    }
 45
 46    [Export(typeof(ArchiveFormat))]
 47    public class HzcOpener : ArchiveFormat
 48    {
 49        public override string         Tag { get { return "HZC/MULTI"; } }
 50        public override string Description { get { return "Favorite View Point multi-frame image"; } }
 51        public override uint     Signature { get { return 0x31637A68; } } // 'HZC1'
 52        public override bool  IsHierarchic { get { return false; } }
 53        public override bool      CanWrite { get { return false; } }
 54
 55        public HzcOpener ()
 56        {
 57            Extensions = new string[] { "hzc" };
 58        }
 59
 60        static readonly Lazy<ImageFormat> Hzc = new Lazy<ImageFormat> (() => ImageFormat.FindByTag ("HZC"));
 61
 62        public override ArcFile TryOpen (ArcView file)
 63        {
 64            uint header_size = file.View.ReadUInt32 (8);
 65            HzcMetaData image_info;
 66            using (var header = file.CreateStream (0, 0xC+header_size))
 67            {
 68                image_info = Hzc.Value.ReadMetaData (header) as HzcMetaData;
 69                if (null == image_info)
 70                    return null;
 71            }
 72            int count = file.View.ReadInt32 (0x20);
 73            if (0 == count)
 74                count = 1;
 75            string base_name = Path.GetFileNameWithoutExtension (file.Name);
 76            int frame_size = image_info.UnpackedSize / count;
 77            var dir = new List<Entry> (count);
 78            for (int i = 0; i < count; ++i)
 79            {
 80                var entry = new Entry {
 81                    Name = string.Format ("{0}#{1:D3}", base_name, i),
 82                    Type = "image",
 83                    Offset = frame_size * i,
 84                    Size = (uint)frame_size,
 85                };
 86                dir.Add (entry);
 87            }
 88            return new HzcArchive (file, this, dir, image_info);
 89        }
 90
 91        public override Stream OpenEntry (ArcFile arc, Entry entry)
 92        {
 93            var hzc = (HzcArchive)arc;
 94            using (var input = arc.File.CreateStream (0xC+hzc.ImageInfo.HeaderSize))
 95            using (var z = new ZLibStream (input, CompressionMode.Decompress))
 96            {
 97                uint frame_size = entry.Size;
 98                var pixels = new byte[frame_size];
 99                uint offset = 0;
100                for (;;)
101                {
102                    if (pixels.Length != z.Read (pixels, 0, pixels.Length))
103                        throw new EndOfStreamException();
104                    if (offset >= entry.Offset)
105                        break;
106                    offset += frame_size;
107                }
108                return new BinMemoryStream (pixels, entry.Name);
109            }
110        }
111
112        public override IImageDecoder OpenImage (ArcFile arc, Entry entry)
113        {
114            var hzc = (HzcArchive)arc;
115            var input = arc.File.CreateStream (0xC+hzc.ImageInfo.HeaderSize);
116            try
117            {
118                return new HzcDecoder (input, hzc.ImageInfo, entry);
119            }
120            catch
121            {
122                input.Dispose();
123                throw;
124            }
125        }
126    }
127}

不难看出,等等?ZLibStream?

17

这下看懂了,原来图像数据是使用zlib进行压缩了

18

有关mode:

0代表背景为rgb,1代表立绘基底为rgba,2代表差分(多个文件)也为rgba

接着那串0000之后就是zlib压缩的数据了,解压之后就可以得到rgba/rgb

19

然后利用opencv直接合成就好

实现代码如下(写的好屎,勿喷):

GitHub: https://github.com/bGlzdGRlcg/unfavbs

unfavbs.h

  1#include <bits/stdc++.h>
  2#include <iostream>
  3#include <fstream>
  4#include <vector>
  5#include <opencv2/opencv.hpp>
  6#include <sstream>
  7#include <zlib.h>
  8#include <windows.h>
  9#include <locale>
 10#include <codecvt>
 11#include <map>
 12#include <io.h>
 13
 14using namespace std;
 15using namespace cv;
 16
 17unsigned int readbinhelf(ifstream &binfile){
 18	unsigned short int val;
 19	binfile.read((char *) &val, 2);
 20	return val;
 21}
 22
 23unsigned int readbin(ifstream &binfile){
 24	unsigned int val;
 25	binfile.read((char *) &val, 4);
 26	return val;
 27}
 28
 29unsigned int readpng(istringstream &pngfile){
 30	unsigned int val;
 31	pngfile.read((char *) &val, 4);
 32	return val;
 33}
 34
 35unsigned int readpnghelf(istringstream &pngfile){
 36	unsigned short int val;
 37	pngfile.read((char *) &val, 2);
 38	return val;
 39}
 40
 41string ShiftJISToUTF8(const string& shiftjis) {
 42    int len = MultiByteToWideChar(932,0,shiftjis.c_str(),-1,NULL,0);
 43    if (len == 0) return "";
 44    wstring wstr(len,0);
 45    MultiByteToWideChar(932,0,shiftjis.c_str(),-1,&wstr[0],len);
 46    len = WideCharToMultiByte(CP_UTF8,0,&wstr[0],-1,NULL,0,NULL,NULL);
 47    if (len == 0) return "";
 48    std::string utf8(len,0);
 49    WideCharToMultiByte(CP_UTF8,0,&wstr[0],-1,&utf8[0],len,NULL,NULL);
 50    return utf8;
 51}
 52
 53string ShiftJISToGBK(const string& shiftjis) {
 54    int len = MultiByteToWideChar(932, 0, shiftjis.c_str(), -1, NULL, 0);
 55    if (len == 0) return "";
 56    std::wstring wstr(len, 0);
 57    MultiByteToWideChar(932, 0, shiftjis.c_str(), -1, &wstr[0], len);
 58    len = WideCharToMultiByte(936, 0, wstr.c_str(), -1, NULL, 0, NULL, NULL);
 59    if (len == 0) return "";
 60    string gbk(len, 0);
 61    WideCharToMultiByte(936, 0, wstr.c_str(), -1, &gbk[0], len, NULL, NULL);
 62    return gbk;
 63}
 64
 65string getbinname(ifstream &binfile){
 66	string str="";
 67	char ch;
 68    while (binfile.get(ch)) {
 69        if (int(ch) == 0) break;
 70        str += ch;
 71    }
 72    return ShiftJISToGBK(str);
 73}
 74
 75bool cmp(int a[],int b[]) {
 76    return a[0] < b[0];
 77}
 78
 79void unbinout(ifstream &binfile,ofstream &outfile, streampos start, streampos end) {
 80    binfile.seekg(start);
 81    char buffer[1024];
 82    streampos remaining = end - start;
 83    while (remaining > 0) {
 84        size_t bytesToRead = min(remaining, static_cast<streampos>(sizeof(buffer)));
 85        binfile.read(buffer, bytesToRead);
 86        outfile.write(buffer, bytesToRead);
 87        remaining -= bytesToRead;
 88    }
 89    outfile.close();
 90}
 91
 92istringstream decompressData(ifstream &inputFile, int startOffset, int uncompressedSize) {
 93    inputFile.seekg(startOffset);
 94    vector<char> buffer(uncompressedSize);
 95    inputFile.read(buffer.data(), uncompressedSize);
 96    vector<char> uncompressedBuffer(uncompressedSize * 2);
 97    uLongf destLen = uncompressedSize * 2;
 98    int result = uncompress((Bytef*)uncompressedBuffer.data(), &destLen, (const Bytef*)buffer.data(), uncompressedSize);
 99    if(result != Z_OK) {
100        cerr<<"Failed to decompress data. Error code: " <<result<<endl;
101        return istringstream();
102        //return "";
103    }
104    string decompressedData(uncompressedBuffer.begin(), uncompressedBuffer.begin() + destLen);
105    //return decompressedData;
106	istringstream iss(decompressedData);
107    return iss;
108}
109
110void overlayImages(const Mat &background, const Mat &foreground, Mat &output, int x, int y){
111    output = background.clone();
112    cv::Rect roi(x, y, foreground.cols, foreground.rows);
113    roi &= cv::Rect(0, 0, background.cols, background.rows);
114    cv::Mat roi_output = output(roi);
115    cv::Mat roi_foreground = foreground(cv::Rect(0, 0, roi.width, roi.height));
116    for (int i = 0; i < roi.height; ++i){
117        for (int j = 0; j < roi.width; ++j){
118            cv::Vec4b pixel_foreground = roi_foreground.at<cv::Vec4b>(i, j);
119            cv::Vec4b &pixel_output = roi_output.at<cv::Vec4b>(i, j);
120            double alpha_foreground = pixel_foreground[3] / 255.0;
121            double alpha_background = 1.0 - alpha_foreground;
122            for (int k = 0; k < 3; ++k){
123                pixel_output[k] = static_cast<uchar>(alpha_foreground * pixel_foreground[k] + alpha_background * pixel_output[k]);
124            }
125            pixel_output[3] = static_cast<uchar>((alpha_foreground * 255) + (alpha_background * pixel_output[3]));
126        }
127    }
128}

unfavbs.cpp

  1#include "unfavbs.h"
  2
  3//定义导出文件数量,以及文件名在bin文件内所占字节大小
  4int filenamesize,filenumber;
  5
  6int main(int argc,char* argv[]){
  7	
  8	//创建output文件夹
  9	system("md graph_bs");
 10	system("cls");
 11
 12	//打开graph_bs.bin文件
 13	ifstream binfile;
 14	binfile.open("graph_bs.bin",ios::in | ios::binary);
 15	
 16	//从bin文件读入变量filenumber和filenamesize
 17	filenumber = readbin(binfile);
 18	filenamesize = readbin(binfile);
 19	//cout<<filenumber<<" "<<filenamesize<<endl;
 20	
 21	/*
 22		初始化动态数组bin和unbinname
 23		bin用来存放对应序列号,文件大小以及起始地址
 24		unbinname用来存放输出的文件名
 25	*/
 26	int **bin = new int *[filenumber];
 27	string *unbinname = new string[filenumber];
 28	for(int i = 0;i < filenumber;i++) bin[i] = new int[3];
 29	
 30	//从bin文件内读入bin数组
 31	for(int i = 0;i < filenumber;i++) for(int j = 0;j < 3;j++) bin[i][j] = readbin(binfile);
 32	
 33	//排序,使得bin可以与unbinname对应
 34	sort(bin,bin+filenumber,cmp);
 35	
 36	//读入unbinname
 37	for(int i = 0;i < filenumber;i++) unbinname[i] = getbinname(binfile);
 38	for(int i = 0;i < filenumber;i++) unbinname[i].erase(unbinname[i].length()-1);
 39	//for(int i = 0;i < filenumber;i++) cout<<bin[i][0]<<" "<<bin[i][1]<<" "<<bin[i][2]<<" "<<unbinname[i]<<" "<<unbinname[i].size()<<endl;
 40
 41	//定义一个vecoter用来保存表情文件对应下标
 42	vector<int>face;
 43
 44	//保存表情文件下标
 45	//-79 -19 -57 -23为GBK字符: "表情"
 46	for(int i = 0;i < filenumber;i++) 
 47		if(int(unbinname[i][unbinname[i].size()-4])==-79 &&
 48		int(unbinname[i][unbinname[i].size()-3])==-19 &&
 49		int(unbinname[i][unbinname[i].size()-2])==-57 &&
 50		int(unbinname[i][unbinname[i].size()-1])==-23)
 51			face.push_back(i);
 52			
 53	//创建一个map映射文件名-->下标
 54	map<string,int>binmap;
 55	for(int i = 0;i < filenumber;i++) binmap[unbinname[i]]=i;
 56
 57	//定义一个vecoter用来保存每个表情的基底
 58	vector<int>base;
 59
 60	//计算每个基底的下标
 61	for(int i=0;i<face.size();i++){
 62		string temp="";
 63		for(int t=0;t<unbinname[face[i]].size()-5;t++) temp+=unbinname[face[i]][t];
 64		if(unbinname[binmap[temp]]==temp) base.push_back(binmap[temp]);
 65		else {
 66			for(int t=i;t<face.size()-1;t++) swap(face[t],face[t+1]);
 67			face.pop_back();
 68			i--;
 69		}
 70	}
 71	//for(int i=0;i<face.size();i++) cout<<unbinname[face[i]]<<" "<<unbinname[base[i]]<<" "<<face[i]<<endl;
 72
 73	//清空之前的缓存
 74	system("del cachebase");
 75	system("del cacheface");
 76	system("cls");
 77	
 78	//合成立绘并输出到graph_bs文件夹
 79	for(int i=0;i<base.size();i++){
 80		//定义两个流basestream和facestream
 81		ifstream basestream,facestream;
 82
 83		//定义临时文件流
 84		ofstream cachebase,cacheface;
 85		cachebase.open("cachebase",ios::out | ios::binary);
 86		cacheface.open("cacheface",ios::out | ios::binary);
 87
 88		//读入basestream
 89		unbinout(binfile,cachebase,bin[base[i]][1],bin[base[i]][2]+bin[base[i]][1]);
 90		basestream.open("cachebase",ios::in | ios::binary);
 91
 92		//读入facestream
 93		unbinout(binfile,cacheface,bin[face[i]][1],bin[face[i]][2]+bin[face[i]][1]);
 94		facestream.open("cacheface",ios::in | ios::binary);
 95
 96		//读入底图解压后的数据大小
 97		readbin(basestream);
 98		int basepngsize=readbin(basestream);
 99
100		//读取底图的画布大小
101		readbin(basestream);
102		readbin(basestream);
103		readbin(basestream);
104		int basepngx=readbinhelf(basestream),basepngy=readbinhelf(basestream);
105		readbin(basestream);
106		readbin(basestream);
107		readbin(basestream);
108		readbin(basestream);
109		readbin(basestream);
110		istringstream basepngstream=decompressData(basestream,44,basepngsize);
111
112		//创建底图
113		Mat basepng(basepngy,basepngx, CV_8UC4);
114		for(int j = 0; j < basepngy; ++j) for(int k = 0; k < basepngx; ++k) {
115        	basepng.at<cv::Vec4b>(j, k)[0] = int(basepngstream.get());
116        	basepng.at<cv::Vec4b>(j, k)[1] = int(basepngstream.get());
117        	basepng.at<cv::Vec4b>(j, k)[2] = int(basepngstream.get());
118        	basepng.at<cv::Vec4b>(j, k)[3] = int(basepngstream.get());
119    	}
120
121		//读入表情解压后的数据大小
122		readbin(facestream);
123		int facepngsize=readbin(facestream);
124		readbin(facestream);
125		readbin(facestream);
126		readbin(facestream);
127
128		//读表情的画布大小以及偏移坐标
129		int facepngx=readbinhelf(facestream),facepngy=readbinhelf(facestream),x=readbinhelf(facestream),y=readbinhelf(facestream);
130		readbin(facestream);
131		
132		//读入表情图片数量
133		int pngnumber=readbin(facestream);
134		readbin(facestream);
135		readbin(facestream);
136		istringstream facepngstream=decompressData(facestream,44,facepngsize);
137
138		//合成并输出
139		string cmdstr="md .\\graph_bs\\"+unbinname[base[i]];
140    	system(cmdstr.c_str());
141    	system("cls");
142    	for(int n=0;n<pngnumber;n++) {
143    		Mat facepng(facepngy,facepngx, CV_8UC4);
144    		for(int k = 0; k < facepngy; ++k) for(int j = 0; j < facepngx; ++j) {
145        		facepng.at<cv::Vec4b>(k, j)[0] = int(facepngstream.get());
146        		facepng.at<cv::Vec4b>(k, j)[1] = int(facepngstream.get());
147        		facepng.at<cv::Vec4b>(k, j)[2] = int(facepngstream.get());
148        		facepng.at<cv::Vec4b>(k, j)[3] = int(facepngstream.get());
149    		}
150			Mat pngfile;
151			overlayImages(basepng,facepng,pngfile,x,y);
152    		imwrite(".\\graph_bs\\"+unbinname[base[i]]+'\\'+unbinname[base[i]]+'_'+to_string(n)+".png",pngfile);
153		}
154		basestream.close();
155		facestream.close();
156		system("del cachebase");
157		system("del cacheface");
158		system("cls");
159	}
160
161	//释放动态数组
162	for(int i = 0;i < filenumber;i++) delete[] bin[i];
163	delete[] bin;
164	delete[] unbinname;
165	
166	//关闭bin文件
167	binfile.close();
168	
169	return 0;
170}