перевод текстов
This commit is contained in:
@@ -0,0 +1,39 @@
|
|||||||
|
# Solo Random Dungeon client patch
|
||||||
|
|
||||||
|
The server recognizes five custom `LFGDungeons.dbc` entries:
|
||||||
|
|
||||||
|
| Custom ID | Stock category |
|
||||||
|
| --- | --- |
|
||||||
|
| 1258 | Random Classic Dungeon (258) |
|
||||||
|
| 1259 | Random Burning Crusade Dungeon (259) |
|
||||||
|
| 1260 | Random Burning Crusade Heroic (260) |
|
||||||
|
| 1261 | Random Lich King Dungeon (261) |
|
||||||
|
| 1262 | Random Lich King Heroic (262) |
|
||||||
|
|
||||||
|
The generated server DBC is at:
|
||||||
|
|
||||||
|
`client_patch/enUS/DBFilesClient/LFGDungeons.dbc`
|
||||||
|
|
||||||
|
It is mounted into `ac-worldserver` by `docker-compose.override.yml`.
|
||||||
|
|
||||||
|
## Client installation
|
||||||
|
|
||||||
|
Use the DBC extracted from the exact locale of the target 3.3.5a client:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./tools/build-solo-lfg-dbc.py \
|
||||||
|
/path/to/client/DBFilesClient/LFGDungeons.dbc \
|
||||||
|
/tmp/solo-rdf/DBFilesClient/LFGDungeons.dbc
|
||||||
|
```
|
||||||
|
|
||||||
|
Put the generated `DBFilesClient/LFGDungeons.dbc` into a client MPQ patch
|
||||||
|
loaded after the stock locale archives. For a Russian client this is normally
|
||||||
|
a new archive under `Data/ruRU/` named like `patch-ruRU-S.MPQ`.
|
||||||
|
|
||||||
|
After installing the patch, delete the client's `Cache` directory. The
|
||||||
|
Dungeon Finder list will contain separate solo random categories appropriate
|
||||||
|
for the character's level.
|
||||||
|
|
||||||
|
Do not replace a localized client DBC with the bundled enUS server DBC. Run
|
||||||
|
the generator against that client's own DBC so all stock localized rows remain
|
||||||
|
intact.
|
||||||
Binary file not shown.
@@ -8,3 +8,4 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ./modules:/azerothcore/modules:ro
|
- ./modules:/azerothcore/modules:ro
|
||||||
- ./lua_scripts:/azerothcore/lua_scripts
|
- ./lua_scripts:/azerothcore/lua_scripts
|
||||||
|
- ./client_patch/enUS/DBFilesClient/LFGDungeons.dbc:/azerothcore/env/dist/data/dbc/LFGDungeons.dbc:ro
|
||||||
|
|||||||
@@ -108,6 +108,8 @@ namespace lfg
|
|||||||
RANDOM_DUNGEON_HEROIC_WOTLK = 262
|
RANDOM_DUNGEON_HEROIC_WOTLK = 262
|
||||||
};
|
};
|
||||||
|
|
||||||
|
constexpr uint32 LFG_SOLO_RANDOM_DUNGEON_ID_OFFSET = 1000;
|
||||||
|
|
||||||
class Lfg5Guids;
|
class Lfg5Guids;
|
||||||
|
|
||||||
typedef std::list<Lfg5Guids> Lfg5GuidsList;
|
typedef std::list<Lfg5Guids> Lfg5GuidsList;
|
||||||
|
|||||||
@@ -519,6 +519,7 @@ namespace lfg
|
|||||||
LfgJoinResultData joinData;
|
LfgJoinResultData joinData;
|
||||||
LfgGuidSet players;
|
LfgGuidSet players;
|
||||||
uint32 rDungeonId = 0;
|
uint32 rDungeonId = 0;
|
||||||
|
bool soloQueue = false;
|
||||||
bool isContinue = grp && grp->isLFGGroup() && GetState(gguid) != LFG_STATE_FINISHED_DUNGEON;
|
bool isContinue = grp && grp->isLFGGroup() && GetState(gguid) != LFG_STATE_FINISHED_DUNGEON;
|
||||||
|
|
||||||
if (grp && (grp->isBGGroup() || grp->isBFGroup()))
|
if (grp && (grp->isBGGroup() || grp->isBFGroup()))
|
||||||
@@ -611,6 +612,10 @@ namespace lfg
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
soloQueue = rDungeonId && IsSoloRandomDungeon(rDungeonId);
|
||||||
|
if (soloQueue && grp)
|
||||||
|
joinData.result = LFG_JOIN_PARTY_NOT_MEET_REQS;
|
||||||
|
|
||||||
if (!isRaid && joinData.result == LFG_JOIN_OK)
|
if (!isRaid && joinData.result == LFG_JOIN_OK)
|
||||||
{
|
{
|
||||||
// Check player or group member restrictions
|
// Check player or group member restrictions
|
||||||
@@ -775,7 +780,7 @@ namespace lfg
|
|||||||
LfgRolesMap rolesMap;
|
LfgRolesMap rolesMap;
|
||||||
rolesMap[guid] = roles;
|
rolesMap[guid] = roles;
|
||||||
LFGQueue& queue = GetQueue(guid);
|
LFGQueue& queue = GetQueue(guid);
|
||||||
queue.AddQueueData(guid, GameTime::GetGameTime().count(), dungeons, rolesMap);
|
queue.AddQueueData(guid, GameTime::GetGameTime().count(), dungeons, rolesMap, soloQueue);
|
||||||
|
|
||||||
if (!isContinue)
|
if (!isContinue)
|
||||||
{
|
{
|
||||||
@@ -1671,7 +1676,11 @@ namespace lfg
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Xinef: Store amount of random players player grouped with
|
// Xinef: Store amount of random players player grouped with
|
||||||
if (group)
|
LfgDungeonSet const& selectedDungeons = GetSelectedDungeons(pguid);
|
||||||
|
bool const soloRandom = !selectedDungeons.empty() && IsSoloRandomDungeon(*selectedDungeons.begin());
|
||||||
|
if (soloRandom)
|
||||||
|
SetRandomPlayersCount(pguid, 0);
|
||||||
|
else if (group)
|
||||||
{
|
{
|
||||||
SetRandomPlayersCount(pguid, group->GetMembersCount() >= MAXGROUPSIZE ? 0 : MAXGROUPSIZE - group->GetMembersCount());
|
SetRandomPlayersCount(pguid, group->GetMembersCount() >= MAXGROUPSIZE ? 0 : MAXGROUPSIZE - group->GetMembersCount());
|
||||||
oldGroupGUID = group->GetGUID();
|
oldGroupGUID = group->GetGUID();
|
||||||
@@ -2345,8 +2354,12 @@ namespace lfg
|
|||||||
*/
|
*/
|
||||||
LfgReward const* LFGMgr::GetRandomDungeonReward(uint32 dungeon, uint8 level)
|
LfgReward const* LFGMgr::GetRandomDungeonReward(uint32 dungeon, uint8 level)
|
||||||
{
|
{
|
||||||
|
uint32 dungeonId = dungeon & 0x00FFFFFF;
|
||||||
|
if (IsSoloRandomDungeon(dungeonId))
|
||||||
|
dungeonId -= LFG_SOLO_RANDOM_DUNGEON_ID_OFFSET;
|
||||||
|
|
||||||
LfgReward const* rew = nullptr;
|
LfgReward const* rew = nullptr;
|
||||||
LfgRewardContainerBounds bounds = RewardMapStore.equal_range(dungeon & 0x00FFFFFF);
|
LfgRewardContainerBounds bounds = RewardMapStore.equal_range(dungeonId);
|
||||||
for (LfgRewardContainer::const_iterator itr = bounds.first; itr != bounds.second; ++itr)
|
for (LfgRewardContainer::const_iterator itr = bounds.first; itr != bounds.second; ++itr)
|
||||||
{
|
{
|
||||||
rew = itr->second;
|
rew = itr->second;
|
||||||
@@ -2358,6 +2371,20 @@ namespace lfg
|
|||||||
return rew;
|
return rew;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool LFGMgr::IsSoloRandomDungeon(uint32 dungeon)
|
||||||
|
{
|
||||||
|
uint32 dungeonId = dungeon & 0x00FFFFFF;
|
||||||
|
if (dungeonId < LFG_SOLO_RANDOM_DUNGEON_ID_OFFSET)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
LFGDungeonData const* solo = GetLFGDungeon(dungeonId);
|
||||||
|
LFGDungeonData const* regular = GetLFGDungeon(dungeonId - LFG_SOLO_RANDOM_DUNGEON_ID_OFFSET);
|
||||||
|
return solo && regular &&
|
||||||
|
solo->type == LFG_TYPE_RANDOM &&
|
||||||
|
regular->type == LFG_TYPE_RANDOM &&
|
||||||
|
solo->group == regular->group;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Given a Dungeon id returns the dungeon Type
|
Given a Dungeon id returns the dungeon Type
|
||||||
|
|
||||||
|
|||||||
@@ -525,6 +525,8 @@ namespace lfg
|
|||||||
bool IsDungeonDisabled(uint32 mapId, Difficulty difficulty) const;
|
bool IsDungeonDisabled(uint32 mapId, Difficulty difficulty) const;
|
||||||
/// Gets the random dungeon reward corresponding to given dungeon and player level
|
/// Gets the random dungeon reward corresponding to given dungeon and player level
|
||||||
LfgReward const* GetRandomDungeonReward(uint32 dungeon, uint8 level);
|
LfgReward const* GetRandomDungeonReward(uint32 dungeon, uint8 level);
|
||||||
|
/// Checks whether an entry is a custom one-player random dungeon category
|
||||||
|
bool IsSoloRandomDungeon(uint32 dungeon);
|
||||||
/// Returns all random and seasonal dungeons for given level and expansion
|
/// Returns all random and seasonal dungeons for given level and expansion
|
||||||
LfgDungeonSet GetRandomAndSeasonalDungeons(uint8 level, uint8 expansion);
|
LfgDungeonSet GetRandomAndSeasonalDungeons(uint8 level, uint8 expansion);
|
||||||
/// Teleport a player to/from selected dungeon
|
/// Teleport a player to/from selected dungeon
|
||||||
|
|||||||
@@ -104,10 +104,10 @@ namespace lfg
|
|||||||
restoredAfterProposal.remove(guid);
|
restoredAfterProposal.remove(guid);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LFGQueue::AddQueueData(ObjectGuid guid, time_t joinTime, LfgDungeonSet const& dungeons, LfgRolesMap const& rolesMap)
|
void LFGQueue::AddQueueData(ObjectGuid guid, time_t joinTime, LfgDungeonSet const& dungeons, LfgRolesMap const& rolesMap, bool solo)
|
||||||
{
|
{
|
||||||
LOG_DEBUG("lfg", "JOINED AddQueueData: {}", guid.ToString());
|
LOG_DEBUG("lfg", "JOINED AddQueueData: {}", guid.ToString());
|
||||||
QueueDataStore[guid] = LfgQueueData(joinTime, dungeons, rolesMap);
|
QueueDataStore[guid] = LfgQueueData(joinTime, dungeons, rolesMap, solo);
|
||||||
AddToQueue(guid);
|
AddToQueue(guid);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,6 +263,7 @@ namespace lfg
|
|||||||
// Check if more than one LFG group and number of players joining
|
// Check if more than one LFG group and number of players joining
|
||||||
uint8 numPlayers = 0;
|
uint8 numPlayers = 0;
|
||||||
uint8 numLfgGroups = 0;
|
uint8 numLfgGroups = 0;
|
||||||
|
bool soloQueue = false;
|
||||||
ObjectGuid guid;
|
ObjectGuid guid;
|
||||||
uint64 addToFoundMask = 0;
|
uint64 addToFoundMask = 0;
|
||||||
|
|
||||||
@@ -280,6 +281,7 @@ namespace lfg
|
|||||||
for (LfgRolesMap::const_iterator it2 = itQueue->second.roles.begin(); it2 != itQueue->second.roles.end(); ++it2)
|
for (LfgRolesMap::const_iterator it2 = itQueue->second.roles.begin(); it2 != itQueue->second.roles.end(); ++it2)
|
||||||
proposalGroups[it2->first] = itQueue->first.IsGroup() ? itQueue->first : ObjectGuid::Empty;
|
proposalGroups[it2->first] = itQueue->first.IsGroup() ? itQueue->first : ObjectGuid::Empty;
|
||||||
|
|
||||||
|
soloQueue = soloQueue || itQueue->second.solo;
|
||||||
numPlayers += itQueue->second.roles.size();
|
numPlayers += itQueue->second.roles.size();
|
||||||
|
|
||||||
if (sLFGMgr->IsLfgGroup(guid))
|
if (sLFGMgr->IsLfgGroup(guid))
|
||||||
@@ -293,8 +295,13 @@ namespace lfg
|
|||||||
if (numLfgGroups > 1)
|
if (numLfgGroups > 1)
|
||||||
return LFG_INCOMPATIBLES_MULTIPLE_LFG_GROUPS;
|
return LFG_INCOMPATIBLES_MULTIPLE_LFG_GROUPS;
|
||||||
|
|
||||||
|
// A solo RDF entry is a complete one-player match and must not absorb
|
||||||
|
// entries from the regular five-player queue.
|
||||||
|
if (soloQueue && (check.size() != 1 || numPlayers != 1))
|
||||||
|
return LFG_INCOMPATIBLES_WRONG_GROUP_SIZE;
|
||||||
|
|
||||||
// Group with less that MAXGROUPSIZE members always compatible
|
// Group with less that MAXGROUPSIZE members always compatible
|
||||||
if (!sLFGMgr->IsTesting() && check.size() == 1 && numPlayers < MAXGROUPSIZE)
|
if (!sLFGMgr->IsTesting() && !soloQueue && check.size() == 1 && numPlayers < MAXGROUPSIZE)
|
||||||
{
|
{
|
||||||
LfgQueueDataContainer::iterator itQueue = QueueDataStore.find(check.front());
|
LfgQueueDataContainer::iterator itQueue = QueueDataStore.find(check.front());
|
||||||
LfgRolesMap roles = itQueue->second.roles;
|
LfgRolesMap roles = itQueue->second.roles;
|
||||||
@@ -388,10 +395,27 @@ namespace lfg
|
|||||||
proposalDungeons = queue.dungeons;
|
proposalDungeons = queue.dungeons;
|
||||||
proposalRoles = queue.roles;
|
proposalRoles = queue.roles;
|
||||||
LFGMgr::CheckGroupRoles(proposalRoles); // assing new roles
|
LFGMgr::CheckGroupRoles(proposalRoles); // assing new roles
|
||||||
|
|
||||||
|
// A one-player proposal still has to contain one of the three
|
||||||
|
// canonical combat roles. The stock queue never reaches a
|
||||||
|
// proposal with PLAYER_ROLE_NONE, while the solo queue can.
|
||||||
|
// Sending NONE (or a multi-role mask) makes the 3.3.5a client
|
||||||
|
// return "UNKNOWN" and LFDFrame.lua raises an error.
|
||||||
|
if (soloQueue)
|
||||||
|
{
|
||||||
|
uint8& role = proposalRoles.begin()->second;
|
||||||
|
role &= PLAYER_ROLE_TANK | PLAYER_ROLE_HEALER | PLAYER_ROLE_DAMAGE;
|
||||||
|
if (role & PLAYER_ROLE_TANK)
|
||||||
|
role = PLAYER_ROLE_TANK;
|
||||||
|
else if (role & PLAYER_ROLE_HEALER)
|
||||||
|
role = PLAYER_ROLE_HEALER;
|
||||||
|
else
|
||||||
|
role = PLAYER_ROLE_DAMAGE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enough players?
|
// Enough players?
|
||||||
if (!sLFGMgr->IsTesting() && numPlayers != MAXGROUPSIZE)
|
if (!sLFGMgr->IsTesting() && !soloQueue && numPlayers != MAXGROUPSIZE)
|
||||||
{
|
{
|
||||||
strGuids.addRoles(proposalRoles);
|
strGuids.addRoles(proposalRoles);
|
||||||
for (uint8 i = 0; i < 5 && check.guids[i]; ++i)
|
for (uint8 i = 0; i < 5 && check.guids[i]; ++i)
|
||||||
|
|||||||
@@ -40,9 +40,9 @@ namespace lfg
|
|||||||
{
|
{
|
||||||
LfgQueueData();
|
LfgQueueData();
|
||||||
|
|
||||||
LfgQueueData(time_t _joinTime, LfgDungeonSet _dungeons, LfgRolesMap _roles):
|
LfgQueueData(time_t _joinTime, LfgDungeonSet _dungeons, LfgRolesMap _roles, bool _solo):
|
||||||
joinTime(_joinTime), lastRefreshTime(_joinTime), tanks(LFG_TANKS_NEEDED), healers(LFG_HEALERS_NEEDED),
|
joinTime(_joinTime), lastRefreshTime(_joinTime), tanks(LFG_TANKS_NEEDED), healers(LFG_HEALERS_NEEDED),
|
||||||
dps(LFG_DPS_NEEDED), dungeons(std::move(_dungeons)), roles(std::move(_roles))
|
dps(LFG_DPS_NEEDED), dungeons(std::move(_dungeons)), roles(std::move(_roles)), solo(_solo)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
time_t joinTime; // Player queue join time (to calculate wait times)
|
time_t joinTime; // Player queue join time (to calculate wait times)
|
||||||
@@ -52,6 +52,7 @@ namespace lfg
|
|||||||
uint8 dps{LFG_DPS_NEEDED}; // Dps needed
|
uint8 dps{LFG_DPS_NEEDED}; // Dps needed
|
||||||
LfgDungeonSet dungeons; // Selected Player/Group Dungeon/s
|
LfgDungeonSet dungeons; // Selected Player/Group Dungeon/s
|
||||||
LfgRolesMap roles; // Selected Player Role/s
|
LfgRolesMap roles; // Selected Player Role/s
|
||||||
|
bool solo{false}; // Match as a one-player RDF group
|
||||||
Lfg5Guids bestCompatible; // Best compatible combination of people queued
|
Lfg5Guids bestCompatible; // Best compatible combination of people queued
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -75,7 +76,7 @@ namespace lfg
|
|||||||
// Add/Remove from queue
|
// Add/Remove from queue
|
||||||
void AddToQueue(ObjectGuid guid, bool failedProposal = false);
|
void AddToQueue(ObjectGuid guid, bool failedProposal = false);
|
||||||
void RemoveFromQueue(ObjectGuid guid, bool partial = false); // xinef: partial remove, dont delete data from list!
|
void RemoveFromQueue(ObjectGuid guid, bool partial = false); // xinef: partial remove, dont delete data from list!
|
||||||
void AddQueueData(ObjectGuid guid, time_t joinTime, LfgDungeonSet const& dungeons, LfgRolesMap const& rolesMap);
|
void AddQueueData(ObjectGuid guid, time_t joinTime, LfgDungeonSet const& dungeons, LfgRolesMap const& rolesMap, bool solo = false);
|
||||||
void RemoveQueueData(ObjectGuid guid);
|
void RemoveQueueData(ObjectGuid guid);
|
||||||
|
|
||||||
// Update Timers (when proposal success)
|
// Update Timers (when proposal success)
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
"""Add native solo RDF categories to a WotLK 3.3.5a LFGDungeons.dbc."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import struct
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
MAGIC = b"WDBC"
|
||||||
|
FIELD_COUNT = 49
|
||||||
|
RECORD_SIZE = FIELD_COUNT * 4
|
||||||
|
SOLO_ID_OFFSET = 1000
|
||||||
|
|
||||||
|
SOLO_NAMES = {
|
||||||
|
258: "Случайное одиночное подземелье: Классика",
|
||||||
|
259: "Случайное одиночное подземелье: Burning Crusade",
|
||||||
|
260: "Случайное одиночное героическое: Burning Crusade",
|
||||||
|
261: "Случайное одиночное подземелье Lich King",
|
||||||
|
262: "Случайное одиночное героическое подземелье Lich King",
|
||||||
|
}
|
||||||
|
|
||||||
|
SOLO_DESCRIPTION = (
|
||||||
|
"Случайное подземелье для одного игрока. "
|
||||||
|
"Существа и боссы масштабируются системой AutoBalance."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args() -> argparse.Namespace:
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Duplicate the five stock random dungeon rows as solo RDF categories."
|
||||||
|
)
|
||||||
|
parser.add_argument("source", type=Path, help="Source LFGDungeons.dbc")
|
||||||
|
parser.add_argument("output", type=Path, help="Patched LFGDungeons.dbc")
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def add_string(string_block: bytearray, value: str) -> int:
|
||||||
|
encoded = value.encode("utf-8") + b"\0"
|
||||||
|
offset = len(string_block)
|
||||||
|
string_block.extend(encoded)
|
||||||
|
return offset
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
args = parse_args()
|
||||||
|
raw = args.source.read_bytes()
|
||||||
|
if len(raw) < 20 or raw[:4] != MAGIC:
|
||||||
|
raise SystemExit(f"{args.source}: not a WDBC file")
|
||||||
|
|
||||||
|
record_count, field_count, record_size, string_size = struct.unpack_from("<4I", raw, 4)
|
||||||
|
if field_count != FIELD_COUNT or record_size != RECORD_SIZE:
|
||||||
|
raise SystemExit(
|
||||||
|
f"{args.source}: unexpected layout "
|
||||||
|
f"(fields={field_count}, record_size={record_size})"
|
||||||
|
)
|
||||||
|
|
||||||
|
records_start = 20
|
||||||
|
strings_start = records_start + record_count * record_size
|
||||||
|
strings_end = strings_start + string_size
|
||||||
|
if strings_end != len(raw):
|
||||||
|
raise SystemExit(f"{args.source}: inconsistent WDBC size")
|
||||||
|
|
||||||
|
records: list[list[int]] = []
|
||||||
|
source_records: dict[int, list[int]] = {}
|
||||||
|
solo_ids = {base_id + SOLO_ID_OFFSET for base_id in SOLO_NAMES}
|
||||||
|
|
||||||
|
for index in range(record_count):
|
||||||
|
offset = records_start + index * record_size
|
||||||
|
record = list(struct.unpack_from(f"<{FIELD_COUNT}I", raw, offset))
|
||||||
|
if record[0] in SOLO_NAMES:
|
||||||
|
source_records[record[0]] = record
|
||||||
|
if record[0] not in solo_ids:
|
||||||
|
records.append(record)
|
||||||
|
|
||||||
|
missing = sorted(set(SOLO_NAMES) - set(source_records))
|
||||||
|
if missing:
|
||||||
|
raise SystemExit(f"{args.source}: missing stock random dungeon rows: {missing}")
|
||||||
|
|
||||||
|
string_block = bytearray(raw[strings_start:strings_end])
|
||||||
|
for base_id, name in SOLO_NAMES.items():
|
||||||
|
record = source_records[base_id].copy()
|
||||||
|
record[0] = base_id + SOLO_ID_OFFSET
|
||||||
|
|
||||||
|
name_offset = add_string(string_block, name)
|
||||||
|
description_offset = add_string(string_block, SOLO_DESCRIPTION)
|
||||||
|
|
||||||
|
# Fill every locale slot. This keeps the custom row readable regardless
|
||||||
|
# of which locale column the client selects.
|
||||||
|
record[1:17] = [name_offset] * 16
|
||||||
|
record[32:48] = [description_offset] * 16
|
||||||
|
records.append(record)
|
||||||
|
|
||||||
|
records.sort(key=lambda record: record[0])
|
||||||
|
output = bytearray()
|
||||||
|
output.extend(MAGIC)
|
||||||
|
output.extend(struct.pack("<4I", len(records), FIELD_COUNT, RECORD_SIZE, len(string_block)))
|
||||||
|
for record in records:
|
||||||
|
output.extend(struct.pack(f"<{FIELD_COUNT}I", *record))
|
||||||
|
output.extend(string_block)
|
||||||
|
|
||||||
|
args.output.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
temporary = args.output.with_suffix(args.output.suffix + ".tmp")
|
||||||
|
temporary.write_bytes(output)
|
||||||
|
temporary.replace(args.output)
|
||||||
|
print(f"Wrote {args.output} with solo RDF entries 1258-1262")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user