← 미니 토치 DB로 돌아가기

외부 앱 로그 연동 스펙

토치라이트 인피니트의 UE_game.log를 다루는 앱이 빌드를 넘기면 미니 토치 DB가 빌드를 자동 생성합니다. 이 페이지는 AI 코딩 도구에 그대로 붙여넣어 구현을 맡길 수 있도록 작성됐습니다. 방식은 두 가지입니다 — 탭에서 골라 보세요.

A-1. 보내는 방법 — 프로토콜 관리 불필요

로그에서 추출하지 않고, 마지막 GetPlayerData 덤프 구간을 그대로 보냅니다. 해석은 전부 미니 토치 DB가 하므로 새 기능이 추가돼도 앱을 업데이트할 필요가 없습니다.

// ① 로그에서 마지막 "----Socket RecvMessage STT----GetPlayerData----" 부터
//    다음 GetPlayerData 직전(또는 파일 끝)까지 슬라이스 (원문 수 MB여도 OK)
// ② raw DEFLATE 압축 (RFC 1951 — Python wbits=-15. 실측: 2.5MB → 114KB)
// ③ POST (본문 = 압축 바이트, 압축 후 1.9MB 이하)
POST https://mini-tlidb.winterer.workers.dev/api/logrelay?src=앱이름
  → 200 {"tok": "...", "url": "https://.../craft_search?logdump=...", "expiresInSec": 1800}
// ④ 응답의 url을 브라우저로 열면 끝 — 가져오기 확인 창이 뜹니다
프라이버시: 전달된 로그는 임시 보관입니다 — 5회 수령 또는 30분 경과 시 서버에서 삭제됩니다. 서버는 내용을 해석·열람·집계하지 않으며, 파싱은 사용자 브라우저 안에서만 이루어집니다. 압축하지 않은 원문 POST도 동작하지만(1.9MB 이하일 때) 압축을 권장합니다.
주의: 기본 라이브러리 User-Agent(예: python-urllib)는 Cloudflare 봇 필터에 403으로 걸릴 수 있습니다 — User-Agent: 앱이름/버전 형태로 지정해주세요. (실측: curl UA는 통과)
속도 제한: 1분에 10건이면 10분 차단, 10분에 50건이면 24시간 차단(429 + retryAfterSec). 전체 동시 보관 상한 초과 시 503. 정상 사용(사용자당 1회)에는 해당 없습니다.

A-2. 수신 측 동작

방식 B는 앱이 로그에서 ID를 직접 추출해 보내는 1세대 방식입니다. 필드가 늘 때마다 앱 업데이트가 필요하므로 신규 연동은 방식 A를 권장합니다. (MLI1은 계속 지원됩니다)

B-1. URL 형식

https://mini-tlidb.winterer.workers.dev/craft_search?logimport=<CODE>

B-2. 페이로드 JSON

모든 필드는 선택입니다. 모르는 값은 생략하세요 (0이나 null로 채우지 말 것). ID는 문자열/숫자 모두 허용.

{
  "v": 1,                      // 필수. 페이로드 버전
  "char": "닉네임#1234",        // 캐릭터 이름 → 빌드 이름이 됨
  "hero": "310",               // HeroId (로그의 +HeroId [N])
  "heroPur": "1002",           // HeroPurchaseId — 끝자리가 특성 번호
  "src": "mttd",               // (권장) 보내는 앱의 식별자 — 영숫자·-_ 16자 이내. 유입 집계에 쓰입니다

  // 착용 스킬: 키 = 게임 -6 페이지 SlotId 그대로, 값 = 스킬 ConfigBaseId
  //   1~5 = 액티브 메인 / N01~N05 = 액티브 N의 보조 소켓 / 1001+6(g-1) = 패시브 g 메인, +1~+5 = 보조
  "skills": { "1": 7226, "101": 8000, "1001": 8300 },

  // 착용 장비 (게임 -1 페이지, 슬롯 1~10):
  //   1=투구 2=무기 3=방패 4=갑옷 5=목걸이 6=반지1 7=반지2 8=벨트 9=장갑 10=신발
  "gear": [
    { "slot": 5, "base": 4528, "uniq": 113280 },   // 레전드: uniq(UniqueItmId)만으로 충분
    { "slot": 1, "base": 3811, "uniq": 0,          // 제작/블러드: 어픽스 ID를 로그 섹션별로
      "baseAffix": ["1507205"],                    //   BaseAffix → 기본 옵션
      "prefix": ["130720000", "105720101"],        //   Prefix    → 메인 옵션
      "suffix": ["108720200"],                     //   Suffix    → 서브 옵션
      "chip": ["160507006"],                       //   ChipAffix → 무기 타워 시퀀스 (무기 슬롯만)
      "enchant": ["3704000"] }                     //   EnchantAffixs → 조향 (벨트 슬롯만)
  ],

  // 장착 신격 석판이 부여하는 재능 노드 ID 전부 (BaseTalentList + EnchantTalentList)
  // 같은 노드가 여러 장이면 그대로 여러 번 — 수신 측이 ×N으로 표시
  "slate": ["54060200", "30070500", "30070500"],

  // (선택) 석판 인스턴스별 상세 — 있으면 수신 측이 출처 아이템·보드 배치까지 복원하고 slate 대신 사용
  // 원소 = [UniqueItmId(0이면 BaseId — 신격 계열), Index(열×10+행, 1-based), Direction(0~3, 없으면 0), [노드ID...], InfluenceId(선택)]
  // InfluenceId: 빛이 된 나방(1001)의 복제 방향이 여기 실림 (1001위·1002왼쪽·1003아래·1004오른쪽) — 블록의 +InfluenceId 값 그대로
  // 덤프 dressingTalentBlocks의 +N+Index [n] 블록에서 PageId=-4인 항목의 값을 가공 없이 그대로 옮기면 됨
  // 명왕 신격처럼 Direction이 로그에 없는 석판은 0으로 두면 수신 측이 들어가는 회전을 자동 판별
  "sb": [[1003, 24, 1, ["24020200", "24001100"]], [1001, 14, 0, [], 1001], [394004, 25, 0, ["90000108", "43040200"]]],

  // 히어로 추억 (base: 71001=근원, 71002=자기 수호, 71003=진격)
  // baseAffix → 고유 옵션(≤2), suffix → 랜덤 옵션(≤2). BaseAttrAffix(403대역)는 보내지 말 것(미지원)
  "mems": [
    { "base": 71001, "baseAffix": ["402174201"], "suffix": ["402166101"],
      "awaken": ["500202100"] }                  // (선택) 부활 옵션 — 덤프 AwakenAffix의 Id (5xxx 대역)
  ],

  // 배치한 핵심 재능 geniusId (덤프 bonusGeniusInfo의 value+geniusId, 최대 5)
  "genius": { "core": ["42001300", "20002200"] },

  // 운명 휠에 소켓한 천명·숙명의 게임 id (덤프 tattoInfo의 BaseId, 350xxx대역)
  // 같은 숙명 여러 개면 그대로 여러 번 — 수신 측이 ×N으로 표시
  "dst": ["350744", "350100", "350100"],

  // 제노 프리즘 (덤프 prismInfo 블록): gid=꽂힌 재능 노드, base=프리즘 게임 id(360xxx),
  // slots=옵션 어픽스 ID [1칸,2칸,3칸] (PrefixSlots+N+affixId, 빈 칸은 null)
  "prism": { "gid": "21030200", "base": "360614",
             "slots": ["1234308011", "1234300681", "1234333214"] }
}

B-3. 로그에서 값을 뽑는 방법 (참고)

UE_game.log는 UTF-8(BOM) 텍스트입니다. 시간순으로 한 줄씩 처리하세요.

정보로그 라인 패턴
캐릭터 전환 시작----Socket RecvMessage STT----GetPlayerData---- — 이 지점에서 상태 전부 리셋 (한 로그에 캐릭터 여러 명 가능)
착용 변화 이벤트ItemChange@ (Add|Update|Delete) Id=<cfgId>_<uuid> BagNum=N in PageId=<P> SlotId=<S>
P=-1 장비 / P=-6 스킬 / P=-4 석판. Delete는 슬롯 비움. 시간순 재생 = 최종 착용 상태
아이템 상세(어픽스)로그인 덤프 트리의 +Id [<cfgId>_<uuid>](인스턴스), +UniqueItmId [N](0이면 제작), +BaseAffix+/+Prefix+/+Suffix+/+ChipAffix+(무기 시퀀스)/+EnchantAffixs+(벨트 조향) 섹션 아래 +Id [6~9자리]가 어픽스 ID. 블록은 +PageId [ 줄에서 끝남
석판 노드+BaseTalentList+1 [id] 뒤로 +2 [id], +3 [id]… 로 이어짐 (+EnchantTalentList 동일)
추억+reminiscenceOverView 아래 아이템 블록 — +PageId 없이 +state [Active]로 끝남. BaseId 71001~71003
핵심 재능덤프 +geniusInfos+ 아래 +value+geniusId [id] — 계열(geniusCareerId)마다 bonusGeniusInfo+1+value+geniusId [id], 여러 개면 +2+value+geniusId [id]로 이어짐. 이 id들을 genius.core로
운명 (천명·숙명)덤프 +pointDatas+의 +tattoInfo+ 블록 안 +BaseId [350xxx] — 소켓된 천명·숙명의 게임 id. 이 값들을 dst로 (같은 숙명 여러 개면 여러 번). selectContract만 있고 tattoInfo 없는 포인트는 보내지 않음
제노 프리즘덤프 +prismInfo+geniusId [노드id] 블록 — 안의 +BaseId [360xxx]가 프리즘 종류, +PrefixSlots+N+affixId [id](N=1~3)가 옵션 칸. prismEffectPositions에서 블록 종료
석판 배치(sb)덤프 +dressingTalentBlocks+N+Index [n] 블록 중 +PageId [-4]인 것 — Index=보드 위치(열×10+행, 1-based), 블록 안 +UniqueItmId [id](0이면 +BaseId 사용)=석판 종류, +Direction [0~3](없으면 0)=반시계 90° 회전, BaseTalentList·EnchantTalentList=꽂힌 노드
히어로+HeroId [N], +HeroPurchaseId [N]
캐릭터 이름+Name [닉네임] (덤프당 1회)

B-4. 인코딩 참조 구현

Python:

import json, zlib, base64
def encode_mli1(payload: dict) -> str:
    raw = json.dumps(payload, ensure_ascii=False, separators=(',', ':')).encode('utf-8')
    c = zlib.compressobj(9, zlib.DEFLATED, -15)   # raw deflate
    d = c.compress(raw) + c.flush()
    return 'MLI1.' + base64.urlsafe_b64encode(d).decode().rstrip('=')

JavaScript (브라우저/Node 18+):

async function encodeMli1(payload) {
  const raw = new TextEncoder().encode(JSON.stringify(payload));
  const ds = new Blob([raw]).stream().pipeThrough(new CompressionStream('deflate-raw'));
  const buf = new Uint8Array(await new Response(ds).arrayBuffer());
  const b64 = btoa(String.fromCharCode(...buf)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
  return 'MLI1.' + b64;
}

Kotlin/Java: Deflater(9, /*nowrap=*/true) + Base64.getUrlEncoder().withoutPadding()

B-5. 테스트 벡터 (구현 검증용)

아래 JSON을 인코딩하면 아래 문자열이 나와야 합니다 (키 순서·공백 없음 유지 시). 인코더가 달라 바이트가 달라도 디코드 결과가 같은 JSON이면 유효합니다.

{"v":1,"char":"윈터러#9987","hero":"310","heroPur":"1002","skills":{"1":7226,"101":8000,"102":7478,"1001":8300,"1002":7606},"gear":[{"slot":5,"base":4528,"uniq":113280},{"slot":1,"base":3811,"uniq":0,"baseAffix":["1507205"],"prefix":["130720000","105720101"],"suffix":["108720200"]}],"slate":["54060200","30070500","30070500"],"mems":[{"base":71001,"baseAffix":["402174201"],"suffix":["402166101"]}]}
MLI1.XY89TsQwEIXv8mhdjB3_rTtuQI9SBORlI7IsxBuEFKWj4BBIXICCewF3YCYhSKyr52_ezLwZ8YikFa53TY-Er9eX7-ePz7f3s80mBijscn9gXmn6_VwM4tNEhkG5bbuuII3QSMEYr7jCMhKRSMPUhihyxtWCZ-7JTwo3WfZejijd4YjkFK6akpGsM9w23LUPHE9XJtKkVpNeTVXUejXRAs-32_aJB0I7CoYcaoX7Pq-wEsgPEsOxlrhsKcNfH0XGbEI9SaFrjlm4s-RnrMBXBHL_JVv3eV_mU5ZwQW4-CWXJ6GDN6U7B3s9Rpnr6AQ

B-6. 동작 예시 (실제 빌드)

아래 링크는 실제 캐릭터 로그에서 만든 연동 URL입니다 — 클릭하면 수신 흐름(확인 팝업 → 빌드 생성)을 그대로 볼 수 있습니다.

▶ 예시 빌드 가져오기 실행 (리한 · 성난 그림자 — 스킬 9칸·장비 10슬롯·시퀀스·추억·석판 포함, 1,131자)

인코딩 전 JSON 펼쳐보기 (이 예시의 원본 페이로드)
{
  "v": 1,
  "char": "윈터러#9987",
  "hero": "310",
  "heroPur": "1002",
  "skills": {
    "1": 7054,
    "2": 7175,
    "3": 7130,
    "4": 7289,
    "5": 7291,
    "101": 8019,
    "102": 7435,
    "103": 7451,
    "104": 7464,
    "105": 7469,
    "201": 7415,
    "202": 7514,
    "203": 7786,
    "204": 7412,
    "205": 7789,
    "301": 8003,
    "302": 7407,
    "401": 8006,
    "402": 7614,
    "403": 7616,
    "501": 8006,
    "502": 7614,
    "503": 7616,
    "1001": 8330,
    "1002": 7606,
    "1003": 8603,
    "1007": 8309,
    "1008": 7606,
    "1009": 8603,
    "1013": 8300,
    "1014": 7606,
    "1015": 8603,
    "1019": 8310,
    "1020": 7606
  },
  "gear": [
    {
      "slot": 1,
      "base": 3611,
      "uniq": 0,
      "baseAffix": [
        "1507014"
      ],
      "prefix": [
        "130700000",
        "109700301",
        "104700000"
      ],
      "suffix": [
        "130700100",
        "110700100",
        "104700100"
      ]
    },
    {
      "slot": 2,
      "base": 619,
      "uniq": 0,
      "baseAffix": [
        "1500810"
      ],
      "prefix": [
        "140082000",
        "109080000",
        "140080400"
      ],
      "suffix": [
        "140080800",
        "110080100",
        "140081300"
      ],
      "chip": [
        "160509008"
      ]
    },
    {
      "slot": 3,
      "base": 4211,
      "uniq": 0,
      "baseAffix": [
        "1508015"
      ],
      "prefix": [
        "104800000",
        "140800100",
        "109800300"
      ],
      "suffix": [
        "110800300",
        "130800301",
        "140800200"
      ],
      "chip": [
        "160582080"
      ]
    },
    {
      "slot": 4,
      "base": 3110,
      "uniq": 112162,
      "baseAffix": [
        "60066109"
      ],
      "prefix": [
        "51416211",
        "51416221",
        "51416231",
        "51416241"
      ],
      "suffix": []
    },
    {
      "slot": 5,
      "base": 4537,
      "uniq": 0,
      "baseAffix": [
        "1502015"
      ],
      "prefix": [
        "140201101",
        "130200000",
        "104200000"
      ],
      "suffix": [
        "116200200",
        "140200000",
        "130200100"
      ]
    },
    {
      "slot": 6,
      "base": 380009,
      "uniq": 0,
      "baseAffix": [
        "172102000"
      ],
      "prefix": [
        "251436600",
        "251180901",
        "251432001"
      ],
      "suffix": [
        "51533751",
        "252181201",
        "51533731"
      ]
    },
    {
      "slot": 7,
      "base": 4625,
      "uniq": 0,
      "baseAffix": [
        "1502112"
      ],
      "prefix": [
        "109210200",
        "104210000",
        "140210300"
      ],
      "suffix": [
        "140210700",
        "140210600",
        "130210300"
      ]
    },
    {
      "slot": 8,
      "base": 4725,
      "uniq": 113342,
      "baseAffix": [],
      "prefix": [
        "51534211",
        "51534221",
        "51534241",
        "3704000"
      ],
      "suffix": [
        "51534251",
        "51534261"
      ]
    },
    {
      "slot": 9,
      "base": 3310,
      "uniq": 112323,
      "baseAffix": [],
      "prefix": [
        "51432411",
        "51432421",
        "51432430",
        "51432440"
      ],
      "suffix": [
        "51432451"
      ]
    },
    {
      "slot": 10,
      "base": 4009,
      "uniq": 112403,
      "baseAffix": [],
      "prefix": [
        "56440321",
        "51440331",
        "51440341"
      ],
      "suffix": [
        "51440351"
      ]
    }
  ],
  "slate": [
    "11070100",
    "33070500",
    "42030300",
    "22010300",
    "11070100",
    "11070100",
    "90000108",
    "43040200",
    "42060300",
    "43060100",
    "62020200",
    "34040400",
    "42070500",
    "33070500",
    "24020200",
    "43030300",
    "42070300",
    "42070500",
    "42020200",
    "50040500",
    "33001200",
    "42001200",
    "51040300",
    "41030400"
  ],
  "mems": [
    {
      "base": 71001,
      "baseAffix": [
        "402151902",
        "402172803"
      ],
      "suffix": []
    },
    {
      "base": 71002,
      "baseAffix": [
        "402153401",
        "402153901"
      ],
      "suffix": [
        "402168001",
        "402167601"
      ]
    },
    {
      "base": 71003,
      "baseAffix": [
        "402155401",
        "402155801"
      ],
      "suffix": [
        "402171301",
        "402161501"
      ]
    }
  ]
}
전체 URL 펼쳐보기 (1,131자)
https://mini-tlidb.winterer.workers.dev/craft_search?logimport=MLI1.fVZLbhRBDL1Lse2FXXZ9enbcgD3KIkQTMmICIZMgpCg7FhwCiQuw4F7AHbDd9enumUxWL0_P9ivbVT1P7ovb4OCubi7v3cb9_fH937fff37-ejWOObnB3WzvPwlPCOWfN4-qQwAvxOHDbr8_uM2TQ7dJEHhwXgCmMDhSQDA4FuDzOLigYJRiCCLPgKNCDWAKCjWEgwk0iCMrDAZF6zUsMQaFGhZQC1pYylGhhaFXGIyVMJqqASm0apDEVWGjQmWjJmNLFlHY0AWhC0IXSA9UQXpG64fwMPGiyVELCk6qATsq5Jlm7Bok01ge5K7BMNOoXsZgLYNJ8zy491ud29snd9h_erBJvrs8bN2GIgp-_Lj77DYwka-vr3dfReswQNI6F4O7u99WkoTUP6cVRsHaNxtF4UV-eLxeyHGS4wybXPHF81Bd-eoq6shfNAUZYWWKhfTNFORqUHngI1NG52pKEHa5ODb51c3uzsQRgqSUmcycUnXK_mz_ss5maRU4Q7eXe0NG3b0jqwiFHrSXubXbQv2xVWlDXjSV26hRt2Kyiugx-pXfKEscxcfSsFwekaIWnaDvkDpkXBifGQitV4HSmV75416xkjgdmPS0dcTsT-2a2Jh6MkyhVW6hq12LrS06jpfXLXm0TEtnXg5NMVp6wZhlQ3DCTFps6SxgIJJnyBQeM3qYOqe0NHHmK7VuRR_OdUtGuN6scfJaOoR9ywTTiUvg7UI2SWzdKvLuKjdXqbtCJOLVEq1WJxDX1VHoO2SFlPRywnGz2IcujYsGjW1wtNhn8nTWisyF6xYL9B0SNMhHXpQNCwMIrRmzxREHDGcdRMlOtaxA6pCP9kVZK6v8_vJhWx6DVJ4L0nc1GJS7QOWF8LJX9bHo2hkcdSfkSdEwAi7bIhliCRM2Fq1cJl8ExMD2jJq2Fp558Ny0TM2OaWkdxj2vENyTATY7FQZZ5JpBT1ae8tvt7cG-ZtMckn5iV1dENzrgaD8_FMtPC_ncHj1RPYE_lYDYLuqEx_W1VjrqC14kUT62ZVV6XjqVN8zyhnwqr_4mankxWN6L5_8

B-7. 수신 측 동작

B-8. 호환성 정책 (MLI1)

게임에 새 스킬·아이템·옵션이 추가돼도 송신 측은 아무것도 바꿀 필요가 없습니다. ID를 그대로 보내면 수신 측 데이터 갱신만으로 자동 인식됩니다.

· v1 안에서는 필드가 추가만 되고, 기존 필드의 삭제·의미 변경은 하지 않습니다
· 수신 측은 모르는 키·ID를 조용히 무시합니다 — 송신 측이 앞서 나가도 깨지지 않습니다
· 호환 불가 변경은 접두를 MLI2.로 올려 병행 수신합니다 (MLI1은 계속 동작)
· 이 페이지가 스펙의 최신본입니다: https://mini-tlidb.winterer.workers.dev/logimport_spec

B-9. 제약·주의

토치라이트 인피니트가 남기는 UE_game.log의 구조를 실측으로 정리한 문서입니다. 게임이 공개한 스펙이 아니라 실제 로그를 뜯어 확인한 내용이라, 게임 업데이트로 바뀔 수 있습니다. 같은 정보를 여러 곳에 다른 순서로 적어 두는 로그라서 “함정” 항목을 특히 눈여겨봐 주세요.

C-1. 로그에 정보가 실리는 두 경로

경로언제무엇이 들어 있나
상태 덤프
GetPlayerData
로그인·캐릭터 전환 시 1회 그 시점의 전체 상태 — 장비·스킬·추억·정령·재능·프리즘·석판·운명·히어로
이벤트
ItemChange@
착용·해제할 때마다 어느 칸에 무엇을 꽂았는지 (칸 번호 ↔ 아이템 인스턴스의 연결)
둘 다 필요합니다. 덤프는 아이템의 내용(옵션·수치)을 주지만 어느 칸에 있는지는 이벤트가 알려줍니다. 그래서 파서는 “덤프에서 인스턴스별 내용을 모으고 → 이벤트로 칸에 배치”하는 순서로 재생합니다. 재접속하지 않고 뽑은 로그는 덤프가 없어 재능·운명·프리즘 같은 항목이 통째로 빠집니다.

C-2. 덤프 트리 읽는 법

+skills+skills+1+RareValue [1]      ← 배열 1번째 원소의 시작 (첫 필드가 RareValue)
|      |      | +Id [7415_4a1e...]  ← 같은 원소의 다음 필드들
|      |      | +BaseId [7415]
|      |      | +Level [20]
|      |      +2+RareValue [1]      ← "+2+" = 다음 원소 시작
함정 ① 키 순서가 덤프마다 다릅니다. 같은 아이템이라도 어떤 로그는 Id가 먼저, 어떤 로그는 DynArgs(굴림값)가 먼저 옵니다. “Id를 만나면 그 앞의 값들을 붙인다” 식으로 한 방향만 가정하면 절반의 로그에서 값이 어긋납니다 — 양방향 대기(pending)가 필요합니다.
함정 ② 원소의 첫 필드가 무엇인지도 덤프마다 다릅니다. +29+BaseId·+30+Evalued·+28+RareValue·+6+Exp 등 제각각이라, 경계 판정 키 목록을 좁게 잡으면 앞 항목에 다음 항목 값이 붙습니다. 실제로 Exp가 빠져 있어 스킬 레벨이 옆 아이템에 붙는 버그가 있었습니다. 반대로 Version은 넣으면 안 됩니다 — 어픽스 내부에도 나오는 키라 항목을 잘못 쪼갭니다.
함정 ③ 숫자로 끝나는 줄은 경계가 아닙니다. +DynArgs+1 [16.0]처럼 마지막 조각이 숫자면 그건 값의 연속이지 새 원소가 아닙니다. 이걸 경계로 오인하면 그 뒤에 오는 필드를 통째로 잃습니다.

C-3. 착용 칸 번호 (PageId · SlotId)

ItemChange@ Add Id=3611_e991... in PageId=-1 SlotId=1
PageId대상SlotId
-1장비1 투구 · 2 무기1 · 3 무기2(방패) · 4 갑옷 · 5 목걸이 · 6·7 반지 · 8 벨트 · 9 장갑 · 10 신발
-6스킬1~5 = 액티브 메인 / N01~N05 = 액티브 N의 보조 / 1001+6(g-1) = 패시브 g 메인, +1~+5 = 그 보조
-4석판보드 배치 칸

C-4. 섹션별 구조

각 항목을 눌러 펼쳐 보세요.

장비 BaseId · UniqueItmId · 어픽스
+Item+UniqueItmId [113342]   ← 0보다 크면 레전드, 0이면 제작·일반
|    +Id [4725_b246...]
|    +RareValue [100]
|    +BaseId [4725]           ← 베이스 아이템 (아머·회피·보호막 등 기본 수치의 출처)
|    +BaseAffix+1+Id [520001] ← 기본 옵션
|    +Prefix+1+Id [...]       ← 메인 옵션
|    |       +DynArgs+1 [155.0] ← 실제 굴림값 (없으면 데이터의 범위값을 써야 함)
|    +Suffix+1+Id [...]       ← 서브 옵션
어픽스 섹션뜻
BaseAffix기본 옵션
Prefix / Suffix메인 / 서브 옵션
ChipAffix타워 시퀀스 (무기)
EnchantAffixs조향 (벨트)
FixedBaseAffix역상(제노 프리즘)의 고정 옵션
AwakenAffix / BaseAttrAffix추억의 부활 옵션 / 기본 속성
DynArgs가 핵심입니다. 이 값이 없으면 “크리티컬 +(87–120)%”처럼 범위로만 알 수 있어 계산이 뭉개집니다. 값의 순서는 옵션 문구에 나오는 수치 자리 순서와 같습니다.
함정 — 레전드의 “<랜덤 옵션 1개>” 자리는 실제로 굴린 옵션이 별도 어픽스 ID로 실립니다. 어픽스 개수와 옵션 줄 수가 안 맞는 게 정상이라(기본 속성 등이 섞임), 개수로 짝지으려 하면 실패합니다.
스킬 dressSkillItems · Level · Exp · 771/772 어픽스
+24+Item+Exp [607]
|  |    +Level [1]              ← 스킬 자체 레벨 (기본 20)
|  |    +SkillItemLevel [90]    ← 아이템 레벨 (드랍 레벨) — 스킬 레벨이 아님
|  |    +Prefix+1+Id [771030000]
|  |    |      +DynArgs+1 [16.0]
|  |    +BaseId [7823]
|  +Index [105]                 ← 칸 번호
|  +PageId [-6]
필드뜻
Level스킬 레벨. 보통 20이지만 17·21처럼 다를 수 있어 그대로 써야 정확합니다
Exp경험치. Level과 짝으로 움직입니다
771… 어픽스부귀·숭고 보조의 티어 옵션 (DynArgs = 굴림값)
772… 어픽스모듈 스킬의 프로토콜. '7720000' + (용어 id − 2000) 규칙
함정 — 부귀·숭고 보조의 Level은 일반 스킬 레벨이 아니라 0~5짜리 육성 레벨이고, 로그는 1부터 셉니다(Level 1 = 미육성 0). 이걸 일반 레벨로 쓰면 엉뚱한 값이 됩니다. SkillItemLevel도 스킬 레벨이 아니라 아이템 레벨입니다.
추억 71001~71003 · state · RareValue
+Id [71002_....]        ← 71001 근원 / 71002 자기 수호 / 71003 진격
+RareValue [4]          ← 5 절대자 · 4 엘리트 · 3 레어 · 2 매직 · 1 일반
+BaseAttrAffix+1+Id [4030312]  ← 기본 속성 (403 + 옵션코드 2자리 + 성장단계)
+AwakenAffix+1+Id [...] ← 부활 옵션
+state [Active]         ← Active가 아니면 미착용

기본 속성 코드(4031 뒤 2자리): 01 힘 · 02 민첩 · 03 지혜 · 04 대미지 · 05 소환 대미지 · 06 HP · 07 MP · 08 아머 · 09 회피 · 10 보호막 · 11 공격속도 · 12 시전속도 · 13 이동속도 · 14 소환 공속 · 15 소환 시속

함정 — state가 어픽스보다 먼저 나오는 덤프도, 나중에 나오는 덤프도 있습니다. 한쪽만 가정하면 부위가 한 칸씩 밀립니다. 이미 확정한 인스턴스 Id가 다시 나오면 그건 항목의 끝 표시이지 새 항목이 아닙니다.
정령 ContractDatas · pointDatas · servantData
+ContractDatas+1+servantId [5330]
|              | +areaId [1]        ← 1~3 전투 / 4~6 드롭
+pointDatas+1+contractPointId [109] ← 영역×100 + 자리
|          | +selectContract [1]    ← 1 정령 노드 / 2 운명으로 교체
|          | +isActive [true]
+ServantInfos+servantData+1+starLevel [5]
|            |           | +servantId [4320]
승급 = starLevel + 1 (로그가 0부터 셉니다). starLevel 줄이 아예 없으면 미승급(=승급 1)입니다.
함정 ① — servantId와 starLevel의 순서가 덤프마다 반대입니다. 한 방향만 가정하면 다른 정령의 승급을 물려받습니다.
함정 ② — 보유 정령 목록은 덤프당 30개씩만 실립니다. 재접속이 여러 번인 로그는 덤프마다 목록이 달라서, 합쳐야 더 많이 복원됩니다. 그래도 목록에 없는 정령은 알 수 없습니다.
함정 ③ — ContractDatas의 첫 줄에도 servantId가 실려서, 위 목록의 종료 판정을 먼저 하지 않으면 섞입니다.
재능 보드 geniusInfos · geniusId · point · 핵심 재능
+geniusInfos+info+1+bonusGeniusInfo+1+value+geniusId [30001100] ← 배치한 핵심 재능
|           |    | +geniusCareerId [3]
|           |    | +geniusList+1+position+y [1]
|           |    |          | |        +x [3]
|           |    |          | +point [3]          ← 찍은 포인트 (효과가 이 배수로 커집니다)
|           |    |          | +geniusId [30010300]
  • geniusId 앞 2자리 = 어느 보드(계열)인지
  • point가 없는 노드는 찍지 않은 것 — 블록 경계에서 반드시 비워야 앞 노드의 값이 새지 않습니다
  • 역상(반사)으로 온 노드는 원래 자리가 아닌 칸에 놓입니다
제노 프리즘 · 역상 prismInfo · PrefixSlots
+prismInfo+1+BaseId [360503]     ← 프리즘 종류
|         | +geniusId [...]      ← 꽂힌 노드 (앞 2자리 = 보드)
|         | +PrefixSlots+1+affixId [...]  ← 옵션 3칸
|         | +FixedBaseAffix+1+Id [1224626031] ← 역상의 고정 옵션
함정 — 프리즘과 역상은 서로 다른 보드에 꽂혀 있을 수 있습니다. 둘을 한 덩어리로 읽으면 역상 툴팁에 프리즘 옵션이 섞여 나옵니다.
석판 PageId -4 · ShapeId · Direction · Index
+ShapeId [1011]        ← 신격 석판의 실제 모양 (BaseId는 기본형만 알려줌)
+Direction [2]         ← 회전 (키가 없으면 0)
+InfluenceId [1003]    ← 빛이 된 나방의 복제 방향
+BaseTalentList+1 [...] ← 석판 고정 옵션
+EnchantTalentList+1 [...] ← 소켓에 꽂은 낙인
함정 — 보드 좌표 Index는 열×10 + 행입니다(행×10+열이 아닙니다). 그리고 화면에 그리는 좌표계는 로그 좌표의 전치라, 그대로 찍으면 뒤집힌 보드가 나옵니다. BaseTalentList와 EnchantTalentList가 나오는 순서도 덤프마다 반대입니다.
운명 · 히어로 tattoInfo · HeroId · activeId
+tattoInfo+Level [90]
|         +BaseId [350007]   ← 소켓된 천명·숙명 (블록의 첫 BaseId)
+HeroId [...]
+HeroPurchaseId [...]
+heroCharacterv3...+activeId+1 [...]  ← 고른 히어로 특성
  • 히어로 특성 activeId의 마지막 두 자리가 좌표입니다 — 십의 자리 = 단계(레벨 조건 45/60/75), 일의 자리 = 그 단계 안 순번
함정 — tattoInfo 블록에도 Level이 있어서(장비 레벨 90), 스킬·장비 레벨을 읽을 때 여기까지 긁으면 엉뚱한 값이 섞입니다. 캐릭터 레벨(Level [100])도 마찬가지입니다.

C-5. 함정 한눈에 보기

증상원인
수치가 범위(87~120%)로만 나옴DynArgs를 안 읽음
옵션이 한 부위씩 밀림state 위치가 덤프마다 달라 항목 경계가 어긋남
어떤 아이템의 값이 옆 아이템에 붙음원소 경계 키 목록이 좁음 (Exp 등 누락)
필드가 통째로 사라짐+DynArgs+1처럼 숫자로 끝나는 줄을 경계로 오인
스킬 레벨이 100처럼 말이 안 됨캐릭터 레벨·운명 블록의 Level을 같이 읽음
정령 승급이 남의 것servantId·starLevel 순서를 한 방향만 가정
보유 정령 일부만 나옴덤프당 30개 제한 — 여러 덤프를 합쳐야 함
보드가 뒤집혀 보임Index가 열×10+행이고, 화면 좌표는 전치
재능·운명·프리즘이 통째로 없음재접속 없이 뽑은 로그라 GetPlayerData 덤프가 없음

C-6. 로그를 직접 파싱하지 않으려면

여기까지 읽고 “직접 하고 싶지 않다”면 그게 정상입니다 — 방식 A(원시 로그 릴레이)는 덤프 구간을 통째로 보내기만 하면 이 해석을 전부 미니 토치 DB가 맡습니다. 위 함정들도 이미 반영돼 있고, 새로 발견될 때마다 여기서 고쳐집니다.

변경 이력