首先我们需要知道favorite的立绘通常在一个叫做graph_bs.bin
的文件下,用grabro
打开可以看到如下内容:
很明显,主体部分和面部表情是分开的,这种情况我们称之为差分
而garbro
并不会自动处理差分内容,所以如果要提取人物立绘就得我们自己手动合成
那么如何实现呢,我们先使用010editor
打开这个文件看看结构
看起来。。。好像什么都看不出来
不用着急,我们先看看最简单的bgm.bin
来认识一下*.bin
的文件结构
先用使用garbro
打开,可以看到,这个bgm.bin
文件下共有60个文件
使用010editor
打开我们可以发现地址0h-4h刚好也等于60
接着是一个值大小为240,这个值我们先不用理他
现在我们先使用garbro
随便提取一个文件,与原bgm.bin
文件进行比较
你会发现,好家伙原来这文件数据就在.bin
文件里面,他甚至不愿意异或一下
起始地址为968,共2723547个字节
接着我们返回原bgm.bin
一看,哎这不是968和2723547吗·
于是我们对前面这一部分就有了一点点了解,但是这里每个文件的第一个数据每次加4,这个4是拿来干什么的的呢?不着急,我们慢慢看。
接着,当我们读完这些数据后,会发现之后这个东西
wow,这不是文件名吗,还有原来之前的240是指的文件名的长度,那不难猜出前面的4指的就是文件名的大小的,也就是第一块是文件的文件名在文件名块(index)里面的地址
接着打开graph_bs.bin
验证我们的猜想
完全符合,虽然第一个不是0但也对应了第一个文件的文件名所在的地址
我们甚至可以知道文件名是以00分割的
这下我们不难写出提取文件的程序了,但问题来了,我们提取出来的是一个.hzc
文件啊,相当于garbro
提取时的保留原样
ffmpeg
都无法正确识别这个格式
但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?
这下看懂了,原来图像数据是使用zlib
进行压缩了
有关mode:
0代表背景为rgb
,1代表立绘基底为rgba
,2代表差分(多个文件)也为rgba
接着那串0000之后就是zlib
压缩的数据了,解压之后就可以得到rgba/rgb
然后利用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}