Author SHA1 Message Date
ff14wedandgithub-actions[bot] d4e943f31a Update for 7.50h (2026.05.01.0000.0000) 2026-05-07 12:12:42 +00:00
Flawed 28ea068169 fix typo 2026-04-28 00:55:02 -07:00
Flawed 555829c343 Update for 7.50 2026-04-28 00:36:25 -07:00
Flawed b01e2dc3b6 7.5 opcode offset hack 2026-04-28 00:33:31 -07:00
FlawedandGitHub 01017f29ff Merge pull request #8 from xivdev/auto-update-964e2e9
Update for 7.45h2 (2026.03.17.0000.0000)
2026-03-24 00:41:33 -07:00
ff14wedandgithub-actions[bot] ceabd258b2 Update for 7.45h2 (2026.03.17.0000.0000) 2026-03-24 07:39:04 +00:00
Flawed 964e2e909d Fix dependency order in workflow 2026-03-11 21:55:47 -07:00
Flawed 8bfa266661 Only download latest ffxiv exes if there is a new patch 2026-03-11 21:53:24 -07:00
Flawed 4ba7cfaea1 More robust hotfix version handling in automation 2026-03-11 21:50:14 -07:00
FlawedandGitHub 1c3a2e2ffe Merge pull request #7 from xivdev/auto-update-65e6d9a
Update for 7.45h (2026.03.07.0000.0000)
2026-03-11 01:32:58 -07:00
ff14wedandgithub-actions[bot] 0d851d2d4f Update for 7.45h (2026.03.07.0000.0000) 2026-03-11 08:31:58 +00:00
FlawedandGitHub 65e6d9a197 Hack to support hotfixes for now
Will make this check more robust later
2026-03-11 01:30:39 -07:00
9 changed files with 25970 additions and 1520 deletions
+10 -11
View File
@@ -15,13 +15,6 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
- name: Download Latest ffxiv_dx11.exe
id: downloader-latest
uses: WorkingRobot/ffxiv-downloader@v8
with:
output-path: latest
regex: '^ffxiv_dx11\.exe$'
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v6 uses: actions/setup-python@v6
with: with:
@@ -30,14 +23,20 @@ jobs:
- name: Fetch Patch Info and Update Versions - name: Fetch Patch Info and Update Versions
id: versions id: versions
if: steps.downloader-latest.outputs.updated == 'true' || github.event_name == 'workflow_dispatch'
run: python automation/ffxiv_info.py >> $GITHUB_OUTPUT run: python automation/ffxiv_info.py >> $GITHUB_OUTPUT
- name: Download Latest ffxiv_dx11.exe
if: steps.versions.outputs.is_new == 'true'
uses: WorkingRobot/ffxiv-downloader@v8
with:
output-path: latest
regex: '^ffxiv_dx11\.exe$'
- name: Download Previous ffxiv_dx11.exe - name: Download Previous ffxiv_dx11.exe
if: steps.versions.outputs.is_new == 'true' if: steps.versions.outputs.is_new == 'true'
uses: WorkingRobot/ffxiv-downloader@v8 uses: WorkingRobot/ffxiv-downloader@v8
with: with:
version: ${{ steps.versions.outputs.date_prev }} version: ${{ steps.versions.outputs.thaliak_version_prev }}
output-path: previous output-path: previous
regex: '^ffxiv_dx11\.exe$' regex: '^ffxiv_dx11\.exe$'
@@ -78,8 +77,8 @@ jobs:
uses: peter-evans/create-pull-request@v8 uses: peter-evans/create-pull-request@v8
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "Update for ${{ steps.versions.outputs.retail_new }} (${{ steps.versions.outputs.date_new }})" commit-message: "Update for ${{ steps.versions.outputs.retail_new }} (${{ steps.versions.outputs.thaliak_version_new }})"
title: "Update for ${{ steps.versions.outputs.retail_new }} (${{ steps.versions.outputs.date_new }})" title: "Update for ${{ steps.versions.outputs.retail_new }} (${{ steps.versions.outputs.thaliak_version_new }})"
body: | body: |
This automatic update contains the following changes: This automatic update contains the following changes:
- Updated `ffxiv_versions_global.json` - Updated `ffxiv_versions_global.json`
+4 -1
View File
@@ -135,7 +135,10 @@ def get_packet_handler_opcode_offset(r2, exe_file: str):
r2.cmd(f"s {opcode_offset_target}") r2.cmd(f"s {opcode_offset_target}")
r2.cmd("aei; aeim; aeip") # Initialize ESIL VM, stack, and instruction pointer r2.cmd("aei; aeim; aeip") # Initialize ESIL VM, stack, and instruction pointer
if semver.compare(sem_ver, "7.4.0") >= 0: if semver.compare(sem_ver, "7.5.0") >= 0:
offset_reg = "r13"
opcode_offset = _get_opcode_offset_post_72(r2, offset_reg)
elif semver.compare(sem_ver, "7.4.0") >= 0:
offset_reg = "r15" offset_reg = "r15"
opcode_offset = _get_opcode_offset_post_72(r2, offset_reg) opcode_offset = _get_opcode_offset_post_72(r2, offset_reg)
elif semver.compare(sem_ver, "7.3.8") >= 0: elif semver.compare(sem_ver, "7.3.8") >= 0:
+66 -42
View File
@@ -79,12 +79,6 @@ def load_versions() -> List[dict]:
return [] return []
def get_latest_version_entry() -> Optional[dict]:
"""Returns the most recent version entry from the version file."""
data = load_versions()
return data[-1] if data else None
def fetch_latest_thaliak_patch() -> Optional[str]: def fetch_latest_thaliak_patch() -> Optional[str]:
""" """
Fetches the latest date-based version string from the Thaliak API. Fetches the latest date-based version string from the Thaliak API.
@@ -131,7 +125,35 @@ def extract_patch_version(html):
if not html or not (m := re.search(r"patch\s+(\d+\.\d+)", html, re.I)): if not html or not (m := re.search(r"patch\s+(\d+\.\d+)", html, re.I)):
return None return None
v = m.group(1) v = m.group(1)
return v + "0" if len(v.split(".")[1]) == 1 else v if len(v.split(".")[1]) == 1:
v += "0"
return v
def get_next_hotfix_version(base_v, existing_versions):
matching = [
v["retail_version"]
for v in existing_versions
if v["retail_version"].startswith(base_v)
]
hotfixes = [v for v in matching if "h" in v]
if not hotfixes:
return f"{base_v}h"
# Extract numeric suffix: h -> 1, h2 -> 2, etc.
nums = [int(h.split("h")[1]) if h.split("h")[1].isdigit() else 1 for h in hotfixes]
return f"{base_v}h{max(nums) + 1}"
def determine_retail_version(base_v, existing_versions, thaliak_version_new):
matching = [v for v in existing_versions if v["retail_version"].startswith(base_v)]
latest = matching[-1] if matching else None
# If we have a record for this base version but the Thaliak version is new, it's a hotfix.
if latest and thaliak_version_new and thaliak_version_new != latest["version_string"]:
return get_next_hotfix_version(base_v, existing_versions)
return latest["retail_version"] if latest else base_v
def is_maintenance_post(title): def is_maintenance_post(title):
@@ -140,14 +162,7 @@ def is_maintenance_post(title):
return "all worlds" in t and "maintenance" in t and "follow-up" not in t return "all worlds" in t and "maintenance" in t and "follow-up" not in t
def format_retail_version(v, count): def scrape_latest_maintenance(thaliak_version_new=None):
"""Formats the version with hotfix suffixes: 7.41, 7.41h, 7.41h2, etc."""
if count <= 1:
return v
return f"{v}h{count - 1 if count > 2 else ''}"
def scrape_latest_maintenance():
""" """
Scrapes the Lodestone to find the most recent 'All Worlds Maintenance' post. Scrapes the Lodestone to find the most recent 'All Worlds Maintenance' post.
""" """
@@ -164,6 +179,7 @@ def scrape_latest_maintenance():
return None return None
news_items = parse_lodestone_news_list(html) news_items = parse_lodestone_news_list(html)
existing_versions = load_versions()
for item in news_items: for item in news_items:
title = item["title"] title = item["title"]
@@ -173,12 +189,18 @@ def scrape_latest_maintenance():
continue continue
detail_html = fetch_url(link) detail_html = fetch_url(link)
version = extract_patch_version(detail_html) if not detail_html:
continue
if version: base_version = extract_patch_version(detail_html)
# We assume the first (most recent) valid maintenance post if not base_version:
# with a patch version is our target. continue
return {"retail_version": version, "title": title, "url": link}
retail_version = determine_retail_version(
base_version, existing_versions, thaliak_version_new
)
return {"retail_version": retail_version, "title": title, "url": link}
return None return None
@@ -187,15 +209,15 @@ def get_version_info() -> Optional[dict]:
""" """
Returns the latest version information by combining Thaliak and Lodestone data. Returns the latest version information by combining Thaliak and Lodestone data.
""" """
date_new = fetch_latest_thaliak_patch() thaliak_version_new = fetch_latest_thaliak_patch()
maintenance = scrape_latest_maintenance() maintenance = scrape_latest_maintenance(thaliak_version_new=thaliak_version_new)
if not maintenance and not date_new: if not maintenance and not thaliak_version_new:
return None return None
return { return {
"retail_version": maintenance["retail_version"] if maintenance else "Unknown", "retail_version": maintenance["retail_version"] if maintenance else "Unknown",
"version_string": date_new, "version_string": thaliak_version_new,
"title": maintenance["title"] if maintenance else "Unknown", "title": maintenance["title"] if maintenance else "Unknown",
"url": maintenance["url"] if maintenance else "Unknown", "url": maintenance["url"] if maintenance else "Unknown",
} }
@@ -204,27 +226,26 @@ def get_version_info() -> Optional[dict]:
def get_patch_context(): def get_patch_context():
""" """
Returns a dictionary containing: Returns a dictionary containing:
- retail_prev, date_prev: The latest registered version. - retail_prev, thaliak_version_prev: The latest registered version.
- retail_new, date_new: The latest versions found externally. - retail_new, thaliak_version_new: The latest versions found externally.
- is_new: True if date_new is not in the registry. - is_new: True if thaliak_version_new is not in the registry.
""" """
versions = load_versions() versions = load_versions()
prev = versions[-1] if versions else None prev = versions[-1] if versions else None
date_new = fetch_latest_thaliak_patch() thaliak_version_new = fetch_latest_thaliak_patch()
maintenance = scrape_latest_maintenance(thaliak_version_new=thaliak_version_new)
maintenance = scrape_latest_maintenance()
retail_new = maintenance["retail_version"] if maintenance else None retail_new = maintenance["retail_version"] if maintenance else None
is_new = False is_new = thaliak_version_new is not None and not any(
if date_new: v["version_string"] == thaliak_version_new for v in versions
is_new = not any(v["version_string"] == date_new for v in versions) )
return { return {
"retail_prev": prev["retail_version"] if prev else None, "retail_prev": prev["retail_version"] if prev else None,
"date_prev": prev["version_string"] if prev else None, "thaliak_version_prev": prev["version_string"] if prev else None,
"retail_new": retail_new, "retail_new": retail_new,
"date_new": date_new, "thaliak_version_new": thaliak_version_new,
"is_new": is_new, "is_new": is_new,
} }
@@ -236,23 +257,26 @@ def update_and_get_info():
""" """
ctx = get_patch_context() ctx = get_patch_context()
if ctx["date_prev"]: if ctx["thaliak_version_prev"]:
print(f"date_prev={ctx['date_prev']}") print(f"thaliak_version_prev={ctx['thaliak_version_prev']}")
if ctx["retail_prev"]: if ctx["retail_prev"]:
print(f"retail_prev={ctx['retail_prev']}") print(f"retail_prev={ctx['retail_prev']}")
if ctx["date_new"]: if ctx["thaliak_version_new"]:
print(f"date_new={ctx['date_new']}") print(f"thaliak_version_new={ctx['thaliak_version_new']}")
if ctx["retail_new"]: if ctx["retail_new"]:
print(f"retail_new={ctx['retail_new']}") print(f"retail_new={ctx['retail_new']}")
print(f"is_new={'true' if ctx['is_new'] else 'false'}") print(f"is_new={'true' if ctx['is_new'] else 'false'}")
if ctx["is_new"] and ctx["date_new"] and ctx["retail_new"]: if ctx["is_new"] and ctx["thaliak_version_new"] and ctx["retail_new"]:
data = load_versions() data = load_versions()
data.append( data.append(
{"retail_version": ctx["retail_new"], "version_string": ctx["date_new"]} {
"retail_version": ctx["retail_new"],
"version_string": ctx["thaliak_version_new"],
}
) )
print( print(
f"Added new version mapping: {ctx['retail_new']} -> {ctx['date_new']}", f"Added new version mapping: {ctx['retail_new']} -> {ctx['thaliak_version_new']}",
file=sys.stderr, file=sys.stderr,
) )
with open(VERSIONS_FILE, "w") as f: with open(VERSIONS_FILE, "w") as f:
+13 -1
View File
@@ -132,7 +132,19 @@
"version_string": "2026.02.20.0000.0000" "version_string": "2026.02.20.0000.0000"
}, },
{ {
"retail_version": "7.45", "retail_version": "7.45h",
"version_string": "2026.03.07.0000.0000" "version_string": "2026.03.07.0000.0000"
},
{
"retail_version": "7.45h2",
"version_string": "2026.03.17.0000.0000"
},
{
"retail_version": "7.50",
"version_string": "2026.04.21.0000.0000"
},
{
"retail_version": "7.50h",
"version_string": "2026.05.01.0000.0000"
} }
] ]
+1465 -1465
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+6158
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff