Skip to content

Instantly share code, notes, and snippets.

@MikuAuahDark
Last active July 20, 2019 13:14
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MikuAuahDark/44c6397752fd9a92fb1c to your computer and use it in GitHub Desktop.
Save MikuAuahDark/44c6397752fd9a92fb1c to your computer and use it in GitHub Desktop.
SIF Decryption (prototype). The fully working project is in https://github.com/MikuAuahDark/HonokaMiku
--[[
Love Live! School Idol Festival game files decoder/decrypter. Part of Project HonokaMiku
For SIF JP version.
LL!SIF JP file decryption routines written in pure Lua & tested in Lua 5.1.4
Requires MD5 implementation in lua. https://github.com/kikito/md5.lua
It may not use same license below, open link above for more information.
Copyright © 2036 Dark Energy Processor Corporation
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute,
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
local keyTables={
1210253353 ,1736710334 ,1030507233 ,1924017366 ,1603299666 ,1844516425 ,1102797553 ,32188137,
782633907 ,356258523 ,957120135 ,10030910 ,811467044 ,1226589197 ,1303858438 ,1423840583,
756169139 ,1304954701 ,1723556931 ,648430219 ,1560506399 ,1987934810 ,305677577 ,505363237,
450129501 ,1811702731 ,2146795414 ,842747461 ,638394899 ,51014537 ,198914076 ,120739502,
1973027104 ,586031952 ,1484278592 ,1560111926 ,441007634 ,1006001970 ,2038250142 ,232546121,
827280557 ,1307729428 ,775964996 ,483398502 ,1724135019 ,2125939248 ,742088754 ,1411519905,
136462070 ,1084053905 ,2039157473 ,1943671327 ,650795184 ,151139993 ,1467120569 ,1883837341,
1249929516 ,382015614 ,1020618905 ,1082135529 ,870997426 ,1221338057 ,1623152467 ,1020681319
}
local keyMultipler=214013
local keyAdd=2531011
md5=dofile("md5.lua") -- source: https://github.com/kikito/md5.lua
local bxor
local bnot
do
local _,bit=pcall(require,"bit")
if _ then
bxor=bit.bxor
bnot=bit.bnot
else
_,bit=pcall(require,"bit32")
if _ then
bxor=function(a,b) return bit.bxor(a>2147483647 and a-4294967296 or a,b>2147483647 and b-4294967296 or b) end
bnot=bit.bnot
else
bxor=function(a,b) -- source: http://stackoverflow.com/a/25594410
local p,c=1,0
while a>0 and b>0 do
local ra,rb=a%2,b%2
if ra~=rb then c=c+p end
a,b,p=(a-ra)/2,(b-rb)/2,p*2
end
if a<b then a=b end
while a>0 do
local ra=a%2
if ra>0 then c=c+p end
a,p=(a-ra)/2,p*2
end
return c
end
bnot=function(n)
local p,c=1,0
while n>0 do
local r=n%2
if r<1 then c=c+p end
n,p=(n-r)/2,p*2
end
return c
end
end
end
end
-- Returns the XOR key and the new multipler key.
-- Unlike SIF EN decrypter, both values returned as numbers instead.
local function updateKey(mkey)
local a=(mkey*keyMultipler+keyAdd)%4294967296
return math.floor(a/16777216),a
end
-- Decrypter setup.
-- Header is the first 16-bytes file contents
-- File is the file path in string. Actually the function just needs the basename
-- Returns the decrypter context/structure
function decryptSetup(header,file)
local hash=md5.sum("Hello"..file:sub(-(file:reverse():find("/") or file:reverse():find("\\") or 0)+1))
local w=hash:gmatch("....")
local t={}
w() -- discard
local hchk=w()
hchk=bnot(hchk:sub(1,1):byte()+hchk:sub(2,2):byte()*256+hchk:sub(3,3):byte()*65536+hchk:sub(4,4):byte()*16777216)
hchk=string.char(hchk%256)..string.char(math.floor(hchk/256)%256)..string.char(math.floor(hchk/65536)%256)
assert(hchk==header:sub(1,3),"Header file doesn't match!") -- Only decrypt mode 3 is supported.
local idx=header:sub(12,12):byte()%64+1 -- +1 because Lua is 1-based indexing
t.init_key=keyTables[idx]
t.update_key=t.init_key
t.xor_key=math.floor(t.init_key/16777216)
t.pos=0
return t
end
-- Used internally. Updates the key
local function updateKeyStruct(dctx)
dctx.xor_key,dctx.update_key=updateKey(dctx.update_key)
end
-- Decrypt block of string(bytes).
-- dctx is decrypter context/struct
-- b is bytes that want to decrypted
-- Returns decrypted bytes
function decryptBlock(dctx,b)
if #b==0 then return end
local t={}
local char=string.char
local inst=table.insert
for i=1,#b do
table.insert(t,char(bxor(dctx.xor_key,b:sub(i,i):byte())))
updateKeyStruct(dctx)
dctx.pos=dctx.pos+1
end
return table.concat(t)
end
-- Sets decrypter key position to decrypt at <pos+offset> later.
-- dctx is decrypter context
-- offset is relative to current position
-- UNTESTED!
function gotoOffset(dctx,offset)
if offset==0 then return end
local x=dctx.pos+offset
assert(x>=0,"Negative position")
dctx.pos=x
local floor=math.floor
dctx.update_key=dctx.init_key
dctx.xor_key=floor(key/16777216)
if x>0 then
for i=1,x do
updateKeyStruct(dctx)
end
end
end
--[[
So, example decryption flow:
local path="file/to/tx_u_41001001_rankup_navi.texb"
local f=io.open(path,"rb")
local dctx=decryptSetup(f:read(16),path)
local f2=io.open("Honoka card #79.texb","wb")
f2:write(decryptBlock(dctx,f:read("*a")))
f2:close()
f:close()
Decrypted file is stored as "Honoka card #79.texb"
]]
--[[
Love Live! School Idol Festival game files decoder/decrypter. Part of Project HonokaMiku
For SIF EN version.
LL!SIF EN file decryption routines written in pure Lua & tested in Lua 5.1.4
Requires MD5 implementation in lua. https://github.com/kikito/md5.lua
It may not use same license below, open link above for more information.
Copyright © 2036 Dark Energy Processor Corporation
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute,
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
md5=dofile("md5.lua") -- source: https://github.com/kikito/md5.lua
local bxor
do
local _,bit=pcall(require,"bit")
if _ then bxor=bit.bxor
else
_,bit=pcall(require,"bit32")
if _ then bxor=function(a,b) return bit.bxor(a>2147483647 and a-4294967296 or a,b>2147483647 and b-4294967296 or b) end
else
bxor=function(a,b) -- source: http://stackoverflow.com/a/25594410
local p,c=1,0
while a>0 and b>0 do
local ra,rb=a%2,b%2
if ra~=rb then c=c+p end
a,b,p=(a-ra)/2,(b-rb)/2,p*2
end
if a<b then a=b end
while a>0 do
local ra=a%2
if ra>0 then c=c+p end
a,p=(a-ra)/2,p*2
end
return c
end
end
end
end
-- Returns the XOR key(2 bytes) and the new multipler key. In new tables
-- We use decimals instead of hexadecimals. I think it looks cool
local function updateKey(mkey)
local floor=math.floor
local _={unpack(mkey)}
local a=mkey[1]+mkey[2]*256+mkey[3]*65536+mkey[4]*16777216
local b=floor(a/65536)%65536
local c=(b*1101463552)%2147483648
local d=c+(a%65536)*16807
local e=floor(b*16807/32768)
local f=e+d-2147483647
if d%4294967296>2147483646 then d=f
else d=d+e end
return {
floor(d/8388608)%256,
floor(d/32768)%256
},
{
d%256,
floor(d/256)%256,
floor(d/65536)%256,
floor(d/16777216)
}
end
-- Decrypter setup.
-- Header is the first 4-bytes file contents
-- File is the file path in string. Actually the function just needs the basename
-- Returns the decrypter context/structure
function decryptSetup(header,file)
local hash=md5.sum("BFd3EnkcKa"..file:sub(-(file:reverse():find("/") or file:reverse():find("\\") or 0)+1))
local t={}
local w=hash:sub(1,8):gmatch("....")
local key=w()
assert(header==w(),"Header file doesn't match!") -- Currently only one decryption method is supported
local uk={
key:sub(4,4):byte(),
key:sub(3,3):byte(),
key:sub(2,2):byte(),
key:sub(1,1):byte()%128
}
key=uk[1]+uk[2]*256+uk[3]*65536+uk[4]*16777216
t.xor_key={math.floor(key/8388608)%256,math.floor(key/32768)%256}
t.pos=0
t.update_key=uk
t.init_key={unpack(uk)}
return t
end
-- Used internally. Updates the key
local function updateKeyStruct(dctx)
local a,b=updateKey(dctx.update_key)
dctx.update_key=b
dctx.xor_key=a
end
-- Decrypt block of string(bytes).
-- dctx is decrypter context/struct
-- b is bytes that want to decrypted
-- Returns decrypted bytes
function decryptBlock(dctx,b)
if #b==0 then return end
local char=string.char
local inst=table.insert
local t={}
if dctx.pos%2==1 then
inst(t,char(bxor(b:sub(1,1):byte(),dctx.xor_key[2])))
updateKeyStruct(dctx)
dctx.pos=dctx.pos+1
bytes=bytes:sub(2)
end
for i=1,#b do
if i%2==1 then
inst(t,char(bxor(b:sub(i,i):byte(),dctx.xor_key[1])))
else
inst(t,char(bxor(b:sub(i,i):byte(),dctx.xor_key[2])))
updateKeyStruct(dctx)
end
dctx.pos=dctx.pos+1
end
return table.concat(t)
end
-- Sets decrypter key position to decrypt at <pos+offset> later.
-- dctx is decrypter context
-- offset is relative to current position
-- Currently slow and experimental
function gotoOffset(dctx,offset)
if offset==0 then return end
local x=dctx.pos+offset
assert(x>=0,"Negative position")
dctx.pos=x
local floor=math.floor
dctx.update_key={unpack(dctx.init_key)}
local key=dctx.update_key[1]+dctx.update_key[2]*256+dctx.update_key[3]*65536+dctx.update_key[4]*16777216
dctx.xor_key={floor(key/8388608)%256,floor(key/32768)%256}
if x>1 then
for i=1,floor(x/2) do
updateKeyStruct(dctx)
end
end
end
--[[
So, example decryption flow:
local path="file/to/tx_u_41001001_rankup_navi.texb"
local f=io.open(path,"rb")
local dctx=decryptSetup(f:read(4),path)
local f2=io.open("Honoka card #79.texb","wb")
f2:write(decryptBlock(dctx,f:read("*a")))
f2:close()
f:close()
Decrypted file is stored as "Honoka card #79.texb"
]]
// HonokaMiku.cpp
// Loads SIF libGame.so and execute it's decrypt function
// 10/15/2015: JP is now supported. Requires SIF JP v2.0.5 x86 libGame.so
#if !defined(_M_IX86) && !defined(__i386__)
#error "Only x86 targets are supported!"
#endif
#include <cstdlib>
#include <cstdio>
#include <iostream>
#include <Windows.h>
#include <direct.h> // change this to unistd.h if you want to build in Linux
#ifndef _MSC_VER
#include <libgen.h>
#endif
#ifndef SIF_JP
// So it's easy to change it if we want to use JP libGame.so for example.
// Relative to SIF EN v2.0.5 x86 libGame.so
#define LIBGAME_STRLEN_PTR 0x73170
#define LIBGAME_MEMCPY_PTR 0x73160
#define LIBGAME_MEMSET_PTR 0x73180
#define LIBGAME_MALLOC_PTR 0x73130
#define LIBGAME_FREE_PTR 0x73140
#define LIBGAME_ASRTLOG_PTR 0x210280
#define DECRYPTER_STRUCTSIZE 0x40
#define DECRYPTER_SETUPFUNC 0x189860
#define DECRYPTER_DECRYPT 0x189480
// Some patched code address. Without patching it, we will very likely to get Access Violation
#define DECRYPTSETUP_NOP1 0x189052
#define DECRYPTSETUP_NOP1_SIZE 15
#define DECRYPTSETUP_NOP2 0x1893ac
#define DECRYPTSETUP_NOP3 0x1890ab
#define DECRYPTSETUP_NOP3_SIZE 14
#else
// Relative to SIF JP v2.0.5 x86 libGame.so
#define LIBGAME_STRLEN_PTR 0x72fb0
#define LIBGAME_MEMCPY_PTR 0x72fa0
#define LIBGAME_MEMSET_PTR 0x72fc0
#define LIBGAME_MALLOC_PTR 0x72f70
#define LIBGAME_FREE_PTR 0x72f80
#define LIBGAME_ASRTLOG_PTR 0x2245a0
#define DECRYPTER_STRUCTSIZE 0x4c
#define DECRYPTER_SETUPFUNC 0x19b6c0
#define DECRYPTER_SETUPFUNC2 0x19b7e0
#define DECRYPTER_DECRYPT 0x19b970
#define DECRYPTSETUP_NOP1 0x19ad92
#define DECRYPTSETUP_NOP1_SIZE 19
#define DECRYPTSETUP_NOP3 0x19b0d5
#define DECRYPTSETUP_NOP3_SIZE 15
#endif
static char libGame[10485760]; // 10MB of char array
typedef int (*decryptSetupExPre)(void* ,const char* ,const char * ,unsigned int& );
typedef void (*decrypt)(void* ,void* ,size_t);
#ifdef SIF_JP
typedef void (*decryptSetupExPost)(void* ,const char* );
#endif
void setFuncPtr(void* lib_address,unsigned int func_address_in_file,void* func_ptr) {
char* lib_addr_as_char=(char*)lib_address;
memset((void*)((unsigned int)lib_address+func_address_in_file),0x90,16); // 0x90 = xchg eax,eax a.k.a nop
lib_addr_as_char[func_address_in_file]=0xe9; // 0xe9 = jmp relative
*(unsigned int*)((unsigned int)lib_address+func_address_in_file+1)=(unsigned int)func_ptr-((unsigned int)lib_address+func_address_in_file)-5;
}
void assertLogFunction(int code,char* file,char* message) {
std::cerr << "Error " << code << "." << std::endl << file << ": " << message << std::endl;
exit(-1);
}
void initLibGame() {
char curdir[260];
char formatted_dir[260];
char header[4];
GetModuleFileNameA(nullptr,curdir,260); // change this if you want to build in Linux
{
char* temp=strrchr(curdir,'\\'); // change this if you want to build in Linux
temp[1]=0;
}
sprintf(formatted_dir,"%s%s",curdir,"libGame.so");
FILE* f=fopen(formatted_dir,"rb");
if(f==nullptr) {
std::cerr << "Cannot load libGame.so: " << strerror(errno) << std::endl;
exit(-1);
}
fread(header,1,4,f);
if(memcmp(header,"\177ELF",4)) {
std::cerr << "Not a valid .so file" << std::endl;
exit(-1);
}
fseek(f,0,SEEK_END);
size_t file_size=ftell(f);
fseek(f,0,SEEK_SET);
//libGame=new char[file_size];
fread(libGame,1,10485760,f);
fclose(f);
// Patch function in .so file
setFuncPtr(libGame,LIBGAME_STRLEN_PTR,(void*)&strlen);
setFuncPtr(libGame,LIBGAME_MEMCPY_PTR,(void*)&memcpy);
setFuncPtr(libGame,LIBGAME_MEMSET_PTR,(void*)&memset);
setFuncPtr(libGame,LIBGAME_MALLOC_PTR,(void*)&malloc);
setFuncPtr(libGame,LIBGAME_FREE_PTR,(void*)&free);
setFuncPtr(libGame,LIBGAME_ASRTLOG_PTR,(void*)&assertLogFunction);
// NOP some opcodes
memset(libGame+DECRYPTSETUP_NOP1,0x90,DECRYPTSETUP_NOP1_SIZE);
#ifndef SIF_JP
memset(libGame+DECRYPTSETUP_NOP2,0x90,17);
#endif
memset(libGame+DECRYPTSETUP_NOP3,0x90,DECRYPTSETUP_NOP3_SIZE);
// Allow execution in our allocated memory
DWORD old_protect;
VirtualProtect(libGame,10485760,PAGE_EXECUTE_READWRITE,&old_protect); // change this if you want to build in Linux
// Setup VTables. JP Ver only
#ifdef SIF_JP
*(unsigned int*)(libGame+0x3a1ff0)=(unsigned int)(libGame+0x39c1f8);
for(int i=0;i<4;i++) {
*(unsigned int*)(libGame+0x39c1f8+i*8)+=(unsigned int)libGame;
}
#endif
}
char* get_basename(char* path) {
char* temp=strrchr(path,'/');
if(temp==nullptr) {
temp=strrchr(path,'\\');
if(temp==nullptr) return path;
}
return temp+1;
}
int main(int argc,char** argv) {
char* decrypt_struct;
char* buffer;
FILE* f;
unsigned int uiv=0;
size_t file_size;
char* file_path;
char* file_out=nullptr;
if(argc<2) {
std::cout << "Usage: " << argv[0] << " <encrypted file> [decrypted file out]" << std::endl;
return -1;
}
if(argc>=3)
file_out=argv[2];
f=fopen(argv[1],"rb");
if(f==nullptr) {
std::cerr << "Cannot open " << argv[1] << ": " << strerror(errno) << std::endl;
return -1;
}
fseek(f,0,SEEK_END);
file_size=ftell(f);
fseek(f,0,SEEK_SET);
initLibGame();
decryptSetupExPre decrypt_preparation=decryptSetupExPre((void*)(libGame+DECRYPTER_SETUPFUNC));
#ifdef SIF_JP
decryptSetupExPost decrypt_finish_setup=decryptSetupExPost((void*)(libGame+DECRYPTER_SETUPFUNC2));
#endif
decrypt decrypt_func=(decrypt)(void*)(libGame+DECRYPTER_DECRYPT);
decrypt_struct=new char[DECRYPTER_STRUCTSIZE];
memset(decrypt_struct,0,0x40);
// decrypt_struct[0x22]=0xff;
// decrypt_struct[0x23]=0xff;
#ifdef SIF_JP
decrypt_struct[0x2d]=1;
#endif
buffer=new char[4];
fread(buffer,1,4,f);
int val=decrypt_preparation(decrypt_struct,argv[1],buffer,uiv); // KLab decrypter actually can handle '\' and '/'
if(val) {
#ifdef SIF_JP
delete[] buffer;
buffer=new char[64];
fread(buffer,1,uiv-4,f);
decrypt_finish_setup(decrypt_struct,buffer);
#endif
fseek(f,uiv,SEEK_SET);
delete[] buffer;
size_t bufsize=file_size-uiv;
buffer=new char[bufsize];
fread(buffer,1,bufsize,f);
decrypt_func(decrypt_struct,buffer,bufsize);
fclose(f);
if(file_out==nullptr) {
file_out=new char[260];
sprintf(file_out,"%s_",argv[1]);
}
f=fopen(file_out,"wb");
if(f==nullptr) {
std::cerr << "Cannot open " << file_out << ": " << strerror(errno) << std::endl << "Writing to stdout instead!" << std::endl;
f=stdout;
}
fwrite(buffer,1,bufsize,f);
fclose(f);
delete[] buffer;
return 0;
} else {
std::cerr << "Something's wrong." << std::endl;
#ifdef _MSC_VER
_CrtDbgBreak();
#else
return -1;
#endif
}
system("pause");
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment