• Reverse Engineering FS2013 .i3d.shapes format.
    18 replies, posted
Alright. I've asked this question in WDYNHW a few months ago but it was left unanswered, I've also spent hours on trying with different debuggers and methods of figuring this out but I am at absolute zero progress and thus I've now created this thread. Farming Simulator 2013 stores all of its vehicles in XML files with the ending .i3d. These contain all information, and on smaller models, also the modeldata. But for larger models, there's a separate file with the ending .i3d.shapes containing the modeldata, and that's where I'm stumped, I'm trying to read it. What I know so far: * They are -most likely- compressed. I've compared the hex of multiple different shapes and I see no patterns or relevant information in the beginning of the file, I can neither find any plaintext whatsoever. What I've tried so far: * I got access to an editor which can load up the .shapes file just fine, so I thought about capturing its reading part and then using the assembly to figure out stuff. * Using very limited knowledge of OllyDbg, I've tried to do the above with no results, because I have no idea what I'm doing. * I've downloaded 2 different "signature scan" commandline programs which claims to be able to find most compression algorithms out there, but 0% match with both. * I've googled a whole lot with no results. And that's about it. I got plenty of files to look at and compare, and here's a screenshot of 3 different files, incase I missed something: [IMG]http://puu.sh/bDbdc/b0a058c569.png[/IMG] Modeleditor: [URL]https://dl.dropboxusercontent.com/u/15075155/GIANTS_Editor_5.5.2_win32.zip[/URL] The .shapes files: [URL]https://dl.dropboxusercontent.com/u/15075155/3shapefiles.zip[/URL] I3D file for "kroneBigPack": [url]https://dl.dropboxusercontent.com/u/15075155/kroneBigPack1290.zip[/url] The editor is able to export into obj, and here's the output of the "kroneBigPack": [URL]https://dl.dropboxusercontent.com/u/15075155/kroneBigPack1290.obj[/URL] I appreciate any help.
What about the accompanying XML files? [editline]17th September 2014[/editline] As in, .i3d
Posted in the OP, not sure what useful information you can find there cause only 2 lines are relevant to this thread.
Given you can't open the shapes files on their own, they would be of help if someone with more time and reversing experience than me wanted to look at the editor opening a shapes file in a debugger.
Files are compressed for sure prob with lzma Ill take a look for you tomorrow -edit- [IMG]http://puu.sh/c2qkh.png[/IMG] sub_13E1490 ( ---> int __stdcall sub_1D7BE0(int, void *, char) that loads the .i3d reads from 0 to 4096 stores it somewhere in a buffer the first 4bytes of it doesn't seem encrypted/compressed probably the compression decode bytes. [IMG]http://puu.sh/c2qrJ.png[/IMG] the first 2 bytes change the rest (0x0,0x02) are const I'm still looking at what it actually does with this data
Interesting findings, looking forward for more progress!
Interessing.... From my test i've discover thath: Open i3d with normal shape file: reading frist 4096 bytes,next 4096 bytes,next 4096. finally it start reading shape from offeset 8192. Open i3d with cripted shape file: reading frist 4096 bytes,reading frist 4096 bytes,reading next 4096 bytes, finally it start reading shape from offeset 4096. This protected shape are parts of some DLC, and i've extracted dlc, fixed all encripted script then put into game like as normal mod. And it works until you try to buy element with shape cripted. this made game crashes. I've figured this have different function for loading dlc and standar mod,then i've decripted base data from the game and i've find function thath load mod and dlc(it's lua script): [CODE]function loadDlcs() if g_isDemo or g_isGamescomVersion then return end for i = 1, table.getn(g_dlcsDirectories) do local dir = g_dlcsDirectories[i] if dir.isLoaded then loadDlcsFromDirectory(dir.path) end end end function loadDlcsFromDirectory(dlcsDir) createFolder(dlcsDir) local files = Files:new(dlcsDir) for k, v in pairs(files.files) do local addDLCPrefix = false local dlcFileHash, dlcDir, xmlFilename if v.isDirectory then if g_isDevelopmentVersion then dlcDir = v.filename xmlFilename = "dlcDesc.xml" addDLCPrefix = true end else local len = v.filename:len() if len > 4 then local ext = v.filename:sub(len - 3) if ext == ".dlc" then dlcDir = v.filename:sub(1, len - 4) dlcFileHash = getFileMD5(dlcsDir .. "/" .. v.filename, dlcDir) xmlFilename = "dlcDesc.xml" addDLCPrefix = true elseif ext == ".zip" or ext == ".gar" then dlcDir = v.filename:sub(1, len - 4) dlcFileHash = getFileMD5(dlcsDir .. "/" .. v.filename, dlcDir) xmlFilename = "modDesc.xml" addDLCPrefix = false end end end if dlcDir ~= nil and xmlFilename ~= nil and g_dlcModNameHasPrefix[dlcDir] == nil then local absDlcDir = dlcsDir .. "/" .. dlcDir .. "/" local dlcFile = absDlcDir .. xmlFilename g_dlcModNameHasPrefix[dlcDir] = addDLCPrefix loadModDesc(dlcDir, absDlcDir, dlcFile, dlcFileHash, dlcsDir .. "/" .. v.filename, v.isDirectory, addDLCPrefix) end end end function loadMods() local modsDir = g_modsDirectory if g_isDemo or g_isGamescomVersion then return end g_showIllegalActivityInfo = false local files = Files:new(modsDir) for k, v in pairs(files.files) do local modFileHash, modDir if v.isDirectory then modDir = v.filename else local len = v.filename:len() if len > 4 then local ext = v.filename:sub(len - 3) if ext == ".zip" or ext == ".gar" then modDir = v.filename:sub(1, len - 4) modFileHash = getFileMD5(modsDir .. "/" .. v.filename, modDir) end end end if modDir ~= nil then local absModDir = modsDir .. "/" .. modDir .. "/" local modFile = absModDir .. "modDesc.xml" loadModDesc(modDir, absModDir, modFile, modFileHash, modsDir .. "/" .. v.filename, v.isDirectory, false) end end if g_showIllegalActivityInfo then print("Info: This game protects you from illegal activity") end g_showIllegalActivityInfo = nil end function loadModDesc(modName, modDir, modFile, modFileHash, absBaseFilename, isDirectory, addDLCPrefix) if not getIsValidModDir(modName) then print("Error: Invalid mod name '" .. modName .. "'! Characters allowed: (_, A-Z, a-z, 0-9). The first character must not be a digit") return end local origModName = modName if addDLCPrefix then modName = g_uniqueDlcNamePrefix .. modName end if g_modNameToDirectory[modName] ~= nil then return end g_modNameToDirectory[modName] = modDir local isDLCFile = false if Utils.endsWith(modFile, "dlcDesc.xml") then isDLCFile = true if not fileExists(modFile) then g_hasLicenseError = true print("Error: No license for dlc " .. modName .. ". Please reinstall.") return end end if isDLCFile then print("Load dlc: " .. modName) else print("Load mod: " .. modName) end local xmlFile = loadXMLFile("ModFile", modFile) local modDescVersion = getXMLInt(xmlFile, "modDesc#descVersion") if modDescVersion == nil then print("Error: Missing descVersion attribute in mod " .. modName) delete(xmlFile) return end if modDescVersion ~= 9 and modDescVersion ~= 10 and modDescVersion ~= 11 and modDescVersion ~= 12 and modDescVersion ~= 13 and modDescVersion ~= 14 and modDescVersion ~= 15 and modDescVersion ~= 16 then print("Error: Unsupported mod description version in mod " .. modName) delete(xmlFile) return end if _G[modName] ~= nil then print("Error: Invalid mod name '" .. modName .. "'") delete(xmlFile) return end if isDLCFile then local requiredModName = getXMLString(xmlFile, "modDesc.multiplayer#requiredModName") if requiredModName ~= nil and requiredModName ~= origModName then print("Error: Do not rename dlcs. Name: '" .. origModName .. "'. Expect: '" .. requiredModName .. "'") delete(xmlFile) return end end local modEnv = {} _G[modName] = modEnv local modEnv_mt = {__index = _G} setmetatable(modEnv, modEnv_mt) if not isDLCFile then modEnv._G = modEnv end modEnv.g_i18n = I18N:new(false) I18N.initModI18N(modEnv.g_i18n, g_i18n, modName) function modEnv.loadstring(str, chunkname) str = "setfenv(1," .. modName .. "); " .. str return loadstring(str, chunkname) end function modEnv.source(filename, env) if isAbsolutPath(filename) then source(filename, modName) else source(filename) end end function modEnv.InitEventClass(classObject, className) InitEventClass(classObject, modName .. "." .. className) end function modEnv.InitObjectClass(classObject, className) InitObjectClass(classObject, modName .. "." .. className) end function modEnv.registerObjectClassName(object, className) registerObjectClassName(object, modName .. "." .. className) end function modEnv.registerPlaceableType(typeName, object) registerPlaceableType(modName .. "." .. typeName, object) end modEnv.InitStaticEventClass = "" modEnv.InitStaticObjectClass = "" modEnv.loadMod = "" modEnv.loadModDesc = "" modEnv.loadDlcs = "" modEnv.loadDlcsFromDirectory = "" modEnv.loadMods = "" modEnv.deleteFile = "" modEnv.deleteFolder = "" if not isDLCFile then modEnv.getClassObject = "" modEnv.getFunction = "" end if g_dedicatedServerInfo ~= nil then function modEnv.setFramerateLimiter() end modEnv.g_dedicatedServerMinFrameLimit = g_dedicatedServerMinFrameLimit modEnv.g_dedicatedServerMaxFrameLimit = g_dedicatedServerMaxFrameLimit end local onCreateUtil = {} onCreateUtil.onCreateFunctions = {} modEnv.g_onCreateUtil = onCreateUtil function onCreateUtil.addOnCreateFunction(name, func) onCreateUtil.onCreateFunctions[name] = func end function onCreateUtil.activateOnCreateFunctions() for name in pairs(modOnCreate) do modOnCreate[name] = nil end for name, func in pairs(onCreateUtil.onCreateFunctions) do modOnCreate[name] = function(self, id) func(id) end end end function onCreateUtil.deactivateOnCreateFunctions() for name in pairs(modOnCreate) do modOnCreate[name] = nil end end local i = 0 while true do local baseName = string.format("modDesc.l10n.text(%d)", i) local name = getXMLString(xmlFile, baseName .. "#name") if name == nil then break end local text = getXMLString(xmlFile, baseName .. "." .. g_languageShort) if text == nil then text = getXMLString(xmlFile, baseName .. ".en") if text == nil then text = getXMLString(xmlFile, baseName .. ".de") end end if text == nil then print("Warning: No l10n text found for entry '" .. name .. "' in mod '" .. modName .. "'") elseif modEnv.g_i18n:hasModText(name) then print("Warning: Duplicate l10n entry '" .. name .. "' in mod '" .. modName .. "'. Ignoring this defintion.") else modEnv.g_i18n:setText(name, text) end i = i + 1 end local l10nFilenamePrefix = getXMLString(xmlFile, "modDesc.l10n#filenamePrefix") if l10nFilenamePrefix ~= nil then local l10nFilenamePrefixFull = Utils.getFilename(l10nFilenamePrefix, modDir) local l10nXmlFile local langs = { g_languageShort, "en", "de" } for _, lang in ipairs(langs) do local l10nFilename = l10nFilenamePrefixFull .. "_" .. lang .. ".xml" if fileExists(l10nFilename) then l10nXmlFile = loadXMLFile("TempConfig", l10nFilename) break end end if l10nXmlFile ~= nil then local textI = 0 while true do local key = string.format("l10n.texts.text(%d)", textI) if not hasXMLProperty(l10nXmlFile, key) then break end local name = getXMLString(l10nXmlFile, key .. "#name") local text = getXMLString(l10nXmlFile, key .. "#text") if name ~= nil and text ~= nil then if modEnv.g_i18n:hasModText(name) then print("Warning: Duplicate l10n entry '" .. name .. "' in '" .. l10nFilename .. "'. Ignoring this defintion.") else modEnv.g_i18n:setText(name, text:gsub("\r\n", "\n")) end end textI = textI + 1 end delete(l10nXmlFile) else print("Warning: No l10n file found for '" .. l10nFilenamePrefix .. "' in mod '" .. modName .. "'") end end local title = Utils.getXMLI18N(xmlFile, "modDesc.title", nil, "", modName) local desc = Utils.getXMLI18N(xmlFile, "modDesc.description", nil, "", modName) local iconFilename = Utils.getXMLI18N(xmlFile, "modDesc.iconFilename", nil, "", modName) if title == "" then print("Error: Missing title in mod " .. modName) delete(xmlFile) return end if desc == "" then print("Error: Missing description in mod " .. modName) delete(xmlFile) return end local isMultiplayerSupported = Utils.getNoNil(getXMLBool(xmlFile, "modDesc.multiplayer#supported"), false) if modFileHash == nil then if isMultiplayerSupported then print("Warning: Only zip mods are supported in multiplayer. You need to zip the mod " .. modName .. " to use it in multiplayer.") end isMultiplayerSupported = false end if isMultiplayerSupported and iconFilename == "" then print("Error: Missing icon filename in mod " .. modName) delete(xmlFile) return end loadModDescInput(xmlFile, modName) local i = 0 while true do local baseName = string.format("modDesc.maps.map(%d)", i) if not hasXMLProperty(xmlFile, baseName) then break end local mapId = Utils.getNoNil(getXMLString(xmlFile, baseName .. "#id"), "") local defaultVehiclesXMLFilename = Utils.getNoNil(getXMLString(xmlFile, baseName .. "#defaultVehiclesXMLFilename"), "") local mapTitle = Utils.getXMLI18N(xmlFile, baseName .. ".title", nil, "", modName) local mapDesc = Utils.getXMLI18N(xmlFile, baseName .. ".description", nil, "", modName) local mapClassName = Utils.getNoNil(getXMLString(xmlFile, baseName .. "#className"), "") local mapFilename = Utils.getNoNil(getXMLString(xmlFile, baseName .. "#filename"), "") local briefingImagePrefix = Utils.getXMLI18N(xmlFile, baseName .. ".briefingImagePrefix", nil, "", modName) local briefingTextPrefix = Utils.getXMLI18N(xmlFile, baseName .. ".briefingTextPrefix", nil, "", modName) local mapIconFilename = Utils.getXMLI18N(xmlFile, baseName .. ".iconFilename", nil, "", modName) local altMapIconFilename = Utils.getXMLI18N(xmlFile, baseName .. ".altIconFilename", nil, mapIconFilename, modName) if mapClassName:find("[^%w_]") ~= nil then print("Error: invalid map class name: " .. mapClassName) elseif mapId ~= "" and mapTitle ~= "" and mapDesc ~= "" and mapClassName ~= "" and mapFilename ~= "" and defaultVehiclesXMLFilename ~= "" and briefingImagePrefix ~= "" and briefingTextPrefix ~= "" and mapIconFilename ~= "" then local customEnvironment local useModDirectory = true local baseDirectory = modDir mapFilename, useModDirectory = Utils.getFilename(mapFilename, baseDirectory) if useModDirectory then customEnvironment = modName mapClassName = modName .. "." .. mapClassName end mapId = modName .. "." .. mapId mapIconFilename = Utils.getFilename(mapIconFilename, baseDirectory) altMapIconFilename = Utils.getFilename(altMapIconFilename, baseDirectory) briefingImagePrefix = Utils.getFilename(briefingImagePrefix, baseDirectory) defaultVehiclesXMLFilename = Utils.getFilename(defaultVehiclesXMLFilename, baseDirectory) MapsUtil.addMapItem(mapId, mapFilename, mapClassName, briefingImagePrefix, briefingTextPrefix, defaultVehiclesXMLFilename, mapTitle, mapDesc, mapIconFilename, altMapIconFilename, baseDirectory, customEnvironment) end i = i + 1 end local version = Utils.getXMLI18N(xmlFile, "modDesc.version", nil, "", modName) local author = Utils.getXMLI18N(xmlFile, "modDesc.author", nil, "", modName) if isDLCFile then local dlcProductId = getXMLString(xmlFile, "modDesc.productId") if dlcProductId == nil or version == nil then print("Error: invalid product id or version in DLC " .. modName) elseif not g_isDemo then addNotificationFilter(dlcProductId, version) end end iconFilename = Utils.getFilename(iconFilename, modDir) ModsUtil.addModItem(title, desc, version, author, iconFilename, modName, modDir, modFile, isMultiplayerSupported, modFileHash, absBaseFilename, isDirectory) delete(xmlFile) end[/CODE]
[QUOTE=devilkkw;46203655]Interessing.... From my test i've discover thath: Open i3d with normal shape file: reading frist 4096 bytes,next 4096 bytes,next 4096. finally it start reading shape from offeset 8192. Open i3d with cripted shape file: reading frist 4096 bytes,reading frist 4096 bytes,reading next 4096 bytes, finally it start reading shape from offeset 4096. [...][/QUOTE] How did you monitor the specific file access?
[QUOTE=Tamschi;46253703]How did you monitor the specific file access?[/QUOTE] I would guess by looking at the fread() size and count parameters in IDA.
[QUOTE=birkett;46259237]I would guess by looking at the fread() size and count parameters in IDA.[/QUOTE] You just look at the system calls it uses like ReadFileA QueryFile etc
Sorry to bump this but I'm still interested in making this project a reality. The findings posted here are very interesting but nothing conclusive. As said, I want to attempt to do this myself, I just need some tutoring in how I can approach this. I've seen lots of you facepunchers reversing fileformats, and this can't be such a challenge, can it?
Hi. Did somebody find solution? I have opened dlc from Farming simulator 2015 problem is that i can not open those i3d files. i found in editor log this massage: Error: Shape from 'tatraPhoenix.i3d.shapes' too big (1332681 KB). Maximum supported size is 16384 KB. The tatraPhoenix.i3d.shapes have 3 401 KB. Is somebody interested to find some solution for this please?
I'm not giving up on this project! I've managed to sneak around in IDA and found this monstrosity of code related to reading the file. It does the rotate/xoring 10 times which leads me to believe this some weird way of encrypting their data? Anybody recognize anything like this? [code]int __thiscall sub_107AC60(int this, int a2, unsigned int a3, int a4) { int v4; // edx@1 int result; // eax@1 int v6; // edi@1 unsigned int i; // ecx@4 int v8; // eax@5 char *v9; // esi@6 char v10; // bl@6 int in1_2; // edi@8 int in5_2; // ecx@8 int in9_2; // edx@8 int in13_2; // esi@8 int v15; // ebx@9 int v16; // ecx@9 int v17; // ebx@9 int v18; // edx@9 int v19; // ebx@9 int v20; // esi@9 int v21; // ebx@9 int v22; // ST60_4@9 int v23; // ebx@9 int v24; // edi@9 int v25; // ebx@9 int v26; // ST74_4@9 int v27; // edi@9 int v28; // ebx@9 int v29; // ST80_4@9 int v30; // edi@9 int v31; // ebx@9 int v32; // ST84_4@9 int v33; // ST70_4@9 int v34; // ebx@9 int v35; // edi@9 int v36; // ebx@9 int v37; // ST7C_4@9 int v38; // edi@9 int v39; // ebx@9 int v40; // ST78_4@9 int v41; // edi@9 int v42; // ST6C_4@9 int v43; // edi@9 int v44; // ST8C_4@9 int v45; // ebx@9 int v46; // edi@9 int v47; // ebx@9 int v48; // ST5C_4@9 int v49; // edi@9 int v50; // ebx@9 int v51; // ST64_4@9 int v52; // edi@9 int v53; // ST68_4@9 int v54; // edi@9 int v55; // ST88_4@9 int v56; // ebx@9 int v57; // edi@9 int v58; // ebx@9 int v59; // edi@9 int v60; // ebx@9 int v61; // edi@9 int v62; // edi@9 int v63; // ebx@9 int v64; // edi@9 int v65; // ebx@9 int v66; // edi@9 int v67; // edi@9 int v68; // edi@9 int v69; // ebx@9 int v70; // edi@9 int v71; // edi@9 int v72; // ebx@9 int v73; // edi@9 int v74; // edi@9 int v75; // edi@9 int v76; // ebx@9 int v77; // edi@9 int v78; // ebx@9 int v79; // edi@9 int v80; // edi@9 bool cont; // zf@9 int v82; // ecx@10 int v83; // ebx@10 int v84; // ecx@10 int v85; // edx@10 int v86; // edx@10 unsigned int v87; // edx@12 int v88; // eax@16 int v89; // [sp+4h] [bp-C8h]@1 int v90; // [sp+8h] [bp-C4h]@0 int in4; // [sp+Ch] [bp-C0h]@2 int in2; // [sp+10h] [bp-BCh]@2 int in8; // [sp+14h] [bp-B8h]@2 int in6; // [sp+18h] [bp-B4h]@2 int in16; // [sp+1Ch] [bp-B0h]@2 int in1; // [sp+20h] [bp-ACh]@2 int in15; // [sp+24h] [bp-A8h]@2 int in7; // [sp+28h] [bp-A4h]@2 int in14; // [sp+2Ch] [bp-A0h]@2 int in5; // [sp+30h] [bp-9Ch]@2 int in13; // [sp+34h] [bp-98h]@2 int in3; // [sp+38h] [bp-94h]@2 int in12; // [sp+3Ch] [bp-90h]@2 int in11; // [sp+40h] [bp-8Ch]@2 int v105; // [sp+44h] [bp-88h]@1 signed int loopcount; // [sp+48h] [bp-84h]@8 int v107; // [sp+48h] [bp-84h]@10 int in9; // [sp+4Ch] [bp-80h]@2 int in10; // [sp+50h] [bp-7Ch]@2 int in4_2; // [sp+54h] [bp-78h]@8 int v111; // [sp+58h] [bp-74h]@9 int v112; // [sp+58h] [bp-74h]@10 int in8_2; // [sp+5Ch] [bp-70h]@8 int v114; // [sp+5Ch] [bp-70h]@10 int in12_2; // [sp+60h] [bp-6Ch]@8 int v116; // [sp+60h] [bp-6Ch]@10 int in7_2; // [sp+64h] [bp-68h]@8 int v118; // [sp+64h] [bp-68h]@10 int in2_2; // [sp+68h] [bp-64h]@8 int v120; // [sp+68h] [bp-64h]@10 int in10_2; // [sp+6Ch] [bp-60h]@8 int v122; // [sp+6Ch] [bp-60h]@10 int in3_2; // [sp+70h] [bp-5Ch]@8 int v124; // [sp+70h] [bp-5Ch]@10 int in15_2; // [sp+74h] [bp-58h]@8 int v126; // [sp+74h] [bp-58h]@10 int in14_2; // [sp+78h] [bp-54h]@8 int v128; // [sp+78h] [bp-54h]@10 int in6_2; // [sp+7Ch] [bp-50h]@8 int v130; // [sp+7Ch] [bp-50h]@10 int in16_2; // [sp+80h] [bp-4Ch]@8 int v132; // [sp+80h] [bp-4Ch]@10 int in11_2; // [sp+84h] [bp-48h]@8 int v134; // [sp+84h] [bp-48h]@10 char v135[64]; // [sp+88h] [bp-44h]@5 v4 = a3; result = a2; v6 = a4; v89 = this; v105 = a4; if ( a3 ) { in1 = *(_DWORD *)this; in2 = *(_DWORD *)(this + 4); in3 = *(_DWORD *)(this + 8); in4 = *(_DWORD *)(this + 12); in5 = *(_DWORD *)(this + 16); in6 = *(_DWORD *)(this + 20); in7 = *(_DWORD *)(this + 24); in8 = *(_DWORD *)(this + 28); in9 = *(_DWORD *)(this + 32); in10 = *(_DWORD *)(this + 36); in11 = *(_DWORD *)(this + 40); in12 = *(_DWORD *)(this + 44); in13 = *(_DWORD *)(this + 48); in14 = *(_DWORD *)(this + 52); in15 = *(_DWORD *)(this + 56); in16 = *(_DWORD *)(this + 60); while ( 1 ) { if ( (unsigned int)v4 < 0x40 ) { i = 0; if ( v4 ) { v8 = result - (_DWORD)v135; do { v9 = &v135[i]; v10 = *(&v135[i++] + v8); *v9 = v10; } while ( i < v4 ); } result = (int)v135; v90 = v6; v105 = (int)v135; } in1_2 = in1; in3_2 = in3; in6_2 = in6; in10_2 = in10; in14_2 = in14; in2_2 = in2; in7_2 = in7; in11_2 = in11; in15_2 = in15; in4_2 = in4; in5_2 = in5; in8_2 = in8; in9_2 = in9; in12_2 = in12; in13_2 = in13; in16_2 = in16; loopcount = 10; do { v15 = __ROL4__(in13_2 + in1_2, 7); v16 = v15 ^ in5_2; v17 = __ROL4__(v16 + in1_2, 9); v18 = v17 ^ in9_2; v19 = __ROL4__(v18 + v16, 13); v20 = v19 ^ in13_2; v21 = __ROR4__(v20 + v18, 14); v22 = v21 ^ in1_2; v23 = __ROL4__(in6_2 + in2_2, 7); v24 = v23 ^ in10_2; v25 = __ROL4__((v23 ^ in10_2) + in6_2, 9); v26 = v24; v27 = v25 ^ in14_2; v28 = __ROL4__((v25 ^ in14_2) + v26, 13); v29 = v27; v30 = v28 ^ in2_2; v31 = __ROR4__((v28 ^ in2_2) + v29, 14); v32 = v31 ^ in6_2; v33 = v30; v34 = __ROL4__(in11_2 + in7_2, 7); v35 = v34 ^ in15_2; v36 = __ROL4__((v34 ^ in15_2) + in11_2, 9); v37 = v35; v38 = v36 ^ in3_2; v39 = __ROL4__((v36 ^ in3_2) + v37, 13); v40 = v38; v41 = v39 ^ in7_2; v42 = v41; v43 = __ROR4__(v40 + v41, 14); v44 = v43 ^ in11_2; v45 = __ROL4__(in16_2 + in12_2, 7); v46 = v45 ^ in4_2; v47 = (v45 ^ in4_2) + in16_2; v48 = v46; v47 = __ROL4__(v47, 9); v49 = v47 ^ in8_2; v50 = (v47 ^ in8_2) + v48; v51 = v49; v50 = __ROL4__(v50, 13); v52 = v50 ^ in12_2; v53 = v52; v54 = __ROR4__(v51 + v52, 14); v55 = v54 ^ in16_2; v56 = __ROL4__(v48 + v22, 7); v57 = v56 ^ v33; v58 = (v56 ^ v33) + v22; in2_2 = v57; v58 = __ROL4__(v58, 9); v59 = v58 ^ v40; v60 = (v58 ^ v40) + in2_2; in3_2 = v59; v60 = __ROL4__(v60, 13); v61 = v60 ^ v48; in4_2 = v61; v62 = __ROR4__(in3_2 + v61, 14); v111 = v62 ^ v22; v63 = __ROL4__(v32 + v16, 7); v64 = v63 ^ v42; v65 = (v63 ^ v42) + v32; in7_2 = v64; v65 = __ROL4__(v65, 9); v66 = v65 ^ v51; in8_2 = v66; v67 = __ROL4__(in7_2 + v66, 13); in5_2 = v67 ^ v16; v68 = __ROR4__(in5_2 + in8_2, 14); in6_2 = v68 ^ v32; v69 = __ROL4__(v44 + v26, 7); v70 = v69 ^ v53; in12_2 = v70; v71 = __ROL4__(v44 + v70, 9); in9_2 = v71 ^ v18; v72 = __ROL4__(in12_2 + in9_2, 13); v73 = v72 ^ v26; in10_2 = v73; v74 = __ROR4__(in9_2 + v73, 14); in11_2 = v74 ^ v44; v75 = __ROL4__(v37 + v55, 7); in13_2 = v75 ^ v20; v76 = __ROL4__(v55 + in13_2, 9); v77 = v76 ^ v29; v78 = (v76 ^ v29) + in13_2; in14_2 = v77; v78 = __ROL4__(v78, 13); v79 = v78 ^ v37; in15_2 = v79; v80 = __ROR4__(in14_2 + v79, 14); in16_2 = v80 ^ v55; cont = loopcount-- == 1; in1_2 = v111; } while ( !cont ); v107 = *(_DWORD *)(result + 16) ^ (in5 + in5_2); v130 = *(_DWORD *)(result + 20) ^ (in6 + in6_2); v118 = *(_DWORD *)(result + 24) ^ (in7 + in7_2); v82 = in8_2; v114 = *(_DWORD *)(result + 32) ^ (in9 + in9_2); v122 = *(_DWORD *)(result + 36) ^ (in10 + in10_2); v83 = *(_DWORD *)(result + 12) ^ (in4_2 + in4); v84 = *(_DWORD *)(result + 28) ^ (in8 + v82); v134 = *(_DWORD *)(result + 40) ^ (in11 + in11_2); v85 = in12_2; v116 = *(_DWORD *)(result + 48) ^ (in13 + in13_2); v86 = *(_DWORD *)(result + 44) ^ (in12 + v85); v128 = *(_DWORD *)(result + 52) ^ (in14 + in14_2); v112 = *(_DWORD *)result ^ (in1 + v111); v126 = *(_DWORD *)(result + 56) ^ (in15 + in15_2); v120 = *(_DWORD *)(result + 4) ^ (in2 + in2_2); v132 = *(_DWORD *)(result + 60) ^ (in16 + in16_2); cont = in9++ == -1; v124 = *(_DWORD *)(result + 8) ^ (in3 + in3_2); if ( cont ) ++in10; *(_DWORD *)v105 = v112; *(_DWORD *)(v105 + 28) = v84; *(_DWORD *)(v105 + 4) = v120; *(_DWORD *)(v105 + 32) = v114; *(_DWORD *)(v105 + 44) = v86; *(_DWORD *)(v105 + 8) = v124; *(_DWORD *)(v105 + 36) = v122; *(_DWORD *)(v105 + 48) = v116; *(_DWORD *)(v105 + 16) = v107; *(_DWORD *)(v105 + 40) = v134; *(_DWORD *)(v105 + 56) = v126; v87 = a3; *(_DWORD *)(v105 + 20) = v130; *(_DWORD *)(v105 + 52) = v128; *(_DWORD *)(v105 + 12) = v83; *(_DWORD *)(v105 + 24) = v118; *(_DWORD *)(v105 + 60) = v132; if ( a3 <= 0x40 ) break; v4 = a3 - 64; result += 64; v6 = v105 + 64; a3 -= 64; v105 += 64; } if ( a3 < 0x40 && a3 ) { v88 = v90; do { *(_BYTE *)v88 = *(_BYTE *)(v105 - v90 + v88); ++v88; --v87; } while ( v87 ); } result = v89; *(_DWORD *)(v89 + 32) = in9; *(_DWORD *)(v89 + 36) = in10; } return result; }[/code]
[QUOTE=Donkie;50609563]I'm not giving up on this project! I've managed to sneak around in IDA and found this monstrosity of code related to reading the file. It does the rotate/xoring 10 times which leads me to believe this some weird way of encrypting their data? Anybody recognize anything like this? [/QUOTE] If they're xoring it 10 times then it's almost definitely homebrew encryption. Figure out that key and you've got the files.
fwiw, here is C# code to decrypt the .i3d.shape files. Note that the decrypted data is in some binary format. [url=http://pastebin.com/kzwt6qG3]Full Source here[/url] [code] public void DecryptBlocks(uint[] buf) { if (buf.Length % 0x10 != 0) throw new Exception("Expecting 16 byte blocks"); var tempKey = new uint[m_Key.Length]; ulong blockCounter = m_Key[8] | (m_Key[9] << 32); for (var i = 0; i < buf.Length; i += 0x10) { m_Key.CopyTo(tempKey, 0); for (int j = 0; j < 10; j++) { Shuffle1(tempKey, 0x0, 0xC, 0x4, 0x8); Shuffle1(tempKey, 0x5, 0x1, 0x9, 0xD); Shuffle1(tempKey, 0xA, 0x6, 0xE, 0x2); Shuffle1(tempKey, 0xF, 0xB, 0x3, 0x7); Shuffle2(tempKey, 0x3, 0x0, 0x1, 0x2); Shuffle2(tempKey, 0x4, 0x5, 0x6, 0x7); Shuffle1(tempKey, 0xA, 0x9, 0xB, 0x8); Shuffle2(tempKey, 0xE, 0xF, 0xC, 0xD); } for(var j = 0; j < m_Key.Length; j++) buf[i + j] ^= m_Key[j] + tempKey[j]; blockCounter++; m_Key[8] = (uint)(blockCounter & 0xFFFFFFFF); m_Key[9] = (uint)(blockCounter >> 32); } }[/code] [img]http://i.imgur.com/JzCjIum.png[/img]
Haha, I solved it just today actually. Your code is way more cleaned up though. Thanks for the help!
[QUOTE=high;50648671]fwiw, here is C# code to decrypt the .i3d.shape files. Note that the decrypted data is in some binary format. [url=http://pastebin.com/kzwt6qG3]Full Source here[/url] [code][...][/code] [img]http://i.imgur.com/JzCjIum.png[/img][/QUOTE] That looks like a fairly standard graphics buffer dump, with the index buffer being in view here. [QUOTE=Donkie;50648940]Haha, I solved it just today actually. Your code is way more cleaned up though. Thanks for the help![/QUOTE] You probably only have to figure out where the buffer segments start. For large meshes with skeletal animation, there may be multiple draw calls that map bone indices differently (since the amount of full-size matrix parameters you can pass to a shader at once is fairly limited).
How can i run this script ?
For anyone stumbling upon this thread, here is a tool I made that works with the findings of this thread: GitHub It has a few dependencies and you have to compile it yourself, but it outputs in a variety of 3D formats.
Sorry, you need to Log In to post a reply to this thread.