NodeBB

    • Login
    • Categories
    • Recent
    • Tags
    • Popular
    • Users
    • Groups
    1. Home
    2. changx
    C
    • Profile
    • Following 0
    • Followers 0
    • Topics 7
    • Posts 12
    • Best 0
    • Controversial 0
    • Groups 1

    changx

    @changx

    administrators

    0
    Reputation
    2
    Profile views
    12
    Posts
    0
    Followers
    0
    Following
    Joined Last Online

    changx Unfollow Follow
    administrators

    Latest posts made by changx

    • Đổi domain trên vercel

      vào dự án cần đổi domain trên vercel, chuyển sang tab Settings

      29b70f31-2b89-4aaf-8026-513a20c9eaa8-image.png

      bấm edit vào domain mình muốn đổi
      nhập lại domain mới có thêm random, bấm Save
      90033fbb-11d8-4c9b-ad61-1e85ae2cc5fe-image.png

      chọn Remove old domain, bấm Save
      a23caa8f-ca3c-4cfb-be80-72ad04a8eb66-image.png

      có thể dùng trang này để random id cũng được https://www.random.org/strings/?num=10&len=10&digits=on&loweralpha=on&unique=on&format=html&rnd=new

      posted in Developer Discussion
      C
      changx
    • Chống các phiên bản mod game trong NATV

      Game NATV xuất hiện bản mod từ phía ios như sau: https://iosgods.com/topic/195120-night-valley-tower-defense-v1022-2-jailed-cheats/#google_vignette
      -> đúng với mô tả của mod, và trên web admin tool xuất hiện những người chơi phía ios sử dụng version này có thể mua hàng và tăng thêm gem, blueprint... (phiên bản 1.0.20, 1.0.22)
      -> phía android sẽ xuất hiện bản mod sớm.

      • mod game là việc download bản origin về, chỉnh sửa lại file đã build như asset hình ảnh, âm thanh để cho đẹp mắt hơn, hoặc can thiệp bằng cách inject thư viện thứ 3 hoặc đoạn code rời từ bên ngoài vào, sử dụng reflection để can thiệp và thay đổi code chính, hoặc chỉnh sửa trực tiếp code gốc của tác giả (developer), kể cả là đã build IL2CPP để giúp game có những tính năng bất lợi cho tác giả như remove quảng cáo, thay đổi logic game, thay vì trừ gem thành cộng gem, mod speed

      • mình có thể làm là xáo trộn mã, vd như https://assetstore.unity.com/packages/tools/utilities/obfuscator-source-plug-and-play-code-protection-v2026-210262 giúp cho bản build ra tên hàm và biến trở thành các đoạn như a, b, c chứ không còn là origin name như TakeDamage, PurchaseGem, IsAlive như trong source code ban đầu, (ngoài ra biến các string thành các dạng mã hoá để chống search code) khiến mod khó hiểu và tốn nhiều công sức để có thể hiểu được hàm này có ý nghĩa gì, từ đó khiến mod sẽ mất nhiều thời gian, thậm chí nản và từ bỏ việc cố gắng mod bản game này.
        -> lợi ích là chống mod hiểu được code thì chính nhà phát triển cũng gặp rủi ro trong runtime như sau: ví dụ dùng Newtonsoft.Json để parse 1 json như {type: "monster", hp: 500, damage: 300,...} thành 1 đối tượng class Monster { string type; float hp; float damage; }, thế nhưng các biến như type, hp, damage đã bị obfuscator đổi thành a, b, c thay thế, vậy thì lúc này parse json sẽ ra a, b, c null, có thể giải quyết điều này bằng cách thêm các attribute JsonProperty("type"), hoặc cho vào danh sách ignore list để obfuscator bỏ qua không thực hiện làm rối mã cho Monster.
        -> đây chỉ là 1 trường hợp nhỏ, trong rất nhiều trường hợp khác mà chúng ta chưa nghĩ ra, chỉ có test thấy trường hợp thì mới biết.

      vậy có cách nào an toàn hơn obfuscator?
      có một giải pháp nữa là mọi code gốc đều không cần làm xáo trộn, và mình dùng kỹ thuật checksum để kiểm tra xem bản game đang chạy có bị thay đổi file code:

      1. trong android, các bước thực hiện, sau khi build xong lấy ra checksum của file global-metadata.dat, libil2cpp.so cho vào trong integrity.json StreamingAssets của build.apk, đồng thời khi mở game lên, kiểm tra checksum hash của global-metadata.dat, libil2cpp.so hiện tại còn giống với checksum hash của integrity.json lúc developer build hay không, cấu trúc file integrity.json:
        0e057b51-024d-4a64-959e-b281491b1bdd-image.png
        nếu hash không giống nhau chứng tỏ đã global-metadata.dat hoặc libil2cpp.so đã bị thay đổi, đây chính là bản mod.

      2. điều này chỉ hoạt động trên file build.apk, còn file build.aab upload lên google play thì hoạt động như thế nào? khi download từ google play và cài đặt vào thiết bị thì có 2 file base.apk và armeabi-v7a.apk hoặc arm64-v8a.apk tuỳ theo kiến trúc thiết bị, mình chỉ cần kiểm tra trong cả base.apk, armeabi-v7a.apk hoặc arm64-v8a.apk các hash tồn tại và chính xác là được

      3. nếu vậy kẻ tấn công sau khi mod chỉ cần chỉnh lại file integrity.json để hash khớp với lần chỉnh vừa rồi là bypass mất cái validate của developer thì sao? vì vậy mình cần xác minh độ hợp lệ file integrity.json là chính xác bằng cách dùng signature ký lại nội dung bằng private secret key (mỗi nội dung duy nhất thì signature là duy nhất, bất kỳ 1 thay đổi nhỏ kể cả dấu cách thì signature sẽ không chính xác), sau đó khi runtime kiểm tra xem signature này có hợp lệ hay không, là biết file này thực sự có bị mod chỉnh sửa hay không?
        cấu trúc final integrity.json
        57766243-6b5e-4432-bca9-f413fbaa3a5d-image.png

      4. đối với ios, công việc sẽ khác biệt một chút so với android, mình cần validate build.app/Frameworks/UnityFramework.framework/UnityFramework (thay thế cho libil2cpp.so của android)

      trong hình dưới dây cho thấy có 3 file lạ (được bôi đen, đây là file và thư viện được inject kèm vào để thay đổi logic trong runtime) trong bản mod ios khác với bản build gốc của mình, vì vậy trong ios cần thêm 1 bước kiểm tra các file ở /Frameworks/* có file nào được thêm vào khác với bản gốc của mình hay không, nếu có nghĩa là file mod
      4d013305-a3da-460e-a1e9-35609688ec66-image.png

      1. Vậy kẻ tấn công chỉ cần tìm ra đoạn code thư viện kiểm tra của mình và sửa lại để bypass thì làm sao? Vậy mình cần xáo trộn mã của thư viện kiểm tra này, chống search text, thay đổi ý nghĩa tên hàm, class, ngoài ra có thể đặt thêm các code check giả để kẻ tấn công đi sâu vào đó nhưng không thể làm được gì vì code check thật đã bị che dấu kỹ ở nơi khác.
        ví dụ về chống search text
        bbcda18a-6ed3-471f-9e87-f7e5c3dad63a-image.png

      tham khảo repo https://github.com/Pitaya-VN/AntiMod-Checksum, đã test build android apk, aab upload Google play internal testing, iOS
      Cách sử dụng thư viện Antimod-Checksum:

      if (useValidateAntiMod) {
          var validateResult = await CheckSum.CheckSumChecker.TryValidate();
          if (!validateResult.success) {
              SendReportUseMod(userId, validateResult.error);
      
              if (forceCrashIfMod) Application.Quit();
          }
      }
      

      Các settings mà developer có thể chỉnh sửa thêm nằm ở file CheckSumConstance.cs https://github.com/Pitaya-VN/AntiMod-Checksum/blob/master/Assets/CheckSum/Runtime/Scripts/CheckSumConstance.cs

      posted in Developer Discussion
      C
      changx
    • RE: Thông (Web)

      @admin đợt vừa rồi tạo trang landingpage cho web natv cho thị trường Việt nam rất ổn hài lòng, link vn.nightvalley.gg

      posted in Adminstrator
      C
      changx
    • RE: Tổng hợp Optimize NATV

      https://learn.unity.com/tutorial/fixing-performance-problems-2019-3

      posted in Developer Discussion
      C
      changx
    • Tổng hợp Optimize NATV

      🧩Unity Performance Optimization (NATV)


      I. Giảm DrawCall

      ⚙️ Dùng SRP Batcher của URP

      Gom nhiều mesh có cùng shader vào 1 batch drawcall, giúp giảm đáng kể chi phí CPU khi render nhiều object như monster hoặc vật cản.

      Bật trong Project Settings → Graphics → SRP Batcher

      SRP Batcher Example


      🧱 Dùng chung Shader / Material / Layer

      • Chuyển các vật cản, nơi đứng của monster thành Spine Animation (shader: Universal Render Pipeline/2D/Spine/Skeleton)
      • Dùng Layer giống nhau giữa monster và vật cản để SRP gom vào chung batch
      • Việc biến SpriteRenderer thành Spine Animation rất đơn giản – tham khảo CreateSpineAnimForTextureExtensions.cs

      Skeleton vs Sprite Renderer

      🔴 Vòng tròn đỏ: Skeleton Animation
      🟢 Vòng tròn xanh: Sprite Renderer


      🧩 Sprite Renderer – Dùng chung Material & Atlas

      Khi vẫn dùng SpriteRenderer, cần dùng chung material và chung atlas để Unity nhóm các sprite lại thành 1 drawcall.

      Sprite Renderer Atlas Example


      🌿 GPU Instancing

      Dùng cho các object giống nhau:
      Ví dụ: cỏ, hiệu ứng vòng tròn tấn công, đá nhỏ, lá rơi, …
      → Giúp giảm drawcall mà vẫn giữ hiệu ứng động.


      🧭 Sắp xếp Layer hợp lý

      • Layer 1: Mặt đất, cây cỏ, rác, đá sỏi, các vật có thể bị đạp lên.
      • Layer 2: Monster, hero, effect, dog, base, obstacle – nơi các vật thể có thể che nhau.

      Layer Setup Example


      II. Optimize CPU

      🔄 Giảm đối tượng cần vẽ / tính toán

      Monster nằm ngoài camera chỉ cần cập nhật logic, không cần render.
      → Trong SkeletonAnimation, bật:
      Advanced → Update When Invisible → Only Event Timelines

      Update When Invisible Setting


      ⚡ Loại bỏ Collider / Rigidbody không cần thiết

      Dùng hệ thống va chạm logic tự tính thay cho vật lý thật:

      • Tính khoảng cách giữa tâm hero và monster.
      • Nếu nhỏ hơn bán kính kỹ năng → trong phạm vi tấn công.
        → Tham khảo: DetectCollideManager.cs.

      III. Optimize Addressables

      📦 Tổ chức theo Chapter

      Mỗi chapter là 1 group riêng, chọn Pack Together để Unity build thành 1 bundle duy nhất → gọn nhẹ, dễ load/unload.

      Addressable Group Setup


      🚫 Không bỏ Scene vào Addressable

      Scene luôn bị build dạng Pack Separately, dễ bị duplicate asset → tăng size bundle và memory.


      🧾 Config chỉ chứa data cơ bản

      • Chỉ nên chứa text, number, config JSON.
      • Không chứa sprite, texture, sound để tránh trùng atlas.
      • Nếu buộc phải có sprite → bỏ tích Include in Build trong atlas và dùng SpriteAtlasManager để load thủ công.

      Sprite Atlas Include Example

      Tham khảo: SpriteAtlasAddressableLoader.cs
      Tài liệu: Unity SpriteAtlasManager


      ♻️ Giải phóng tài nguyên đúng cách

      Mọi handle async của Addressables (LoadAssetAsync, InstantiateAsync, …)
      → Phải gọi Addressables.Release(handle) khi không dùng nữa.


      IV. Optimize Memory

      🚫 Không tạo object mới mỗi frame

      Không new string, new List, new Dictionary... trong Update → gây GC Alloc liên tục.
      → Dùng biến cache hoặc List.Clear() để tái sử dụng.

      Memory GC Example


      🧹 Dọn dẹp bộ nhớ

      Khi đổi scene, gọi:

      Addressables.ReleaseUnusedAssets();
      Resources.UnloadUnusedAssets();
      

      Có thể thêm GC.Collect() trong màn hình loading để thu hồi managed memory.


      🧱 Giảm phân mảnh bộ nhớ

      • Dùng List, Queue, Stack, Dictionary có capacity cố định.
      • Tính trước kích thước hợp lý ngay từ đầu để tránh reallocate.
      • Tham khảo code gốc của .NET List<T>:
        List.cs#L198
      • Dùng Object Pooling cho object tạo–hủy thường xuyên (đạn, hiệu ứng, popup...).

      V. Optimize Spine Animation

      • Gom tất cả monster cùng chapter vào 1 atlas duy nhất
        → ví dụ: Chapter_01_Monsters.atlas, Chapter_02_Monsters.atlas.
      • Nếu vùng sprite là hình lồi, cắt lồi để giảm vertices/triangle.
      • Bật Triangulate Low Detail hoặc giảm precision vùng mesh.
      • Tránh sprite có alpha phức tạp hoặc chi tiết biên thừa.
      • Giảm bone, attachment, slot không cần thiết.
      • Gộp nhiều attachment tĩnh thành 1 sprite duy nhất.
      • Xóa bone test, placeholder, slot không render.
      • Animation đơn giản (vd: idle) chỉ cần 2 keyframe đầu/cuối (Spine tự nội suy).
      • Xóa keyframe dư và animation không dùng runtime.

      VI. Optimize Texture

      • Dùng ASTC thay vì RGBA32 để giảm kích thước build và RAM.
      • Dùng ASTC 10×10 hoặc 12×12 nếu chất lượng hình ảnh vẫn ổn.
      • Gom sprite hợp lý vào atlas để giảm texture switch.

      VII. Optimize UIToolkit

      • Dùng usageHints:
        Giúp Unity xác định cách tối ưu layout và render cho UI element.
      • Giảm thay đổi transform:
        Không thay đổi position, rotation, scale thường xuyên — vì sẽ rebuild layout gây tốn CPU.

      VIII. Optimize Âm Thanh

      • Decompress On Load:
        Cho âm thanh phát liên tục (bắn, hit, bước chân).
      • Streaming:
        Cho âm thanh dài (nhạc nền, voice, cutscene).
      • Compress In Memory:
        Cho âm thanh ngắn, ít khi phát (click UI, hiệu ứng nhỏ).
      • Load In Background:
        Dùng cho các âm thanh không yêu cầu độ chính xác cao để không chặn luồng chính.

      IX. Optimize Script

      • Cache các giá trị dùng nhiều:

        • Animator.StringToHash (trigger animation).
        • LayerMask.GetMask, LayerMask.NameToLayer.
        • Camera.main (vì gọi lại sẽ FindGameObjectWithTag).
      • Tạo collection có capacity cụ thể:
        new List<int>(64) hoặc new Dictionary<string, int>(32) để tránh mở rộng mảng.

      • Dùng HashSet thay List:
        Nếu chỉ cần kiểm tra Contains, Add, Remove – tốc độ nhanh hơn nhiều.

      • Local Function:
        Đặt ở lớp ngoài cùng, truyền tham số rõ ràng để tránh GC capture closure.

      • Xoá các hàm Update() hoặc FixedUpdate() nếu không dùng đến

      posted in Developer Discussion
      C
      changx
    • [NATV] Spine Editor

      mục tiêu chính: mỗi chapter dùng 1 atlas do Spine Editor xuất, các bước thực hiện:
      1: tìm ra tên chung cho các con monster và các hiệu ứng của con monster đó, ví dụ như đạn, effect riêng liên quan đến con monster đó, bỏ vào thư mục chứa tên chung này, đưa các hình ảnh vào cho đúng
      2: effect chung của cả chapter là Star_Stunned để vào 1 thư mục riêng
      3: dùng Find And Replace trong SpineEditor để thực hiện đổi tên
      4: loại bỏ những tấm hình không được dùng đến, thực hiện đô
      5: export nhưng không cần pack atlas
      6: copy toàn bộ thư mục images ra 1 thư mục atlas_images, texture packer atlas_images này với scale size là (1 và 0.5)

      có thể tham khảo từ đây: \192.168.0.200\Shared\Artist\GAME - NATV\Chapter-Raidboss REFACTOR
      cho ví dụ:
      trong chapter raidboss:

      1. con Pertrified_Berserker có 1 hiệu ứng là Pertrified_Berserker FX, vì vậy tên chung của cả 2 file spine này là Pertrified_Berserker, tạo 1 thư mục images/Pertrified_Berserker trong cả Pertrified_Berserker và Pertrified_Berserker FX
        5086ca06-0bc5-4fab-a070-45a58e8a4cfe-image.png
      2. tạo 1 thư mục là images/Star_Stunned
        454fa63c-e2d5-431c-8163-98885a6f3980-image.png

      Find: (.*)
      Replace: Pertrified_Berserker/$1
      các option còn lại chọn giống trong hình dưới
      3a2d4bbf-123c-4a84-ab82-c28b3d2af19d-image.png
      4. tìm xem các hình không được dùng đến thì thực hiện xoá
      5. export với setting sau:
      14dec5e4-e069-4438-a3f6-19dd26035035-image.png
      6: tạo 1 folder là Chapter_Raidboss_atlas, tạo thêm 1 images: copy tất cả thư mục images từ tất cả các con trên và bỏ vào trong này, sau đó dùng Spine Editor chọn chức năng TexturePacker: Input folder: là thư mục vừa rồi
      6618ba2b-c0a7-47c4-abf8-b2cfdf5396af-image.png
      Settings giống như sau:
      b3ac9e86-0778-484a-8711-a07783b346d8-image.png

      posted in Developer Discussion
      C
      changx
    • RE: sử dụng ACTk unity cho hiệu quả
      // cả 2 cái initValue1 và initValue2 có 5 và 3 đều dễ bị chỉnh sửa, thay thành dạng GlobalConstance hoặc static readonly ObscuredInt INIT_VALUE_1= 5;
      
      public class ConfigItem {
          public ObscuredInt initValue1 = 5;
      
          public ObscuredInt initValue2;
      
          public ConfigItem () {
              initValue2 = 3;
          } 
      }
      
      
      posted in Developer Discussion
      C
      changx
    • sử dụng ACTk unity cho hiệu quả

      đối với property

      // không an toàn vì khi build ra nó sẽ thành public int get_coin() { // chỗ này quá dễ để cheat chỉnh sửa }
      public int coin => s_coin;
      private ObscuredInt s_coin;
      
      // chỗ này cũng như property coin bên trên, không sử dụng
      public int getCoin() { return  s_coin; }
      

      đổi lại thành

      // khó để return 1 ObscuredInt hơn trong memory
          public ObscuredInt coin => s_coin;
          private ObscuredInt s_coin;
      
      

      trong hàm

      void ChangeGemToCoin() {
          // 2 cái gemCost và coinRecv  không an toàn
          ObscuredInt gemCost = 10;
          ObscuredInt coinRecv = 100;
      
          // vd logic để thay đổi tiền 
          applicationUser.gem -= gemCost;
          applicationUser.coin += coinRecv;
      }
      

      đổi lại thành

      // cái này này đảm bảo việc GEM_COST và COIN_RECV phải được tạo ra khi vừa start chương trình
      public class GlobalConstance {
          public static readonly ObscuredInt GEM_COST = 10;
          public static readonly ObscuredInt COIN_RECV = 100;
      }
      
      void ChangeGemToCoin() {
          
          ObscuredInt gemCost = GlobalConstance.GEM_COST;
          ObscuredInt coinRecv = GlobalConstance.COIN_RECV;
      
          // vd logic để thay đổi tiền 
          applicationUser.gem -= gemCost;
          applicationUser.coin += coinRecv;
      }
      
      posted in Developer Discussion
      C
      changx
    • Lỗ hỏng bảo mật của ACTk Unity

      xem thử 1 biến như ObscuredLong, đoạn hàm Encrypt và Decrypt được viết như sau

      public static long Encrypt(long value, long key)
      {
      	unchecked
      	{
      		return (value ^ key) + key;
      	}
      }
      
      public static long Decrypt(long value, long key)
      {
      	unchecked
      	{
      		return (value - key) ^ key;
      	}
      }
      

      value là giá trị gốc chưa bị che, nếu key = 0 thì (value ^ key) + key sẽ luôn = value; đoạn này được sử dụng trong ObscuredLong như sau:

      currentCryptoKey = GeneratePrivateKey();
      hiddenValue = Encrypt(value, currentCryptoKey);
      

      nếu vậy mục tiêu của mình chỉ cần dùng CheatEngine chỉnh sửa để GeneratePrivateKey() luôn return 0; vậy là hiddenValue sẽ lộ ra và lúc này chỉnh sửa bất kỳ giá trị nào dùng ObscuredLong dễ dàng

      posted in Developer Discussion
      C
      changx
    • RE: Lưu

      @changx
      Việc không localization khi thực hiện NEWS và MAIL BOX tính năng mới trong NATV có thể châm trước là vì game chỉ hỗ trợ tiếng anh nên không thực hiện localization

      Tuy nhiên việc scrollrect hoạt động không chính xác tại MAIL BOX đã được yêu cầu để fix thì lại bỏ qua scrollrect chạy trên NEWS là cần xem lại về thái độ không cẩn thận khi thực hiện task, rõ ràng 2 cái này là Lưu là người làm UI cho nó thì khi biết được cần fix scrollrect bên MAIL BOX thì tại sao không fix luôn bên NEWS, thái độ thiếu chủ động trong công việc, cần phải chủ động và linh hoạt hơn

      posted in Adminstrator
      C
      changx