DNF90 服务端与启动器完整源码(含 GM 工具功能增强)
服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
This commit is contained in:
commit
645b64fe21
4388 files changed
+1405457
No files matched your search
@@ -0,0 +1,407 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
platformpvf "longheng.io/server/internal/platform/pvf"
|
||||
)
|
||||
|
||||
const scriptTokenSize = 5
|
||||
|
||||
var removedItemTimeSections = map[string]struct{}{
|
||||
"expiration date": {},
|
||||
"usable datetime": {},
|
||||
"usable period": {},
|
||||
"usable period after unsealing": {},
|
||||
}
|
||||
|
||||
const unlimitedExpirationSection = "expiration info"
|
||||
|
||||
type patchCounts struct {
|
||||
Removed map[string]int
|
||||
Transformed map[string]int
|
||||
}
|
||||
|
||||
type report struct {
|
||||
InputPath string `json:"input_path"`
|
||||
OutputPath string `json:"output_path,omitempty"`
|
||||
InputSHA256 string `json:"input_sha256"`
|
||||
OutputSHA256 string `json:"output_sha256,omitempty"`
|
||||
ArchiveFiles int `json:"archive_files"`
|
||||
ItemDocumentsScanned int `json:"item_documents_scanned"`
|
||||
ItemDocumentsChanged int `json:"item_documents_changed"`
|
||||
SectionsRemoved int `json:"sections_removed"`
|
||||
SectionsTransformed int `json:"sections_transformed"`
|
||||
BytesRemoved int `json:"bytes_removed"`
|
||||
SectionsByName map[string]int `json:"sections_by_name"`
|
||||
TransformsByName map[string]int `json:"transforms_by_name"`
|
||||
TimeLikeSections map[string]int `json:"time_like_sections"`
|
||||
FilesVerified int `json:"files_verified,omitempty"`
|
||||
UnmodifiedFilesStable int `json:"unmodified_files_stable,omitempty"`
|
||||
DryRun bool `json:"dry_run"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
input := flag.String("pvf", `D:/DNF/runtime/data/dnf/Script.pvf`, "immutable source Script.pvf")
|
||||
output := flag.String("out", "", "new candidate PVF path; must not exist")
|
||||
dryRun := flag.Bool("dry-run", false, "audit item time-limit sections without writing")
|
||||
flag.Parse()
|
||||
|
||||
result, err := run(*input, *output, *dryRun)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "pvfremoveitemexpiry:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
encoded, err := json.MarshalIndent(result, "", " ")
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "pvfremoveitemexpiry: encode report:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println(string(encoded))
|
||||
}
|
||||
|
||||
func run(inputPath, outputPath string, dryRun bool) (report, error) {
|
||||
var result report
|
||||
inputPath, err := filepath.Abs(filepath.Clean(strings.TrimSpace(inputPath)))
|
||||
if err != nil {
|
||||
return result, fmt.Errorf("resolve input path: %w", err)
|
||||
}
|
||||
if inputPath == "" {
|
||||
return result, errors.New("input PVF path is required")
|
||||
}
|
||||
if !dryRun {
|
||||
outputPath, err = filepath.Abs(filepath.Clean(strings.TrimSpace(outputPath)))
|
||||
if err != nil {
|
||||
return result, fmt.Errorf("resolve output path: %w", err)
|
||||
}
|
||||
if outputPath == "" || strings.EqualFold(inputPath, outputPath) {
|
||||
return result, errors.New("output PVF must be a distinct new path")
|
||||
}
|
||||
if _, statErr := os.Stat(outputPath); statErr == nil {
|
||||
return result, fmt.Errorf("output path already exists: %s", outputPath)
|
||||
} else if !errors.Is(statErr, os.ErrNotExist) {
|
||||
return result, fmt.Errorf("inspect output path: %w", statErr)
|
||||
}
|
||||
if parentInfo, statErr := os.Stat(filepath.Dir(outputPath)); statErr != nil || !parentInfo.IsDir() {
|
||||
return result, fmt.Errorf("output directory is unavailable: %s", filepath.Dir(outputPath))
|
||||
}
|
||||
}
|
||||
|
||||
inputHash, err := sha256File(inputPath)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
archive, err := platformpvf.Open(inputPath)
|
||||
if err != nil {
|
||||
return result, fmt.Errorf("open source PVF: %w", err)
|
||||
}
|
||||
files := archive.Files()
|
||||
result = report{
|
||||
InputPath: inputPath,
|
||||
OutputPath: outputPath,
|
||||
InputSHA256: inputHash,
|
||||
ArchiveFiles: len(files),
|
||||
SectionsByName: make(map[string]int, len(removedItemTimeSections)),
|
||||
TransformsByName: make(map[string]int, 1),
|
||||
TimeLikeSections: make(map[string]int),
|
||||
DryRun: dryRun,
|
||||
}
|
||||
unlimitedMagic, found := archive.FindStringMagicOffsets([]string{"[unlimit]"})["[unlimit]"]
|
||||
if !found {
|
||||
return result, errors.New("source PVF string pool has no [unlimit] value")
|
||||
}
|
||||
|
||||
replacements := make([]platformpvf.RawReplacement, 0, 4096)
|
||||
expectedByPath := make(map[string][]byte)
|
||||
for _, file := range files {
|
||||
if !isItemDefinition(file) {
|
||||
continue
|
||||
}
|
||||
result.ItemDocumentsScanned++
|
||||
raw, readErr := archive.FileRaw(file.Index)
|
||||
if readErr != nil {
|
||||
return result, fmt.Errorf("read item document %s: %w", file.ArchivePath, readErr)
|
||||
}
|
||||
for section, count := range itemSectionCounts(raw, archive.ResolveString) {
|
||||
if isTimeLikeItemSection(section) {
|
||||
result.TimeLikeSections[section] += count
|
||||
}
|
||||
}
|
||||
patched, counts, removeErr := rewriteItemTimeLimitSections(raw, archive.ResolveString, unlimitedMagic)
|
||||
if removeErr != nil {
|
||||
return result, fmt.Errorf("remove item time limits from %s: %w", file.ArchivePath, removeErr)
|
||||
}
|
||||
removed := 0
|
||||
for section, count := range counts.Removed {
|
||||
result.SectionsByName[section] += count
|
||||
removed += count
|
||||
}
|
||||
transformed := 0
|
||||
for section, count := range counts.Transformed {
|
||||
result.TransformsByName[section] += count
|
||||
transformed += count
|
||||
}
|
||||
if removed+transformed == 0 {
|
||||
continue
|
||||
}
|
||||
result.ItemDocumentsChanged++
|
||||
result.SectionsRemoved += removed
|
||||
result.SectionsTransformed += transformed
|
||||
result.BytesRemoved += len(raw) - len(patched)
|
||||
pathKey := strings.ToLower(filepath.ToSlash(file.ArchivePath))
|
||||
expectedByPath[pathKey] = patched
|
||||
replacements = append(replacements, platformpvf.RawReplacement{Path: file.ArchivePath, Data: patched})
|
||||
}
|
||||
if dryRun {
|
||||
return result, nil
|
||||
}
|
||||
if len(replacements) == 0 {
|
||||
return result, errors.New("source PVF contains no item expiration sections")
|
||||
}
|
||||
|
||||
currentHash, err := sha256File(inputPath)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
if !strings.EqualFold(currentHash, inputHash) {
|
||||
return result, errors.New("source PVF changed after audit; refusing stale repack")
|
||||
}
|
||||
repacked, err := archive.RepackRaw(replacements)
|
||||
if err != nil {
|
||||
return result, fmt.Errorf("repack item expiration removals: %w", err)
|
||||
}
|
||||
verified, err := platformpvf.OpenBytes(repacked)
|
||||
if err != nil {
|
||||
return result, fmt.Errorf("reopen candidate PVF: %w", err)
|
||||
}
|
||||
candidateUnlimitedMagic, found := verified.FindStringMagicOffsets([]string{"[unlimit]"})["[unlimit]"]
|
||||
if !found {
|
||||
return result, errors.New("candidate PVF string pool has no [unlimit] value")
|
||||
}
|
||||
verifiedFiles := verified.Files()
|
||||
if len(verifiedFiles) != len(files) {
|
||||
return result, fmt.Errorf("candidate file count=%d want=%d", len(verifiedFiles), len(files))
|
||||
}
|
||||
for index, originalFile := range files {
|
||||
candidateFile := verifiedFiles[index]
|
||||
pathKey := strings.ToLower(filepath.ToSlash(originalFile.ArchivePath))
|
||||
expected, changed := expectedByPath[pathKey]
|
||||
expectedMetadata := originalFile
|
||||
if changed {
|
||||
expectedMetadata.Size = len(expected)
|
||||
}
|
||||
if expectedMetadata != candidateFile {
|
||||
return result, fmt.Errorf("candidate metadata changed outside planned size at file index %d: want=%+v after=%+v", index, expectedMetadata, candidateFile)
|
||||
}
|
||||
candidateRaw, readErr := verified.FileRaw(candidateFile.Index)
|
||||
if readErr != nil {
|
||||
return result, fmt.Errorf("read candidate file %s: %w", candidateFile.ArchivePath, readErr)
|
||||
}
|
||||
if changed {
|
||||
if !bytes.Equal(candidateRaw, expected) {
|
||||
return result, fmt.Errorf("candidate changed item %s does not match planned token deletion", originalFile.ArchivePath)
|
||||
}
|
||||
if _, counts, removeErr := rewriteItemTimeLimitSections(candidateRaw, verified.ResolveString, candidateUnlimitedMagic); removeErr != nil {
|
||||
return result, fmt.Errorf("verify candidate item %s: %w", originalFile.ArchivePath, removeErr)
|
||||
} else if countPatchSections(counts) != 0 {
|
||||
return result, fmt.Errorf("candidate item %s still contains time-limit sections", originalFile.ArchivePath)
|
||||
}
|
||||
} else {
|
||||
originalRaw, readErr := archive.FileRaw(originalFile.Index)
|
||||
if readErr != nil {
|
||||
return result, fmt.Errorf("reread source file %s: %w", originalFile.ArchivePath, readErr)
|
||||
}
|
||||
if !bytes.Equal(candidateRaw, originalRaw) {
|
||||
return result, fmt.Errorf("unmodified file changed: %s", originalFile.ArchivePath)
|
||||
}
|
||||
result.UnmodifiedFilesStable++
|
||||
}
|
||||
result.FilesVerified++
|
||||
}
|
||||
|
||||
sum := sha256.Sum256(repacked)
|
||||
result.OutputSHA256 = strings.ToUpper(hex.EncodeToString(sum[:]))
|
||||
if err := writeNewFile(outputPath, repacked); err != nil {
|
||||
return result, err
|
||||
}
|
||||
diskHash, err := sha256File(outputPath)
|
||||
if err != nil {
|
||||
_ = os.Remove(outputPath)
|
||||
return result, err
|
||||
}
|
||||
if !strings.EqualFold(diskHash, result.OutputSHA256) {
|
||||
_ = os.Remove(outputPath)
|
||||
return result, errors.New("candidate PVF hash changed while writing")
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func itemSectionCounts(raw []byte, resolve func(int) string) map[string]int {
|
||||
counts := make(map[string]int)
|
||||
if len(raw)%scriptTokenSize != 0 {
|
||||
return counts
|
||||
}
|
||||
for offset := 0; offset < len(raw); offset += scriptTokenSize {
|
||||
if raw[offset] != 3 {
|
||||
continue
|
||||
}
|
||||
value := int(int32(binary.LittleEndian.Uint32(raw[offset+1 : offset+scriptTokenSize])))
|
||||
if section := normalizeSectionName(resolve(value)); section != "" {
|
||||
counts[section]++
|
||||
}
|
||||
}
|
||||
return counts
|
||||
}
|
||||
|
||||
func isTimeLikeItemSection(section string) bool {
|
||||
for _, fragment := range []string{"expir", "usable period", "use period", "duration", "deadline", "date"} {
|
||||
if strings.Contains(section, fragment) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isItemDefinition(file platformpvf.File) bool {
|
||||
if file.DataType != 1 {
|
||||
return false
|
||||
}
|
||||
archivePath := strings.ToLower(filepath.ToSlash(strings.TrimSpace(file.ArchivePath)))
|
||||
if !strings.HasPrefix(archivePath, "stackable/") && !strings.HasPrefix(archivePath, "equipment/") {
|
||||
return false
|
||||
}
|
||||
extension := strings.ToLower(filepath.Ext(archivePath))
|
||||
return extension == ".stk" || extension == ".equ"
|
||||
}
|
||||
|
||||
func removeItemTimeLimitSections(raw []byte, resolve func(int) string) ([]byte, map[string]int, error) {
|
||||
patched, counts, err := rewriteItemTimeLimitSections(raw, resolve, -1)
|
||||
return patched, counts.Removed, err
|
||||
}
|
||||
|
||||
func rewriteItemTimeLimitSections(raw []byte, resolve func(int) string, unlimitedMagic int) ([]byte, patchCounts, error) {
|
||||
if len(raw)%scriptTokenSize != 0 {
|
||||
return nil, patchCounts{}, fmt.Errorf("type-1 token bytes=%d are not divisible by %d", len(raw), scriptTokenSize)
|
||||
}
|
||||
counts := patchCounts{
|
||||
Removed: make(map[string]int, len(removedItemTimeSections)),
|
||||
Transformed: make(map[string]int, 1),
|
||||
}
|
||||
patched := make([]byte, 0, len(raw))
|
||||
for token := 0; token < len(raw)/scriptTokenSize; {
|
||||
offset := token * scriptTokenSize
|
||||
if raw[offset] != 3 {
|
||||
patched = append(patched, raw[offset:offset+scriptTokenSize]...)
|
||||
token++
|
||||
continue
|
||||
}
|
||||
value := int(int32(binary.LittleEndian.Uint32(raw[offset+1 : offset+scriptTokenSize])))
|
||||
section := normalizeSectionName(resolve(value))
|
||||
if _, remove := removedItemTimeSections[section]; remove {
|
||||
counts.Removed[section]++
|
||||
token++
|
||||
for token < len(raw)/scriptTokenSize && raw[token*scriptTokenSize] != 3 {
|
||||
token++
|
||||
}
|
||||
continue
|
||||
}
|
||||
if section == unlimitedExpirationSection && unlimitedMagic >= 0 {
|
||||
valueStart := token + 1
|
||||
valueEnd := valueStart
|
||||
for valueEnd < len(raw)/scriptTokenSize && raw[valueEnd*scriptTokenSize] != 3 {
|
||||
valueEnd++
|
||||
}
|
||||
if valueEnd == valueStart+1 && raw[valueStart*scriptTokenSize] == 6 {
|
||||
value := int(int32(binary.LittleEndian.Uint32(raw[valueStart*scriptTokenSize+1 : valueStart*scriptTokenSize+scriptTokenSize])))
|
||||
if strings.EqualFold(strings.TrimSpace(resolve(value)), "[unlimit]") {
|
||||
patched = append(patched, raw[offset:valueEnd*scriptTokenSize]...)
|
||||
token = valueEnd
|
||||
continue
|
||||
}
|
||||
}
|
||||
patched = append(patched, raw[offset:offset+scriptTokenSize]...)
|
||||
unlimited := make([]byte, scriptTokenSize)
|
||||
unlimited[0] = 6
|
||||
binary.LittleEndian.PutUint32(unlimited[1:], uint32(int32(unlimitedMagic)))
|
||||
patched = append(patched, unlimited...)
|
||||
counts.Transformed[section]++
|
||||
token = valueEnd
|
||||
continue
|
||||
}
|
||||
patched = append(patched, raw[offset:offset+scriptTokenSize]...)
|
||||
token++
|
||||
}
|
||||
if countPatchSections(counts) == 0 {
|
||||
return append([]byte(nil), raw...), counts, nil
|
||||
}
|
||||
return patched, counts, nil
|
||||
}
|
||||
|
||||
func normalizeSectionName(value string) string {
|
||||
value = strings.ToLower(strings.TrimSpace(value))
|
||||
if len(value) >= 2 && value[0] == '[' && value[len(value)-1] == ']' {
|
||||
value = strings.TrimSpace(value[1 : len(value)-1])
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func countSections(counts map[string]int) int {
|
||||
total := 0
|
||||
for _, count := range counts {
|
||||
total += count
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
func countPatchSections(counts patchCounts) int {
|
||||
return countSections(counts.Removed) + countSections(counts.Transformed)
|
||||
}
|
||||
|
||||
func sha256File(path string) (string, error) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("open %s for hashing: %w", path, err)
|
||||
}
|
||||
defer file.Close()
|
||||
hash := sha256.New()
|
||||
if _, err := io.Copy(hash, file); err != nil {
|
||||
return "", fmt.Errorf("hash %s: %w", path, err)
|
||||
}
|
||||
return strings.ToUpper(hex.EncodeToString(hash.Sum(nil))), nil
|
||||
}
|
||||
|
||||
func writeNewFile(path string, data []byte) error {
|
||||
file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create candidate PVF: %w", err)
|
||||
}
|
||||
ok := false
|
||||
defer func() {
|
||||
_ = file.Close()
|
||||
if !ok {
|
||||
_ = os.Remove(path)
|
||||
}
|
||||
}()
|
||||
if _, err := file.Write(data); err != nil {
|
||||
return fmt.Errorf("write candidate PVF: %w", err)
|
||||
}
|
||||
if err := file.Sync(); err != nil {
|
||||
return fmt.Errorf("flush candidate PVF: %w", err)
|
||||
}
|
||||
if err := file.Close(); err != nil {
|
||||
return fmt.Errorf("close candidate PVF: %w", err)
|
||||
}
|
||||
ok = true
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRemoveItemTimeLimitSectionsRemovesBothFieldsAndPreservesNeighbors(t *testing.T) {
|
||||
resolved := map[int]string{
|
||||
1: "[name]",
|
||||
2: "[expiration date]",
|
||||
3: "[usable period]",
|
||||
4: "[action type]",
|
||||
5: "[level up ticket]",
|
||||
}
|
||||
raw := encodeTokens(
|
||||
rawToken{typ: 3, value: 1},
|
||||
rawToken{typ: 6, value: 5},
|
||||
rawToken{typ: 3, value: 2},
|
||||
rawToken{typ: 6, value: 99},
|
||||
rawToken{typ: 3, value: 3},
|
||||
rawToken{typ: 0, value: 7},
|
||||
rawToken{typ: 3, value: 4},
|
||||
rawToken{typ: 6, value: 5},
|
||||
)
|
||||
patched, counts, err := removeItemTimeLimitSections(raw, func(value int) string { return resolved[value] })
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := encodeTokens(
|
||||
rawToken{typ: 3, value: 1},
|
||||
rawToken{typ: 6, value: 5},
|
||||
rawToken{typ: 3, value: 4},
|
||||
rawToken{typ: 6, value: 5},
|
||||
)
|
||||
if string(patched) != string(want) {
|
||||
t.Fatalf("patched=%x want=%x", patched, want)
|
||||
}
|
||||
if counts["expiration date"] != 1 || counts["usable period"] != 1 {
|
||||
t.Fatalf("counts=%v", counts)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveItemTimeLimitSectionsRemovesRepeatedAndEmptyFields(t *testing.T) {
|
||||
resolved := map[int]string{1: "[expiration date]", 2: "[name]"}
|
||||
raw := encodeTokens(
|
||||
rawToken{typ: 3, value: 1},
|
||||
rawToken{typ: 3, value: 1},
|
||||
rawToken{typ: 6, value: 10},
|
||||
rawToken{typ: 3, value: 2},
|
||||
rawToken{typ: 6, value: 11},
|
||||
)
|
||||
patched, counts, err := removeItemTimeLimitSections(raw, func(value int) string { return resolved[value] })
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := encodeTokens(rawToken{typ: 3, value: 2}, rawToken{typ: 6, value: 11})
|
||||
if string(patched) != string(want) || counts["expiration date"] != 2 {
|
||||
t.Fatalf("patched=%x counts=%v want=%x", patched, counts, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveItemTimeLimitSectionsRejectsPartialToken(t *testing.T) {
|
||||
if _, _, err := removeItemTimeLimitSections([]byte{3, 1, 2}, func(int) string { return "" }); err == nil {
|
||||
t.Fatal("partial token stream was accepted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRewriteItemTimeLimitSectionsMakesEffectExpirationUnlimited(t *testing.T) {
|
||||
resolved := map[int]string{
|
||||
1: "[damage font info]",
|
||||
2: "[expiration info]",
|
||||
3: "[date]",
|
||||
4: "2048-12-31 06:00:00",
|
||||
5: "[/damage font info]",
|
||||
6: "[unlimit]",
|
||||
}
|
||||
raw := encodeTokens(
|
||||
rawToken{typ: 3, value: 1},
|
||||
rawToken{typ: 3, value: 2},
|
||||
rawToken{typ: 6, value: 3},
|
||||
rawToken{typ: 6, value: 4},
|
||||
rawToken{typ: 3, value: 5},
|
||||
)
|
||||
patched, counts, err := rewriteItemTimeLimitSections(raw, func(value int) string { return resolved[value] }, 6)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := encodeTokens(
|
||||
rawToken{typ: 3, value: 1},
|
||||
rawToken{typ: 3, value: 2},
|
||||
rawToken{typ: 6, value: 6},
|
||||
rawToken{typ: 3, value: 5},
|
||||
)
|
||||
if string(patched) != string(want) || counts.Transformed["expiration info"] != 1 {
|
||||
t.Fatalf("patched=%x counts=%+v want=%x", patched, counts, want)
|
||||
}
|
||||
rewritten, secondCounts, err := rewriteItemTimeLimitSections(patched, func(value int) string { return resolved[value] }, 6)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(rewritten) != string(want) || countPatchSections(secondCounts) != 0 {
|
||||
t.Fatalf("second rewrite was not idempotent: patched=%x counts=%+v", rewritten, secondCounts)
|
||||
}
|
||||
}
|
||||
|
||||
type rawToken struct {
|
||||
typ byte
|
||||
value int32
|
||||
}
|
||||
|
||||
func encodeTokens(tokens ...rawToken) []byte {
|
||||
out := make([]byte, len(tokens)*scriptTokenSize)
|
||||
for index, token := range tokens {
|
||||
offset := index * scriptTokenSize
|
||||
out[offset] = token.typ
|
||||
binary.LittleEndian.PutUint32(out[offset+1:offset+scriptTokenSize], uint32(token.value))
|
||||
}
|
||||
return out
|
||||
}
|
||||
Reference in new issue
Block a user